1// Copyright 2015 Google Inc. All rights reserved
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package kati
16
17import (
18	"os"
19	"path/filepath"
20	"reflect"
21	"strings"
22	"testing"
23)
24
25type mockfs struct {
26	id       fileid
27	ofscache *fsCacheT
28}
29
30func newFS() *mockfs {
31	fs := &mockfs{
32		ofscache: fsCache,
33	}
34	fsCache = &fsCacheT{
35		ids:     make(map[string]fileid),
36		dirents: make(map[fileid][]dirent),
37	}
38	fsCache.ids["."] = fs.dir(".").id
39	return fs
40}
41
42func (m *mockfs) dump(t *testing.T) {
43	t.Log("fs ids:")
44	for name, id := range fsCache.ids {
45		t.Logf(" %q=%v", name, id)
46	}
47	t.Log("fs dirents:")
48	for id, ents := range fsCache.dirents {
49		t.Logf(" %v:", id)
50		for _, ent := range ents {
51			t.Logf("  %#v", ent)
52		}
53	}
54}
55
56func (m *mockfs) close() {
57	fsCache = m.ofscache
58}
59
60func (m *mockfs) dirent(name string, mode os.FileMode) dirent {
61	id := m.id
62	m.id.ino++
63	return dirent{id: id, name: name, mode: mode, lmode: mode}
64}
65
66func (m *mockfs) addent(name string, ent dirent) {
67	dir, name := filepath.Split(name)
68	dir = strings.TrimSuffix(dir, string(filepath.Separator))
69	if dir == "" {
70		dir = "."
71	}
72	di, ok := fsCache.ids[dir]
73	if !ok {
74		if dir == "." {
75			panic(". not found:" + name)
76		}
77		de := m.add(m.dir, dir)
78		fsCache.ids[dir] = de.id
79		di = de.id
80	}
81	for _, e := range fsCache.dirents[di] {
82		if e.name == ent.name {
83			return
84		}
85	}
86	fsCache.dirents[di] = append(fsCache.dirents[di], ent)
87}
88
89func (m *mockfs) add(t func(string) dirent, name string) dirent {
90	ent := t(filepath.Base(name))
91	m.addent(name, ent)
92	return ent
93}
94
95func (m *mockfs) symlink(name string, ent dirent) {
96	lent := ent
97	lent.lmode = os.ModeSymlink
98	lent.name = filepath.Base(name)
99	m.addent(name, lent)
100}
101
102func (m *mockfs) dirref(name string) dirent {
103	id := fsCache.ids[name]
104	return dirent{id: id, name: filepath.Base(name), mode: os.ModeDir, lmode: os.ModeDir}
105}
106
107func (m *mockfs) notfound() dirent        { return dirent{id: invalidFileid} }
108func (m *mockfs) dir(name string) dirent  { return m.dirent(name, os.ModeDir) }
109func (m *mockfs) file(name string) dirent { return m.dirent(name, os.FileMode(0644)) }
110
111func TestFilepathClean(t *testing.T) {
112	fs := newFS()
113	defer fs.close()
114	di := fs.add(fs.dir, "dir")
115	fs.symlink("link", di)
116
117	fs.dump(t)
118
119	for _, tc := range []struct {
120		path string
121		want string
122	}{
123		{path: "foo", want: "foo"},
124		{path: ".", want: "."},
125		{path: "./", want: "."},
126		{path: ".///", want: "."},
127		{path: "", want: "."},
128		{path: "foo/bar", want: "foo/bar"},
129		{path: "./foo", want: "foo"},
130		{path: "foo///", want: "foo"},
131		{path: "foo//bar", want: "foo/bar"},
132		{path: "foo/../bar", want: "foo/../bar"},   // foo doesn't exist
133		{path: "dir/../bar", want: "bar"},          // dir is real dir
134		{path: "link/../bar", want: "link/../bar"}, // link is symlink
135		{path: "foo/./bar", want: "foo/bar"},
136		{path: "/foo/bar", want: "/foo/bar"},
137	} {
138		if got, want := filepathClean(tc.path), tc.want; got != want {
139			t.Errorf("filepathClean(%q)=%q; want=%q", tc.path, got, want)
140		}
141	}
142}
143
144func TestParseFindCommand(t *testing.T) {
145	fs := newFS()
146	defer fs.close()
147	fs.add(fs.dir, "testdir")
148
149	maxdepth := 1<<31 - 1
150	for _, tc := range []struct {
151		cmd  string
152		want findCommand
153	}{
154		{
155			cmd: "find testdir",
156			want: findCommand{
157				finddirs: []string{"testdir"},
158				ops:      []findOp{findOpPrint{}},
159				depth:    maxdepth,
160			},
161		},
162		{
163			cmd: "find .",
164			want: findCommand{
165				finddirs: []string{"."},
166				ops:      []findOp{findOpPrint{}},
167				depth:    maxdepth,
168			},
169		},
170		{
171			cmd: "find ",
172			want: findCommand{
173				finddirs: []string{"."},
174				ops:      []findOp{findOpPrint{}},
175				depth:    maxdepth,
176			},
177		},
178		{
179			cmd: "find testdir/../testdir",
180			want: findCommand{
181				finddirs: []string{"testdir/../testdir"},
182				ops:      []findOp{findOpPrint{}},
183				depth:    maxdepth,
184			},
185		},
186		{
187			cmd: "find testdir -print",
188			want: findCommand{
189				finddirs: []string{"testdir"},
190				ops:      []findOp{findOpPrint{}},
191				depth:    maxdepth,
192			},
193		},
194		{
195			cmd: "find testdir -name foo",
196			want: findCommand{
197				finddirs: []string{"testdir"},
198				ops:      []findOp{findOpName("foo"), findOpPrint{}},
199				depth:    maxdepth,
200			},
201		},
202		{
203			cmd: `find testdir -name "file1"`,
204			want: findCommand{
205				finddirs: []string{"testdir"},
206				ops:      []findOp{findOpName("file1"), findOpPrint{}},
207				depth:    maxdepth,
208			},
209		},
210		{
211			cmd: `find testdir -name "*1"`,
212			want: findCommand{
213				finddirs: []string{"testdir"},
214				ops:      []findOp{findOpName("*1"), findOpPrint{}},
215				depth:    maxdepth,
216			},
217		},
218		{
219			cmd: `find testdir -name "*1" -and -name "file*"`,
220			want: findCommand{
221				finddirs: []string{"testdir"},
222				ops:      []findOp{findOpAnd{findOpName("*1"), findOpName("file*")}, findOpPrint{}},
223				depth:    maxdepth,
224			},
225		},
226		{
227			cmd: `find testdir -name "*1" -or -name "file*"`,
228			want: findCommand{
229				finddirs: []string{"testdir"},
230				ops:      []findOp{findOpOr{findOpName("*1"), findOpName("file*")}, findOpPrint{}},
231				depth:    maxdepth,
232			},
233		},
234		{
235			cmd: `find testdir -name "*1" -or -type f`,
236			want: findCommand{
237				finddirs: []string{"testdir"},
238				ops:      []findOp{findOpOr{findOpName("*1"), findOpRegular{}}, findOpPrint{}},
239				depth:    maxdepth,
240			},
241		},
242		{
243			cmd: `find testdir -name "*1" -or -not -type f`,
244			want: findCommand{
245				finddirs: []string{"testdir"},
246				ops:      []findOp{findOpOr{findOpName("*1"), findOpNot{findOpRegular{}}}, findOpPrint{}},
247				depth:    maxdepth,
248			},
249		},
250		{
251			cmd: `find testdir -name "*1" -or \! -type f`,
252			want: findCommand{
253				finddirs: []string{"testdir"},
254				ops:      []findOp{findOpOr{findOpName("*1"), findOpNot{findOpRegular{}}}, findOpPrint{}},
255				depth:    maxdepth,
256			},
257		},
258		{
259			cmd: `find testdir -name "*1" -or -type d`,
260			want: findCommand{
261				finddirs: []string{"testdir"},
262				ops:      []findOp{findOpOr{findOpName("*1"), findOpType{mode: os.ModeDir}}, findOpPrint{}},
263				depth:    maxdepth,
264			},
265		},
266		{
267			cmd: `find testdir -name "*1" -or -type l`,
268			want: findCommand{
269				finddirs: []string{"testdir"},
270				ops:      []findOp{findOpOr{findOpName("*1"), findOpType{mode: os.ModeSymlink}}, findOpPrint{}},
271				depth:    maxdepth,
272			},
273		},
274		{
275			cmd: `find testdir -name "*1" -a -type l -o -name "dir*"`,
276			want: findCommand{
277				finddirs: []string{"testdir"},
278				ops:      []findOp{findOpOr{findOpAnd([]findOp{findOpName("*1"), findOpType{mode: os.ModeSymlink}}), findOpName("dir*")}, findOpPrint{}},
279				depth:    maxdepth,
280			},
281		},
282		{
283			cmd: `find testdir \( -name "dir*" -o -name "*1" \) -a -type f`,
284			want: findCommand{
285				finddirs: []string{"testdir"},
286				ops:      []findOp{findOpAnd([]findOp{findOpOr{findOpName("dir*"), findOpName("*1")}, findOpRegular{}}), findOpPrint{}},
287				depth:    maxdepth,
288			},
289		},
290		{
291			cmd: `cd testdir && find`,
292			want: findCommand{
293				chdir:    "testdir",
294				finddirs: []string{"."},
295				ops:      []findOp{findOpPrint{}},
296				depth:    maxdepth,
297			},
298		},
299		{
300			cmd: `test -d testdir && find testdir`,
301			want: findCommand{
302				testdir:  "testdir",
303				finddirs: []string{"testdir"},
304				ops:      []findOp{findOpPrint{}},
305				depth:    maxdepth,
306			},
307		},
308		{
309			cmd: `if [ -d testdir ] ; then find testdir ; fi`,
310			want: findCommand{
311				testdir:  "testdir",
312				finddirs: []string{"testdir"},
313				ops:      []findOp{findOpPrint{}},
314				depth:    maxdepth,
315			},
316		},
317		{
318			cmd: `if [ -d testdir ]; then find testdir; fi`,
319			want: findCommand{
320				testdir:  "testdir",
321				finddirs: []string{"testdir"},
322				ops:      []findOp{findOpPrint{}},
323				depth:    maxdepth,
324			},
325		},
326		{
327			cmd: `if [ -d testdir ]; then cd testdir && find .; fi`,
328			want: findCommand{
329				chdir:    "testdir",
330				testdir:  "testdir",
331				finddirs: []string{"."},
332				ops:      []findOp{findOpPrint{}},
333				depth:    maxdepth,
334			},
335		},
336		{
337			cmd: `find testdir -name dir2 -prune -o -name file1`,
338			want: findCommand{
339				finddirs: []string{"testdir"},
340				ops:      []findOp{findOpOr{findOpAnd([]findOp{findOpName("dir2"), findOpPrune{}}), findOpName("file1")}, findOpPrint{}},
341				depth:    maxdepth,
342			},
343		},
344		{
345			cmd: `find testdir testdir`,
346			want: findCommand{
347				finddirs: []string{"testdir", "testdir"},
348				ops:      []findOp{findOpPrint{}},
349				depth:    maxdepth,
350			},
351		},
352		{
353			cmd: `find -L testdir -type f`,
354			want: findCommand{
355				finddirs:       []string{"testdir"},
356				followSymlinks: true,
357				ops:            []findOp{findOpRegular{followSymlinks: true}, findOpPrint{}},
358				depth:          maxdepth,
359			},
360		},
361		{
362			cmd: `cd testdir; find -L . -type f`,
363			want: findCommand{
364				chdir:          "testdir",
365				finddirs:       []string{"."},
366				followSymlinks: true,
367				ops:            []findOp{findOpRegular{followSymlinks: true}, findOpPrint{}},
368				depth:          maxdepth,
369			},
370		},
371		{
372			cmd: `find testdir -maxdepth 1`,
373			want: findCommand{
374				finddirs: []string{"testdir"},
375				ops:      []findOp{findOpPrint{}},
376				depth:    1,
377			},
378		},
379		{
380			cmd: `find testdir -maxdepth 0`,
381			want: findCommand{
382				finddirs: []string{"testdir"},
383				ops:      []findOp{findOpPrint{}},
384				depth:    0,
385			},
386		},
387	} {
388		fc, err := parseFindCommand(tc.cmd)
389		if err != nil {
390			t.Errorf("parseFindCommand(%q)=_, %v; want=_, <nil>", tc.cmd, err)
391			continue
392		}
393		if got, want := fc, tc.want; !reflect.DeepEqual(got, want) {
394			t.Errorf("parseFindCommand(%q)=%#v\n want=%#v\n", tc.cmd, got, want)
395		}
396	}
397
398}
399
400func TestParseFindCommandFail(t *testing.T) {
401	for _, cmd := range []string{
402		`find testdir -maxdepth hoge`,
403		`find testdir -maxdepth 1hoge`,
404		`find testdir -maxdepth -1`,
405	} {
406		_, err := parseFindCommand(cmd)
407		if err == nil {
408			t.Errorf("parseFindCommand(%q)=_, <nil>; want=_, err", cmd)
409		}
410	}
411}
412
413func TestFind(t *testing.T) {
414	fs := newFS()
415	defer fs.close()
416	fs.add(fs.file, "Makefile")
417	fs.add(fs.file, "testdir/file1")
418	fs.add(fs.file, "testdir/file2")
419	file1 := fs.add(fs.file, "testdir/dir1/file1")
420	dir1 := fs.dirref("testdir/dir1")
421	fs.add(fs.file, "testdir/dir1/file2")
422	fs.add(fs.file, "testdir/dir2/file1")
423	fs.add(fs.file, "testdir/dir2/file2")
424	fs.symlink("testdir/dir2/link1", file1)
425	fs.symlink("testdir/dir2/link2", dir1)
426	fs.symlink("testdir/dir2/link3", fs.notfound())
427
428	fs.dump(t)
429
430	maxdepth := 1<<31 - 1
431	for _, tc := range []struct {
432		fc   findCommand
433		want string
434	}{
435		{
436			fc: findCommand{
437				finddirs: []string{"testdir"},
438				ops:      []findOp{findOpPrint{}},
439				depth:    maxdepth,
440			},
441			want: `testdir testdir/file1 testdir/file2 testdir/dir1 testdir/dir1/file1 testdir/dir1/file2 testdir/dir2 testdir/dir2/file1 testdir/dir2/file2 testdir/dir2/link1 testdir/dir2/link2 testdir/dir2/link3`,
442		},
443		{
444			fc: findCommand{
445				finddirs: []string{"."},
446				ops:      []findOp{findOpPrint{}},
447				depth:    maxdepth,
448			},
449			want: `. ./Makefile ./testdir ./testdir/file1 ./testdir/file2 ./testdir/dir1 ./testdir/dir1/file1 ./testdir/dir1/file2 ./testdir/dir2 ./testdir/dir2/file1 ./testdir/dir2/file2 ./testdir/dir2/link1 ./testdir/dir2/link2 ./testdir/dir2/link3`,
450		},
451		{
452			fc: findCommand{
453				finddirs: []string{"./"},
454				ops:      []findOp{findOpPrint{}},
455				depth:    maxdepth,
456			},
457			want: `./ ./Makefile ./testdir ./testdir/file1 ./testdir/file2 ./testdir/dir1 ./testdir/dir1/file1 ./testdir/dir1/file2 ./testdir/dir2 ./testdir/dir2/file1 ./testdir/dir2/file2 ./testdir/dir2/link1 ./testdir/dir2/link2 ./testdir/dir2/link3`,
458		},
459		{
460			fc: findCommand{
461				finddirs: []string{".///"},
462				ops:      []findOp{findOpPrint{}},
463				depth:    maxdepth,
464			},
465			want: `./// .///Makefile .///testdir .///testdir/file1 .///testdir/file2 .///testdir/dir1 .///testdir/dir1/file1 .///testdir/dir1/file2 .///testdir/dir2 .///testdir/dir2/file1 .///testdir/dir2/file2 .///testdir/dir2/link1 .///testdir/dir2/link2 .///testdir/dir2/link3`,
466		},
467		{
468			fc: findCommand{
469				finddirs: []string{"./."},
470				ops:      []findOp{findOpPrint{}},
471				depth:    maxdepth,
472			},
473			want: `./. ././Makefile ././testdir ././testdir/file1 ././testdir/file2 ././testdir/dir1 ././testdir/dir1/file1 ././testdir/dir1/file2 ././testdir/dir2 ././testdir/dir2/file1 ././testdir/dir2/file2 ././testdir/dir2/link1 ././testdir/dir2/link2 ././testdir/dir2/link3`,
474		},
475		{
476			fc: findCommand{
477				finddirs: []string{"././"},
478				ops:      []findOp{findOpPrint{}},
479				depth:    maxdepth,
480			},
481			want: `././ ././Makefile ././testdir ././testdir/file1 ././testdir/file2 ././testdir/dir1 ././testdir/dir1/file1 ././testdir/dir1/file2 ././testdir/dir2 ././testdir/dir2/file1 ././testdir/dir2/file2 ././testdir/dir2/link1 ././testdir/dir2/link2 ././testdir/dir2/link3`,
482		},
483		{
484			fc: findCommand{
485				finddirs: []string{"testdir/../testdir"},
486				ops:      []findOp{findOpPrint{}},
487				depth:    maxdepth,
488			},
489			want: `testdir/../testdir testdir/../testdir/file1 testdir/../testdir/file2 testdir/../testdir/dir1 testdir/../testdir/dir1/file1 testdir/../testdir/dir1/file2 testdir/../testdir/dir2 testdir/../testdir/dir2/file1 testdir/../testdir/dir2/file2 testdir/../testdir/dir2/link1 testdir/../testdir/dir2/link2 testdir/../testdir/dir2/link3`,
490		},
491		{
492			fc: findCommand{
493				finddirs: []string{"testdir"},
494				ops:      []findOp{findOpName("foo"), findOpPrint{}},
495				depth:    maxdepth,
496			},
497			want: ``,
498		},
499		{
500			fc: findCommand{
501				finddirs: []string{"testdir"},
502				ops:      []findOp{findOpName("file1"), findOpPrint{}},
503				depth:    maxdepth,
504			},
505			want: `testdir/file1 testdir/dir1/file1 testdir/dir2/file1`,
506		},
507		{
508			fc: findCommand{
509				finddirs: []string{"testdir"},
510				ops:      []findOp{findOpAnd{findOpName("*1"), findOpName("file*")}, findOpPrint{}},
511				depth:    maxdepth,
512			},
513			want: `testdir/file1 testdir/dir1/file1 testdir/dir2/file1`,
514		},
515		{
516			fc: findCommand{
517				finddirs: []string{"testdir"},
518				ops:      []findOp{findOpOr{findOpName("*1"), findOpName("file*")}, findOpPrint{}},
519				depth:    maxdepth,
520			},
521			want: `testdir/file1 testdir/file2 testdir/dir1 testdir/dir1/file1 testdir/dir1/file2 testdir/dir2/file1 testdir/dir2/file2 testdir/dir2/link1`,
522		},
523		{
524			fc: findCommand{
525				finddirs: []string{"testdir"},
526				ops:      []findOp{findOpOr{findOpName("*1"), findOpRegular{}}, findOpPrint{}},
527				depth:    maxdepth,
528			},
529			want: `testdir/file1 testdir/file2 testdir/dir1 testdir/dir1/file1 testdir/dir1/file2 testdir/dir2/file1 testdir/dir2/file2 testdir/dir2/link1`,
530		},
531		{
532			fc: findCommand{
533				finddirs: []string{"testdir"},
534				ops:      []findOp{findOpOr{findOpName("*1"), findOpNot{findOpRegular{}}}, findOpPrint{}},
535				depth:    maxdepth,
536			},
537			want: `testdir testdir/file1 testdir/dir1 testdir/dir1/file1 testdir/dir2 testdir/dir2/file1 testdir/dir2/link1 testdir/dir2/link2 testdir/dir2/link3`,
538		},
539		{
540			fc: findCommand{
541				finddirs: []string{"testdir"},
542				ops:      []findOp{findOpOr{findOpName("*1"), findOpType{mode: os.ModeDir}}, findOpPrint{}},
543				depth:    maxdepth,
544			},
545			want: `testdir testdir/file1 testdir/dir1 testdir/dir1/file1 testdir/dir2 testdir/dir2/file1 testdir/dir2/link1`,
546		},
547		{
548			fc: findCommand{
549				finddirs: []string{"testdir"},
550				ops:      []findOp{findOpOr{findOpName("*1"), findOpType{mode: os.ModeSymlink}}, findOpPrint{}},
551				depth:    maxdepth,
552			},
553			want: `testdir/file1 testdir/dir1 testdir/dir1/file1 testdir/dir2/file1 testdir/dir2/link1 testdir/dir2/link2 testdir/dir2/link3`,
554		},
555		{
556			fc: findCommand{
557				finddirs: []string{"testdir"},
558				ops:      []findOp{findOpOr{findOpAnd([]findOp{findOpName("*1"), findOpType{mode: os.ModeSymlink}}), findOpName("dir*")}, findOpPrint{}},
559				depth:    maxdepth,
560			},
561			want: `testdir/dir1 testdir/dir2 testdir/dir2/link1`,
562		},
563		{
564			fc: findCommand{
565				finddirs: []string{"testdir"},
566				ops:      []findOp{findOpOr{findOpAnd([]findOp{findOpName("*1"), findOpType{mode: os.ModeSymlink}}), findOpName("dir*")}, findOpPrint{}},
567				depth:    maxdepth,
568			},
569			want: `testdir/dir1 testdir/dir2 testdir/dir2/link1`,
570		},
571		{
572			fc: findCommand{
573				finddirs: []string{"testdir"},
574				ops:      []findOp{findOpAnd([]findOp{findOpOr{findOpName("dir*"), findOpName("*1")}, findOpRegular{}}), findOpPrint{}},
575				depth:    maxdepth,
576			},
577			want: `testdir/file1 testdir/dir1/file1 testdir/dir2/file1`,
578		},
579		{
580			fc: findCommand{
581				chdir:    "testdir",
582				finddirs: []string{"."},
583				ops:      []findOp{findOpPrint{}},
584				depth:    maxdepth,
585			},
586			want: `. ./file1 ./file2 ./dir1 ./dir1/file1 ./dir1/file2 ./dir2 ./dir2/file1 ./dir2/file2 ./dir2/link1 ./dir2/link2 ./dir2/link3`,
587		},
588		{
589			fc: findCommand{
590				chdir:    "testdir",
591				finddirs: []string{"../testdir"},
592				ops:      []findOp{findOpPrint{}},
593				depth:    maxdepth,
594			},
595			want: `../testdir ../testdir/file1 ../testdir/file2 ../testdir/dir1 ../testdir/dir1/file1 ../testdir/dir1/file2 ../testdir/dir2 ../testdir/dir2/file1 ../testdir/dir2/file2 ../testdir/dir2/link1 ../testdir/dir2/link2 ../testdir/dir2/link3`,
596		},
597		{
598			fc: findCommand{
599				testdir:  "testdir",
600				finddirs: []string{"testdir"},
601				ops:      []findOp{findOpPrint{}},
602				depth:    maxdepth,
603			},
604			want: `testdir testdir/file1 testdir/file2 testdir/dir1 testdir/dir1/file1 testdir/dir1/file2 testdir/dir2 testdir/dir2/file1 testdir/dir2/file2 testdir/dir2/link1 testdir/dir2/link2 testdir/dir2/link3`,
605		},
606		{
607			fc: findCommand{
608				chdir:    "testdir",
609				testdir:  "testdir",
610				finddirs: []string{"."},
611				ops:      []findOp{findOpPrint{}},
612				depth:    maxdepth,
613			},
614			want: `. ./file1 ./file2 ./dir1 ./dir1/file1 ./dir1/file2 ./dir2 ./dir2/file1 ./dir2/file2 ./dir2/link1 ./dir2/link2 ./dir2/link3`,
615		},
616		{
617			fc: findCommand{
618				finddirs: []string{"testdir"},
619				ops:      []findOp{findOpOr{findOpAnd([]findOp{findOpName("dir2"), findOpPrune{}}), findOpName("file1")}, findOpPrint{}},
620				depth:    maxdepth,
621			},
622			want: `testdir/file1 testdir/dir1/file1 testdir/dir2`,
623		},
624		{
625			fc: findCommand{
626				finddirs: []string{"testdir", "testdir"},
627				ops:      []findOp{findOpPrint{}},
628				depth:    maxdepth,
629			},
630			want: `testdir testdir/file1 testdir/file2 testdir/dir1 testdir/dir1/file1 testdir/dir1/file2 testdir/dir2 testdir/dir2/file1 testdir/dir2/file2 testdir/dir2/link1 testdir/dir2/link2 testdir/dir2/link3 testdir testdir/file1 testdir/file2 testdir/dir1 testdir/dir1/file1 testdir/dir1/file2 testdir/dir2 testdir/dir2/file1 testdir/dir2/file2 testdir/dir2/link1 testdir/dir2/link2 testdir/dir2/link3`,
631		},
632		// symlink
633		{
634			fc: findCommand{
635				finddirs:       []string{"testdir"},
636				followSymlinks: true,
637				ops:            []findOp{findOpRegular{followSymlinks: true}, findOpPrint{}},
638				depth:          maxdepth,
639			},
640			want: `testdir/file1 testdir/file2 testdir/dir1/file1 testdir/dir1/file2 testdir/dir2/file1 testdir/dir2/file2 testdir/dir2/link1 testdir/dir2/link2/file1 testdir/dir2/link2/file2`,
641		},
642		{
643			fc: findCommand{
644				finddirs:       []string{"testdir"},
645				followSymlinks: true,
646				ops:            []findOp{findOpType{mode: os.ModeDir, followSymlinks: true}, findOpPrint{}},
647				depth:          maxdepth,
648			},
649			want: `testdir testdir/dir1 testdir/dir2 testdir/dir2/link2`,
650		},
651		{
652			fc: findCommand{
653				finddirs:       []string{"testdir"},
654				followSymlinks: true,
655				ops:            []findOp{findOpType{mode: os.ModeSymlink, followSymlinks: true}, findOpPrint{}},
656				depth:          maxdepth,
657			},
658			want: `testdir/dir2/link3`,
659		},
660		{
661			fc: findCommand{
662				chdir:          "testdir",
663				finddirs:       []string{"."},
664				followSymlinks: true,
665				ops:            []findOp{findOpRegular{followSymlinks: true}, findOpPrint{}},
666				depth:          maxdepth,
667			},
668			want: `./file1 ./file2 ./dir1/file1 ./dir1/file2 ./dir2/file1 ./dir2/file2 ./dir2/link1 ./dir2/link2/file1 ./dir2/link2/file2`,
669		},
670		// maxdepth
671		{
672			fc: findCommand{
673				finddirs: []string{"testdir"},
674				ops:      []findOp{findOpPrint{}},
675				depth:    1,
676			},
677			want: `testdir testdir/file1 testdir/file2 testdir/dir1 testdir/dir2`,
678		},
679		{
680			fc: findCommand{
681				finddirs: []string{"testdir"},
682				ops:      []findOp{findOpPrint{}},
683				depth:    2,
684			},
685			want: `testdir testdir/file1 testdir/file2 testdir/dir1 testdir/dir1/file1 testdir/dir1/file2 testdir/dir2 testdir/dir2/file1 testdir/dir2/file2 testdir/dir2/link1 testdir/dir2/link2 testdir/dir2/link3`,
686		},
687		{
688			fc: findCommand{
689				finddirs: []string{"testdir"},
690				ops:      []findOp{findOpPrint{}},
691				depth:    0,
692			},
693			want: `testdir`,
694		},
695	} {
696		var wb wordBuffer
697		tc.fc.run(&wb)
698		if got, want := wb.buf.String(), tc.want; got != want {
699			t.Errorf("%#v\n got  %q\n want %q", tc.fc, got, want)
700		}
701	}
702}
703
704func TestParseFindleavesCommand(t *testing.T) {
705	for _, tc := range []struct {
706		cmd  string
707		want findleavesCommand
708	}{
709		{
710			cmd: `build/tools/findleaves.py --prune=out --prune=.repo --prune=.git . CleanSpec.mk`,
711			want: findleavesCommand{
712				name:     "CleanSpec.mk",
713				dirs:     []string{"."},
714				prunes:   []string{"out", ".repo", ".git"},
715				mindepth: -1,
716			},
717		},
718		{
719			cmd: `build/tools/findleaves.py --prune=out --prune=.repo --prune=.git --mindepth=2  art bionic Android.mk`,
720			want: findleavesCommand{
721				name:     "Android.mk",
722				dirs:     []string{"art", "bionic"},
723				prunes:   []string{"out", ".repo", ".git"},
724				mindepth: 2,
725			},
726		},
727	} {
728		fc, err := parseFindleavesCommand(tc.cmd)
729		if err != nil {
730			t.Errorf("parseFindleavesCommand(%q)=_, %v; want=_, <nil", tc.cmd, err)
731			continue
732		}
733		if got, want := fc, tc.want; !reflect.DeepEqual(got, want) {
734			t.Errorf("parseFindleavesCommand(%q)=%#v\n want=%#v\n", tc.cmd, got, want)
735		}
736	}
737}
738
739func TestFindleaves(t *testing.T) {
740	fs := newFS()
741	defer fs.close()
742
743	fs.add(fs.file, "art/Android.mk")
744	fs.add(fs.file, "art/compiler/Android.mk")
745	fs.add(fs.file, "art/CleanSpec.mk")
746	fs.add(fs.file, "bionic/Android.mk")
747	fs.add(fs.file, "bionic/CleanSpec.mk")
748	fs.add(fs.file, "bootable/recovery/Android.mk")
749	fs.add(fs.file, "bootable/recovery/CleanSpec.mk")
750	fs.add(fs.file, "frameworks/base/Android.mk")
751	fs.add(fs.file, "frameworks/base/CleanSpec.mk")
752	fs.add(fs.file, "frameworks/base/cmds/am/Android.mk")
753	fs.add(fs.file, "frameworks/base/cmds/pm/Android.mk")
754	fs.add(fs.file, "frameworks/base/location/Android.mk")
755	fs.add(fs.file, "frameworks/base/packages/WAPPushManager/CleanSpec.mk")
756	fs.add(fs.file, "out/outputfile")
757	fs.add(fs.file, "art/.git/index")
758	fs.add(fs.file, ".repo/manifests")
759
760	fs.dump(t)
761
762	for _, tc := range []struct {
763		fc   findleavesCommand
764		want string
765	}{
766		{
767			fc: findleavesCommand{
768				name:     "CleanSpec.mk",
769				dirs:     []string{"."},
770				prunes:   []string{"out", ".repo", ".git"},
771				mindepth: -1,
772			},
773			want: `./art/CleanSpec.mk ./bionic/CleanSpec.mk ./bootable/recovery/CleanSpec.mk ./frameworks/base/CleanSpec.mk`,
774		},
775		{
776			fc: findleavesCommand{
777				name:     "Android.mk",
778				dirs:     []string{"art", "bionic", "frameworks/base"},
779				prunes:   []string{"out", ".repo", ".git"},
780				mindepth: 2,
781			},
782			want: `art/compiler/Android.mk frameworks/base/cmds/am/Android.mk frameworks/base/cmds/pm/Android.mk frameworks/base/location/Android.mk`,
783		},
784		{
785			fc: findleavesCommand{
786				name:     "Android.mk",
787				dirs:     []string{"art", "bionic", "frameworks/base"},
788				prunes:   []string{"out", ".repo", ".git"},
789				mindepth: 3,
790			},
791			want: `frameworks/base/cmds/am/Android.mk frameworks/base/cmds/pm/Android.mk`,
792		},
793	} {
794		var wb wordBuffer
795		tc.fc.run(&wb)
796		if got, want := wb.buf.String(), tc.want; got != want {
797			t.Errorf("%#v\n got  %q\n want %q", tc.fc, got, want)
798		}
799	}
800}
801