Home | History | Annotate | Download | only in kati
      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 
     15 package kati
     16 
     17 import (
     18 	"os"
     19 	"path/filepath"
     20 	"reflect"
     21 	"strings"
     22 	"testing"
     23 )
     24 
     25 type mockfs struct {
     26 	id       fileid
     27 	ofscache *fsCacheT
     28 }
     29 
     30 func 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 
     42 func (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 
     56 func (m *mockfs) close() {
     57 	fsCache = m.ofscache
     58 }
     59 
     60 func (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 
     66 func (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 
     89 func (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 
     95 func (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 
    102 func (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 
    107 func (m *mockfs) notfound() dirent        { return dirent{id: invalidFileid} }
    108 func (m *mockfs) dir(name string) dirent  { return m.dirent(name, os.ModeDir) }
    109 func (m *mockfs) file(name string) dirent { return m.dirent(name, os.FileMode(0644)) }
    110 
    111 func 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 
    144 func 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 
    400 func 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 
    413 func 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 
    704 func 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 
    739 func 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