OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 package cipd | |
6 | |
7 import ( | |
8 "bytes" | |
9 "fmt" | |
10 "io" | |
11 "io/ioutil" | |
12 "os" | |
13 "path/filepath" | |
14 "sort" | |
15 "testing" | |
16 | |
17 . "github.com/smartystreets/goconvey/convey" | |
18 ) | |
19 | |
20 func TestUtilities(t *testing.T) { | |
21 Convey("Given a temp directory", t, func() { | |
22 tempDir, err := ioutil.TempDir("", "cipd_test") | |
23 So(err, ShouldBeNil) | |
24 Reset(func() { os.RemoveAll(tempDir) }) | |
25 | |
26 // Wrappers that accept paths relative to tempDir. | |
27 touch := func(rel string) { | |
28 abs := filepath.Join(tempDir, filepath.FromSlash(rel)) | |
29 err := os.MkdirAll(filepath.Dir(abs), 0777) | |
30 So(err, ShouldBeNil) | |
31 f, err := os.Create(abs) | |
32 So(err, ShouldBeNil) | |
33 f.Close() | |
34 } | |
35 ensureLink := func(symlinkRel string, target string) error { | |
36 return ensureSymlink(filepath.Join(tempDir, symlinkRel),
target) | |
37 } | |
38 readLink := func(symlinkRel string) string { | |
39 val, err := os.Readlink(filepath.Join(tempDir, symlinkRe
l)) | |
40 So(err, ShouldBeNil) | |
41 return val | |
42 } | |
43 | |
44 Convey("ensureSymlink creates new symlink", func() { | |
45 So(ensureLink("symlink", "target"), ShouldBeNil) | |
46 So(readLink("symlink"), ShouldEqual, "target") | |
47 }) | |
48 | |
49 Convey("ensureSymlink builds full path", func() { | |
50 So(ensureLink(filepath.Join("a", "b", "c"), "target"), S
houldBeNil) | |
51 So(readLink(filepath.Join("a", "b", "c")), ShouldEqual,
"target") | |
52 }) | |
53 | |
54 Convey("ensureSymlink replaces existing one", func() { | |
55 So(ensureLink("symlink", "target"), ShouldBeNil) | |
56 So(ensureLink("symlink", "another"), ShouldBeNil) | |
57 So(readLink("symlink"), ShouldEqual, "another") | |
58 }) | |
59 | |
60 Convey("scanPackageDir works with empty dir", func() { | |
61 err := os.Mkdir(filepath.Join(tempDir, "dir"), 0777) | |
62 So(err, ShouldBeNil) | |
63 files := makeStringSet() | |
64 err = scanPackageDir(filepath.Join(tempDir, "dir"), file
s) | |
65 So(err, ShouldBeNil) | |
66 So(len(files), ShouldEqual, 0) | |
67 }) | |
68 | |
69 Convey("scanPackageDir works", func() { | |
70 touch("unrelated/1") | |
71 touch("dir/a/1") | |
72 touch("dir/a/2") | |
73 touch("dir/b/1") | |
74 touch("dir/.cipdpkg/abc") | |
75 touch("dir/.cipd/abc") | |
76 ensureLink("dir/a/sym_link", "target") | |
77 files := makeStringSet() | |
78 err := scanPackageDir(filepath.Join(tempDir, "dir"), fil
es) | |
79 So(err, ShouldBeNil) | |
80 names := sort.StringSlice{} | |
81 for n := range files { | |
82 names = append(names, filepath.ToSlash(n)) | |
83 } | |
84 names.Sort() | |
85 So(names, ShouldResemble, sort.StringSlice{ | |
86 "a/1", | |
87 "a/2", | |
88 "a/sym_link", | |
89 "b/1", | |
90 }) | |
91 }) | |
92 | |
93 Convey("ensureDirectoryGone works with missing dir", func() { | |
94 So(ensureDirectoryGone(filepath.Join(tempDir, "missing")
), ShouldBeNil) | |
95 }) | |
96 | |
97 Convey("ensureDirectoryGone works", func() { | |
98 touch("dir/a/1") | |
99 touch("dir/a/2") | |
100 touch("dir/b/1") | |
101 So(ensureDirectoryGone(filepath.Join(tempDir, "dir")), S
houldBeNil) | |
102 _, err := os.Stat(filepath.Join(tempDir, "dir")) | |
103 So(os.IsNotExist(err), ShouldBeTrue) | |
104 }) | |
105 | |
106 Convey("ensureFileGone works", func() { | |
107 touch("abc") | |
108 So(ensureFileGone(filepath.Join(tempDir, "abc")), Should
BeNil) | |
109 _, err := os.Stat(filepath.Join(tempDir, "abc")) | |
110 So(os.IsNotExist(err), ShouldBeTrue) | |
111 }) | |
112 | |
113 Convey("ensureFileGone works with missing file", func() { | |
114 So(ensureFileGone(filepath.Join(tempDir, "abc")), Should
BeNil) | |
115 }) | |
116 | |
117 Convey("ensureFileGone works with symlink", func() { | |
118 ensureLink("abc", "target") | |
119 So(ensureFileGone(filepath.Join(tempDir, "abc")), Should
BeNil) | |
120 _, err := os.Stat(filepath.Join(tempDir, "abc")) | |
121 So(os.IsNotExist(err), ShouldBeTrue) | |
122 }) | |
123 }) | |
124 } | |
125 | |
126 func TestDeployInstance(t *testing.T) { | |
127 Convey("Given a temp directory", t, func() { | |
128 tempDir, err := ioutil.TempDir("", "cipd_test") | |
129 So(err, ShouldBeNil) | |
130 Reset(func() { os.RemoveAll(tempDir) }) | |
131 | |
132 Convey("DeployInstance new empty package instance", func() { | |
133 inst := makeTestInstance("test/package", nil) | |
134 info, err := DeployInstance(tempDir, inst) | |
135 So(err, ShouldBeNil) | |
136 So(info, ShouldResemble, PackageState{ | |
137 PackageName: "test/package", | |
138 InstanceID: inst.InstanceID(), | |
139 }) | |
140 So(scanDir(tempDir), ShouldResemble, []string{ | |
141 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/.cipdpkg/manifest.json", | |
142 ".cipd/pkgs/test_package_B6R4ErK5ko/_current:012
3456789abcdef00000123456789abcdef0000", | |
143 }) | |
144 }) | |
145 | |
146 Convey("DeployInstance new non-empty package instance", func() { | |
147 inst := makeTestInstance("test/package", []File{ | |
148 makeTestFile("some/file/path", "data a", false), | |
149 makeTestFile("some/executable", "data b", true), | |
150 makeTestSymlink("some/symlink", "executable"), | |
151 }) | |
152 _, err := DeployInstance(tempDir, inst) | |
153 So(err, ShouldBeNil) | |
154 So(scanDir(tempDir), ShouldResemble, []string{ | |
155 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/.cipdpkg/manifest.json", | |
156 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/some/executable*", | |
157 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/some/file/path", | |
158 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/some/symlink:executable", | |
159 ".cipd/pkgs/test_package_B6R4ErK5ko/_current:012
3456789abcdef00000123456789abcdef0000", | |
160 "some/executable:../.cipd/pkgs/test_package_B6R4
ErK5ko/_current/some/executable", | |
161 "some/file/path:../../.cipd/pkgs/test_package_B6
R4ErK5ko/_current/some/file/path", | |
162 "some/symlink:../.cipd/pkgs/test_package_B6R4ErK
5ko/_current/some/symlink", | |
163 }) | |
164 // Ensure symlinks are actually traversable. | |
165 body, err := ioutil.ReadFile(filepath.Join(tempDir, "som
e", "file", "path")) | |
166 So(err, ShouldBeNil) | |
167 So(string(body), ShouldEqual, "data a") | |
168 // Symlink to symlink is traversable too. | |
169 body, err = ioutil.ReadFile(filepath.Join(tempDir, "some
", "symlink")) | |
170 So(err, ShouldBeNil) | |
171 So(string(body), ShouldEqual, "data b") | |
172 }) | |
173 | |
174 Convey("Redeploy same package instance", func() { | |
175 inst := makeTestInstance("test/package", []File{ | |
176 makeTestFile("some/file/path", "data a", false), | |
177 makeTestFile("some/executable", "data b", true), | |
178 makeTestSymlink("some/symlink", "executable"), | |
179 }) | |
180 _, err := DeployInstance(tempDir, inst) | |
181 So(err, ShouldBeNil) | |
182 _, err = DeployInstance(tempDir, inst) | |
183 So(err, ShouldBeNil) | |
184 So(scanDir(tempDir), ShouldResemble, []string{ | |
185 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/.cipdpkg/manifest.json", | |
186 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/some/executable*", | |
187 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/some/file/path", | |
188 ".cipd/pkgs/test_package_B6R4ErK5ko/0123456789ab
cdef00000123456789abcdef0000/some/symlink:executable", | |
189 ".cipd/pkgs/test_package_B6R4ErK5ko/_current:012
3456789abcdef00000123456789abcdef0000", | |
190 "some/executable:../.cipd/pkgs/test_package_B6R4
ErK5ko/_current/some/executable", | |
191 "some/file/path:../../.cipd/pkgs/test_package_B6
R4ErK5ko/_current/some/file/path", | |
192 "some/symlink:../.cipd/pkgs/test_package_B6R4ErK
5ko/_current/some/symlink", | |
193 }) | |
194 }) | |
195 | |
196 Convey("DeployInstance package update", func() { | |
197 oldPkg := makeTestInstance("test/package", []File{ | |
198 makeTestFile("some/file/path", "data a old", fal
se), | |
199 makeTestFile("some/executable", "data b old", tr
ue), | |
200 makeTestFile("old only", "data c old", true), | |
201 makeTestFile("mode change 1", "data d", true), | |
202 makeTestFile("mode change 2", "data e", false), | |
203 makeTestSymlink("symlink unchanged", "target"), | |
204 makeTestSymlink("symlink changed", "old target")
, | |
205 makeTestSymlink("symlink removed", "target"), | |
206 }) | |
207 oldPkg.instanceID = "00000000000000000000000000000000000
00000" | |
208 | |
209 newPkg := makeTestInstance("test/package", []File{ | |
210 makeTestFile("some/file/path", "data a new", fal
se), | |
211 makeTestFile("some/executable", "data b new", tr
ue), | |
212 makeTestFile("mode change 1", "data d", false), | |
213 makeTestFile("mode change 2", "data d", true), | |
214 makeTestSymlink("symlink unchanged", "target"), | |
215 makeTestSymlink("symlink changed", "new target")
, | |
216 }) | |
217 newPkg.instanceID = "11111111111111111111111111111111111
11111" | |
218 | |
219 _, err := DeployInstance(tempDir, oldPkg) | |
220 So(err, ShouldBeNil) | |
221 _, err = DeployInstance(tempDir, newPkg) | |
222 So(err, ShouldBeNil) | |
223 | |
224 So(scanDir(tempDir), ShouldResemble, []string{ | |
225 ".cipd/pkgs/test_package_B6R4ErK5ko/111111111111
1111111111111111111111111111/.cipdpkg/manifest.json", | |
226 ".cipd/pkgs/test_package_B6R4ErK5ko/111111111111
1111111111111111111111111111/mode change 1", | |
227 ".cipd/pkgs/test_package_B6R4ErK5ko/111111111111
1111111111111111111111111111/mode change 2*", | |
228 ".cipd/pkgs/test_package_B6R4ErK5ko/111111111111
1111111111111111111111111111/some/executable*", | |
229 ".cipd/pkgs/test_package_B6R4ErK5ko/111111111111
1111111111111111111111111111/some/file/path", | |
230 ".cipd/pkgs/test_package_B6R4ErK5ko/111111111111
1111111111111111111111111111/symlink changed:new target", | |
231 ".cipd/pkgs/test_package_B6R4ErK5ko/111111111111
1111111111111111111111111111/symlink unchanged:target", | |
232 ".cipd/pkgs/test_package_B6R4ErK5ko/_current:111
1111111111111111111111111111111111111", | |
233 "mode change 1:.cipd/pkgs/test_package_B6R4ErK5k
o/_current/mode change 1", | |
234 "mode change 2:.cipd/pkgs/test_package_B6R4ErK5k
o/_current/mode change 2", | |
235 "some/executable:../.cipd/pkgs/test_package_B6R4
ErK5ko/_current/some/executable", | |
236 "some/file/path:../../.cipd/pkgs/test_package_B6
R4ErK5ko/_current/some/file/path", | |
237 "symlink changed:.cipd/pkgs/test_package_B6R4ErK
5ko/_current/symlink changed", | |
238 "symlink unchanged:.cipd/pkgs/test_package_B6R4E
rK5ko/_current/symlink unchanged", | |
239 }) | |
240 }) | |
241 | |
242 Convey("DeployInstance two different packages", func() { | |
243 pkg1 := makeTestInstance("test/package", []File{ | |
244 makeTestFile("some/file/path", "data a old", fal
se), | |
245 makeTestFile("some/executable", "data b old", tr
ue), | |
246 makeTestFile("pkg1 file", "data c", false), | |
247 }) | |
248 pkg1.instanceID = "0000000000000000000000000000000000000
000" | |
249 | |
250 // Nesting in package names is allowed. | |
251 pkg2 := makeTestInstance("test/package/another", []File{ | |
252 makeTestFile("some/file/path", "data a new", fal
se), | |
253 makeTestFile("some/executable", "data b new", tr
ue), | |
254 makeTestFile("pkg2 file", "data d", false), | |
255 }) | |
256 pkg2.instanceID = "1111111111111111111111111111111111111
111" | |
257 | |
258 _, err := DeployInstance(tempDir, pkg1) | |
259 So(err, ShouldBeNil) | |
260 _, err = DeployInstance(tempDir, pkg2) | |
261 So(err, ShouldBeNil) | |
262 | |
263 // TODO: Conflicting symlinks point to last installed pa
ckage, it is not | |
264 // very deterministic. | |
265 So(scanDir(tempDir), ShouldResemble, []string{ | |
266 ".cipd/pkgs/package_another_4HL4H61fGm/111111111
1111111111111111111111111111111/.cipdpkg/manifest.json", | |
267 ".cipd/pkgs/package_another_4HL4H61fGm/111111111
1111111111111111111111111111111/pkg2 file", | |
268 ".cipd/pkgs/package_another_4HL4H61fGm/111111111
1111111111111111111111111111111/some/executable*", | |
269 ".cipd/pkgs/package_another_4HL4H61fGm/111111111
1111111111111111111111111111111/some/file/path", | |
270 ".cipd/pkgs/package_another_4HL4H61fGm/_current:
1111111111111111111111111111111111111111", | |
271 ".cipd/pkgs/test_package_B6R4ErK5ko/000000000000
0000000000000000000000000000/.cipdpkg/manifest.json", | |
272 ".cipd/pkgs/test_package_B6R4ErK5ko/000000000000
0000000000000000000000000000/pkg1 file", | |
273 ".cipd/pkgs/test_package_B6R4ErK5ko/000000000000
0000000000000000000000000000/some/executable*", | |
274 ".cipd/pkgs/test_package_B6R4ErK5ko/000000000000
0000000000000000000000000000/some/file/path", | |
275 ".cipd/pkgs/test_package_B6R4ErK5ko/_current:000
0000000000000000000000000000000000000", | |
276 "pkg1 file:.cipd/pkgs/test_package_B6R4ErK5ko/_c
urrent/pkg1 file", | |
277 "pkg2 file:.cipd/pkgs/package_another_4HL4H61fGm
/_current/pkg2 file", | |
278 "some/executable:../.cipd/pkgs/package_another_4
HL4H61fGm/_current/some/executable", | |
279 "some/file/path:../../.cipd/pkgs/package_another
_4HL4H61fGm/_current/some/file/path", | |
280 }) | |
281 }) | |
282 | |
283 Convey("Try to deploy package instance with bad package name", f
unc() { | |
284 _, err := DeployInstance(tempDir, makeTestInstance("../t
est/package", nil)) | |
285 So(err, ShouldNotBeNil) | |
286 }) | |
287 | |
288 Convey("Try to deploy package instance with bad instance ID", fu
nc() { | |
289 inst := makeTestInstance("test/package", nil) | |
290 inst.instanceID = "../000000000" | |
291 _, err := DeployInstance(tempDir, inst) | |
292 So(err, ShouldNotBeNil) | |
293 }) | |
294 }) | |
295 } | |
296 | |
297 func TestFindDeployed(t *testing.T) { | |
298 Convey("Given a temp directory", t, func() { | |
299 tempDir, err := ioutil.TempDir("", "cipd_test") | |
300 So(err, ShouldBeNil) | |
301 Reset(func() { os.RemoveAll(tempDir) }) | |
302 | |
303 Convey("FindDeployed works with empty dir", func() { | |
304 out, err := FindDeployed(tempDir) | |
305 So(err, ShouldBeNil) | |
306 So(out, ShouldBeNil) | |
307 }) | |
308 | |
309 Convey("FindDeployed works", func() { | |
310 // Deploy a bunch of stuff. | |
311 _, err := DeployInstance(tempDir, makeTestInstance("test
/pkg/123", nil)) | |
312 So(err, ShouldBeNil) | |
313 _, err = DeployInstance(tempDir, makeTestInstance("test/
pkg/456", nil)) | |
314 So(err, ShouldBeNil) | |
315 _, err = DeployInstance(tempDir, makeTestInstance("test/
pkg", nil)) | |
316 So(err, ShouldBeNil) | |
317 _, err = DeployInstance(tempDir, makeTestInstance("test"
, nil)) | |
318 So(err, ShouldBeNil) | |
319 | |
320 // Verify it is discoverable. | |
321 out, err := FindDeployed(tempDir) | |
322 So(err, ShouldBeNil) | |
323 So(out, ShouldResemble, []PackageState{ | |
324 PackageState{ | |
325 PackageName: "test", | |
326 InstanceID: "0123456789abcdef0000012345
6789abcdef0000", | |
327 }, | |
328 PackageState{ | |
329 PackageName: "test/pkg", | |
330 InstanceID: "0123456789abcdef0000012345
6789abcdef0000", | |
331 }, | |
332 PackageState{ | |
333 PackageName: "test/pkg/123", | |
334 InstanceID: "0123456789abcdef0000012345
6789abcdef0000", | |
335 }, | |
336 PackageState{ | |
337 PackageName: "test/pkg/456", | |
338 InstanceID: "0123456789abcdef0000012345
6789abcdef0000", | |
339 }, | |
340 }) | |
341 }) | |
342 }) | |
343 } | |
344 | |
345 func TestRemoveDeployed(t *testing.T) { | |
346 Convey("Given a temp directory", t, func() { | |
347 tempDir, err := ioutil.TempDir("", "cipd_test") | |
348 So(err, ShouldBeNil) | |
349 Reset(func() { os.RemoveAll(tempDir) }) | |
350 | |
351 Convey("RemoveDeployed works with missing package", func() { | |
352 err := RemoveDeployed(tempDir, "package/path") | |
353 So(err, ShouldBeNil) | |
354 }) | |
355 | |
356 Convey("RemoveDeployed works", func() { | |
357 // Deploy some instance (to keep it). | |
358 inst := makeTestInstance("test/package/123", []File{ | |
359 makeTestFile("some/file/path1", "data a", false)
, | |
360 makeTestFile("some/executable1", "data b", true)
, | |
361 }) | |
362 _, err := DeployInstance(tempDir, inst) | |
363 So(err, ShouldBeNil) | |
364 | |
365 // Deploy another instance (to remove it). | |
366 inst = makeTestInstance("test/package", []File{ | |
367 makeTestFile("some/file/path2", "data a", false)
, | |
368 makeTestFile("some/executable2", "data b", true)
, | |
369 makeTestSymlink("some/symlink", "executable"), | |
370 }) | |
371 _, err = DeployInstance(tempDir, inst) | |
372 So(err, ShouldBeNil) | |
373 | |
374 // Now remove the second package. | |
375 err = RemoveDeployed(tempDir, "test/package") | |
376 So(err, ShouldBeNil) | |
377 | |
378 // Verify the final state (only first package should sur
vive). | |
379 So(scanDir(tempDir), ShouldResemble, []string{ | |
380 ".cipd/pkgs/package_123_Wnok5l4iFr/0123456789abc
def00000123456789abcdef0000/.cipdpkg/manifest.json", | |
381 ".cipd/pkgs/package_123_Wnok5l4iFr/0123456789abc
def00000123456789abcdef0000/some/executable1*", | |
382 ".cipd/pkgs/package_123_Wnok5l4iFr/0123456789abc
def00000123456789abcdef0000/some/file/path1", | |
383 ".cipd/pkgs/package_123_Wnok5l4iFr/_current:0123
456789abcdef00000123456789abcdef0000", | |
384 "some/executable1:../.cipd/pkgs/package_123_Wnok
5l4iFr/_current/some/executable1", | |
385 "some/file/path1:../../.cipd/pkgs/package_123_Wn
ok5l4iFr/_current/some/file/path1", | |
386 }) | |
387 }) | |
388 }) | |
389 } | |
390 | |
391 //////////////////////////////////////////////////////////////////////////////// | |
392 | |
393 type testPackageInstance struct { | |
394 packageName string | |
395 instanceID string | |
396 files []File | |
397 } | |
398 | |
399 // makeTestInstance returns PackageInstance implementation with mocked guts. | |
400 func makeTestInstance(name string, files []File) *testPackageInstance { | |
401 // Generate and append manifest file. | |
402 out := bytes.Buffer{} | |
403 err := writeManifest(&Manifest{ | |
404 FormatVersion: manifestFormatVersion, | |
405 PackageName: name, | |
406 }, &out) | |
407 if err != nil { | |
408 panic("Failed to write a manifest") | |
409 } | |
410 files = append(files, makeTestFile(manifestName, string(out.Bytes()), fa
lse)) | |
411 return &testPackageInstance{ | |
412 packageName: name, | |
413 instanceID: "0123456789abcdef00000123456789abcdef0000", | |
414 files: files, | |
415 } | |
416 } | |
417 | |
418 func (f *testPackageInstance) Close() error { return nil } | |
419 func (f *testPackageInstance) PackageName() string { return f.packageName
} | |
420 func (f *testPackageInstance) InstanceID() string { return f.instanceID } | |
421 func (f *testPackageInstance) Files() []File { return f.files } | |
422 func (f *testPackageInstance) DataReader() io.ReadSeeker { panic("Not implemente
d") } | |
423 | |
424 //////////////////////////////////////////////////////////////////////////////// | |
425 | |
426 // scanDir returns list of files (regular and symlinks) it finds in a directory. | |
427 // Symlinks are returned as "path:target". Regular executable files are suffixed | |
428 // with '*'. All paths are relative to the scanned directory and slash | |
429 // separated. Symlink targets are slash separated too, but otherwise not | |
430 // modified. Does not look inside symlinked directories. | |
431 func scanDir(root string) (out []string) { | |
432 err := filepath.Walk(root, func(path string, info os.FileInfo, err error
) error { | |
433 if err != nil { | |
434 return err | |
435 } | |
436 rel, err := filepath.Rel(root, path) | |
437 if err != nil { | |
438 return err | |
439 } | |
440 if info.Mode().IsDir() { | |
441 return nil | |
442 } | |
443 | |
444 rel = filepath.ToSlash(rel) | |
445 target, err := os.Readlink(path) | |
446 var item string | |
447 if err == nil { | |
448 item = fmt.Sprintf("%s:%s", rel, filepath.ToSlash(target
)) | |
449 } else { | |
450 if info.Mode().IsRegular() { | |
451 item = rel | |
452 } else { | |
453 item = fmt.Sprintf("%s:??????", rel) | |
454 } | |
455 } | |
456 | |
457 suffix := "" | |
458 if info.Mode().IsRegular() && (info.Mode().Perm()&0100) != 0 { | |
459 suffix = "*" | |
460 } | |
461 | |
462 out = append(out, item+suffix) | |
463 return nil | |
464 }) | |
465 if err != nil { | |
466 panic("Failed to walk a directory") | |
467 } | |
468 return | |
469 } | |
OLD | NEW |