Merge "Add BenchmarkProperties to cc_defaults"
diff --git a/finder/finder.go b/finder/finder.go
index 89be0f5..f20bed2 100644
--- a/finder/finder.go
+++ b/finder/finder.go
@@ -1393,17 +1393,25 @@
 	for _, child := range children {
 		linkBits := child.Mode() & os.ModeSymlink
 		isLink := linkBits != 0
-		if child.IsDir() {
-			if !isLink {
+		if isLink {
+			childPath := filepath.Join(path, child.Name())
+			childStat, err := f.filesystem.Stat(childPath)
+			if err != nil {
+				// If stat fails this is probably a broken or dangling symlink, treat it as a file.
+				subfiles = append(subfiles, child.Name())
+			} else if childStat.IsDir() {
 				// Skip symlink dirs.
 				// We don't have to support symlink dirs because
 				// that would cause duplicates.
-				subdirs = append(subdirs, child.Name())
+			} else {
+				// We do have to support symlink files because the link name might be
+				// different than the target name
+				// (for example, Android.bp -> build/soong/root.bp)
+				subfiles = append(subfiles, child.Name())
 			}
+		} else if child.IsDir() {
+			subdirs = append(subdirs, child.Name())
 		} else {
-			// We do have to support symlink files because the link name might be
-			// different than the target name
-			// (for example, Android.bp -> build/soong/root.bp)
 			subfiles = append(subfiles, child.Name())
 		}
 
diff --git a/finder/finder_test.go b/finder/finder_test.go
index f6d0aa9..88b0c05 100644
--- a/finder/finder_test.go
+++ b/finder/finder_test.go
@@ -20,11 +20,8 @@
 	"log"
 	"os"
 	"path/filepath"
-	"reflect"
-	"runtime/debug"
 	"sort"
 	"testing"
-	"time"
 
 	"android/soong/finder/fs"
 )
@@ -41,7 +38,7 @@
 func newFinderWithNumThreads(t *testing.T, filesystem *fs.MockFs, cacheParams CacheParams, numThreads int) *Finder {
 	f, err := newFinderAndErr(t, filesystem, cacheParams, numThreads)
 	if err != nil {
-		fatal(t, err.Error())
+		t.Fatal(err.Error())
 	}
 	return f
 }
@@ -62,7 +59,7 @@
 func finderWithSameParams(t *testing.T, original *Finder) *Finder {
 	f, err := finderAndErrorWithSameParams(t, original)
 	if err != nil {
-		fatal(t, err.Error())
+		t.Fatal(err.Error())
 	}
 	return f
 }
@@ -78,146 +75,13 @@
 	return f, err
 }
 
-func write(t *testing.T, path string, content string, filesystem *fs.MockFs) {
-	parent := filepath.Dir(path)
-	filesystem.MkDirs(parent)
-	err := filesystem.WriteFile(path, []byte(content), 0777)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-
-func create(t *testing.T, path string, filesystem *fs.MockFs) {
-	write(t, path, "hi", filesystem)
-}
-
-func delete(t *testing.T, path string, filesystem *fs.MockFs) {
-	err := filesystem.Remove(path)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-
-func removeAll(t *testing.T, path string, filesystem *fs.MockFs) {
-	err := filesystem.RemoveAll(path)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-
-func move(t *testing.T, oldPath string, newPath string, filesystem *fs.MockFs) {
-	err := filesystem.Rename(oldPath, newPath)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-
-func link(t *testing.T, newPath string, oldPath string, filesystem *fs.MockFs) {
-	parentPath := filepath.Dir(newPath)
-	err := filesystem.MkDirs(parentPath)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-	err = filesystem.Symlink(oldPath, newPath)
-	if err != nil {
-		fatal(t, err.Error())
-	}
-}
-func read(t *testing.T, path string, filesystem *fs.MockFs) string {
-	reader, err := filesystem.Open(path)
-	if err != nil {
-		t.Fatalf(err.Error())
-	}
-	bytes, err := ioutil.ReadAll(reader)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-	return string(bytes)
-}
-func modTime(t *testing.T, path string, filesystem *fs.MockFs) time.Time {
-	stats, err := filesystem.Lstat(path)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-	return stats.ModTime()
-}
-func setReadable(t *testing.T, path string, readable bool, filesystem *fs.MockFs) {
-	err := filesystem.SetReadable(path, readable)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-}
-
-func setReadErr(t *testing.T, path string, readErr error, filesystem *fs.MockFs) {
-	err := filesystem.SetReadErr(path, readErr)
-	if err != nil {
-		t.Fatal(err.Error())
-	}
-}
-
-func fatal(t *testing.T, message string) {
-	t.Error(message)
-	debug.PrintStack()
-	t.FailNow()
-}
-
-func assertSameResponse(t *testing.T, actual []string, expected []string) {
-	sort.Strings(actual)
-	sort.Strings(expected)
-	if !reflect.DeepEqual(actual, expected) {
-		fatal(
-			t,
-			fmt.Sprintf(
-				"Expected Finder to return these %v paths:\n  %v,\ninstead returned these %v paths:  %v\n",
-				len(expected), expected, len(actual), actual),
-		)
-	}
-}
-
-func assertSameStatCalls(t *testing.T, actual []string, expected []string) {
-	sort.Strings(actual)
-	sort.Strings(expected)
-
-	if !reflect.DeepEqual(actual, expected) {
-		fatal(
-			t,
-			fmt.Sprintf(
-				"Finder made incorrect Stat calls.\n"+
-					"Actual:\n"+
-					"%v\n"+
-					"Expected:\n"+
-					"%v\n"+
-					"\n",
-				actual, expected),
-		)
-	}
-}
-func assertSameReadDirCalls(t *testing.T, actual []string, expected []string) {
-	sort.Strings(actual)
-	sort.Strings(expected)
-
-	if !reflect.DeepEqual(actual, expected) {
-		fatal(
-			t,
-			fmt.Sprintf(
-				"Finder made incorrect ReadDir calls.\n"+
-					"Actual:\n"+
-					"%v\n"+
-					"Expected:\n"+
-					"%v\n"+
-					"\n",
-				actual, expected),
-		)
-	}
-}
-
 // runSimpleTests creates a few files, searches for findme.txt, and checks for the expected matches
 func runSimpleTest(t *testing.T, existentPaths []string, expectedMatches []string) {
 	filesystem := newFs()
 	root := "/tmp"
 	filesystem.MkDirs(root)
 	for _, path := range existentPaths {
-		create(t, filepath.Join(root, path), filesystem)
+		fs.Create(t, filepath.Join(root, path), filesystem)
 	}
 
 	finder := newFinder(t,
@@ -237,7 +101,7 @@
 	for i := range expectedMatches {
 		absoluteMatches = append(absoluteMatches, filepath.Join(root, expectedMatches[i]))
 	}
-	assertSameResponse(t, foundPaths, absoluteMatches)
+	fs.AssertSameResponse(t, foundPaths, absoluteMatches)
 }
 
 // testAgainstSeveralThreadcounts runs the given test for each threadcount that we care to test
@@ -288,7 +152,7 @@
 func TestEmptyPath(t *testing.T) {
 	filesystem := newFs()
 	root := "/tmp"
-	create(t, filepath.Join(root, "findme.txt"), filesystem)
+	fs.Create(t, filepath.Join(root, "findme.txt"), filesystem)
 
 	finder := newFinder(
 		t,
@@ -302,7 +166,7 @@
 
 	foundPaths := finder.FindNamedAt("", "findme.txt")
 
-	assertSameResponse(t, foundPaths, []string{})
+	fs.AssertSameResponse(t, foundPaths, []string{})
 }
 
 func TestFilesystemRoot(t *testing.T) {
@@ -311,7 +175,7 @@
 		filesystem := newFs()
 		root := "/"
 		createdPath := "/findme.txt"
-		create(t, createdPath, filesystem)
+		fs.Create(t, createdPath, filesystem)
 
 		finder := newFinderWithNumThreads(
 			t,
@@ -326,7 +190,7 @@
 
 		foundPaths := finder.FindNamedAt(root, "findme.txt")
 
-		assertSameResponse(t, foundPaths, []string{createdPath})
+		fs.AssertSameResponse(t, foundPaths, []string{createdPath})
 	}
 
 	testAgainstSeveralThreadcounts(t, testWithNumThreads)
@@ -334,7 +198,7 @@
 
 func TestNonexistentDir(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
 
 	_, err := newFinderAndErr(
 		t,
@@ -346,18 +210,18 @@
 		1,
 	)
 	if err == nil {
-		fatal(t, "Did not fail when given a nonexistent root directory")
+		t.Fatal("Did not fail when given a nonexistent root directory")
 	}
 }
 
 func TestExcludeDirs(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/exclude/findme.txt", filesystem)
-	create(t, "/tmp/exclude/subdir/findme.txt", filesystem)
-	create(t, "/tmp/subdir/exclude/findme.txt", filesystem)
-	create(t, "/tmp/subdir/subdir/findme.txt", filesystem)
-	create(t, "/tmp/subdir/findme.txt", filesystem)
-	create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/exclude/findme.txt", filesystem)
+	fs.Create(t, "/tmp/exclude/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/subdir/exclude/findme.txt", filesystem)
+	fs.Create(t, "/tmp/subdir/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -372,7 +236,7 @@
 
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/subdir/findme.txt",
 			"/tmp/subdir/subdir/findme.txt"})
@@ -380,15 +244,15 @@
 
 func TestPruneFiles(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/out/findme.txt", filesystem)
-	create(t, "/tmp/out/.ignore-out-dir", filesystem)
-	create(t, "/tmp/out/child/findme.txt", filesystem)
+	fs.Create(t, "/tmp/out/findme.txt", filesystem)
+	fs.Create(t, "/tmp/out/.ignore-out-dir", filesystem)
+	fs.Create(t, "/tmp/out/child/findme.txt", filesystem)
 
-	create(t, "/tmp/out2/.ignore-out-dir", filesystem)
-	create(t, "/tmp/out2/sub/findme.txt", filesystem)
+	fs.Create(t, "/tmp/out2/.ignore-out-dir", filesystem)
+	fs.Create(t, "/tmp/out2/sub/findme.txt", filesystem)
 
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/include/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/include/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -403,7 +267,7 @@
 
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/include/findme.txt"})
 }
@@ -412,10 +276,10 @@
 // tests of the filesystem root are in TestFilesystemRoot
 func TestRootDir(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/subdir/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
-	create(t, "/tmp/b/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/subdir/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -429,17 +293,17 @@
 
 	foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/findme.txt",
 			"/tmp/a/subdir/findme.txt"})
 }
 
 func TestUncachedDir(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/subdir/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
-	create(t, "/tmp/b/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/subdir/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/subdir/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -456,7 +320,7 @@
 	// fail to notice its slowness. Instead, we only ever search the cache for files
 	// to return, which enforces that we can determine which files will be
 	// interesting upfront.
-	assertSameResponse(t, foundPaths, []string{})
+	fs.AssertSameResponse(t, foundPaths, []string{})
 
 	finder.Shutdown()
 }
@@ -464,9 +328,9 @@
 func TestSearchingForFilesExcludedFromCache(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/misc.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/misc.txt", filesystem)
 
 	// set up the finder and run it
 	finder := newFinder(
@@ -483,7 +347,7 @@
 	// fail to notice its slowness. Instead, we only ever search the cache for files
 	// to return, which enforces that we can determine which files will be
 	// interesting upfront.
-	assertSameResponse(t, foundPaths, []string{})
+	fs.AssertSameResponse(t, foundPaths, []string{})
 
 	finder.Shutdown()
 }
@@ -491,12 +355,12 @@
 func TestRelativeFilePaths(t *testing.T) {
 	filesystem := newFs()
 
-	create(t, "/tmp/ignore/hi.txt", filesystem)
-	create(t, "/tmp/include/hi.txt", filesystem)
-	create(t, "/cwd/hi.txt", filesystem)
-	create(t, "/cwd/a/hi.txt", filesystem)
-	create(t, "/cwd/a/a/hi.txt", filesystem)
-	create(t, "/rel/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/ignore/hi.txt", filesystem)
+	fs.Create(t, "/tmp/include/hi.txt", filesystem)
+	fs.Create(t, "/cwd/hi.txt", filesystem)
+	fs.Create(t, "/cwd/a/hi.txt", filesystem)
+	fs.Create(t, "/cwd/a/a/hi.txt", filesystem)
+	fs.Create(t, "/rel/a/hi.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -509,25 +373,25 @@
 	defer finder.Shutdown()
 
 	foundPaths := finder.FindNamedAt("a", "hi.txt")
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"a/hi.txt",
 			"a/a/hi.txt"})
 
 	foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
 
 	foundPaths = finder.FindNamedAt(".", "hi.txt")
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"hi.txt",
 			"a/hi.txt",
 			"a/a/hi.txt"})
 
 	foundPaths = finder.FindNamedAt("/rel", "hi.txt")
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/rel/a/hi.txt"})
 
 	foundPaths = finder.FindNamedAt("/tmp/include", "hi.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/include/hi.txt"})
 }
 
 // have to run this test with the race-detector (`go test -race src/android/soong/finder/*.go`)
@@ -535,7 +399,7 @@
 func TestRootDirsContainedInOtherRootDirs(t *testing.T) {
 	filesystem := newFs()
 
-	create(t, "/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -549,15 +413,15 @@
 
 	foundPaths := finder.FindNamedAt("/tmp/a", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/b/c/d/e/f/g/h/i/j/findme.txt"})
 }
 
 func TestFindFirst(t *testing.T) {
 	filesystem := newFs()
-	create(t, "/tmp/a/hi.txt", filesystem)
-	create(t, "/tmp/b/hi.txt", filesystem)
-	create(t, "/tmp/b/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/a/hi.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -571,7 +435,7 @@
 
 	foundPaths := finder.FindFirstNamed("hi.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/hi.txt",
 			"/tmp/b/hi.txt"},
 	)
@@ -593,7 +457,7 @@
 		}
 		sort.Strings(paths)
 		for _, path := range paths {
-			create(t, path, filesystem)
+			fs.Create(t, path, filesystem)
 		}
 
 		// set up a finder
@@ -621,7 +485,7 @@
 		// check that each response was correct
 		for i := 0; i < numTests; i++ {
 			foundPaths := <-results
-			assertSameResponse(t, foundPaths, paths)
+			fs.AssertSameResponse(t, foundPaths, paths)
 		}
 	}
 
@@ -649,7 +513,7 @@
 	}
 	sort.Strings(allFiles)
 	for _, path := range allFiles {
-		create(t, path, filesystem)
+		fs.Create(t, path, filesystem)
 	}
 
 	// set up a finder
@@ -687,16 +551,16 @@
 	// check that each response was correct
 	for i := 0; i < numTests; i++ {
 		testRun := <-testRuns
-		assertSameResponse(t, testRun.foundMatches, testRun.correctMatches)
+		fs.AssertSameResponse(t, testRun.foundMatches, testRun.correctMatches)
 	}
 }
 
 func TestStrangelyFormattedPaths(t *testing.T) {
 	filesystem := newFs()
 
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
 
 	finder := newFinder(
 		t,
@@ -710,7 +574,7 @@
 
 	foundPaths := finder.FindNamedAt("//tmp//a//..", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/findme.txt",
 			"/tmp/b/findme.txt",
 			"/tmp/findme.txt"})
@@ -719,9 +583,9 @@
 func TestCorruptedCacheHeader(t *testing.T) {
 	filesystem := newFs()
 
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	write(t, "/finder/finder-db", "sample header", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Write(t, "/finder/finder-db", "sample header", filesystem)
 
 	finder := newFinder(
 		t,
@@ -735,7 +599,7 @@
 
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/findme.txt",
 			"/tmp/findme.txt"})
 }
@@ -743,8 +607,8 @@
 func TestCanUseCache(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -759,11 +623,11 @@
 	// check the response of the first finder
 	correctResponse := []string{"/tmp/a/findme.txt",
 		"/tmp/findme.txt"}
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 	finder.Shutdown()
 
 	// check results
-	cacheText := read(t, finder.DbPath, filesystem)
+	cacheText := fs.Read(t, finder.DbPath, filesystem)
 	if len(cacheText) < 1 {
 		t.Fatalf("saved cache db is empty\n")
 	}
@@ -780,8 +644,8 @@
 	finder2 := finderWithSameParams(t, finder)
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 	// check results
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
-	assertSameReadDirCalls(t, filesystem.StatCalls, statCalls)
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
+	fs.AssertSameReadDirCalls(t, filesystem.StatCalls, statCalls)
 
 	finder2.Shutdown()
 }
@@ -789,8 +653,8 @@
 func TestCorruptedCacheBody(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -807,7 +671,7 @@
 	// check the response of the first finder
 	correctResponse := []string{"/tmp/a/findme.txt",
 		"/tmp/findme.txt"}
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 	numStatCalls := len(filesystem.StatCalls)
 	numReadDirCalls := len(filesystem.ReadDirCalls)
 
@@ -828,7 +692,7 @@
 	finder2 := finderWithSameParams(t, finder)
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 	// check results
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 	numNewStatCalls := len(filesystem.StatCalls)
 	numNewReadDirCalls := len(filesystem.ReadDirCalls)
 	// It's permissable to make more Stat calls with a corrupted cache because
@@ -853,7 +717,7 @@
 func TestStatCalls(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
 
 	// run finder
 	finder := newFinder(
@@ -868,19 +732,19 @@
 	finder.Shutdown()
 
 	// check response
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
 }
 
 func TestFileAdded(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/ignoreme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/b/ignore.txt", filesystem)
-	create(t, "/tmp/b/c/nope.txt", filesystem)
-	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
+	fs.Create(t, "/tmp/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/ignore.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/nope.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -895,11 +759,11 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/b/c/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/findme.txt", filesystem)
 	filesystem.Clock.Tick()
 	filesystem.ClearMetrics()
 
@@ -908,9 +772,9 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/findme.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/c/findme.txt"})
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c"})
 	finder2.Shutdown()
 
 }
@@ -918,11 +782,11 @@
 func TestDirectoriesAdded(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/ignoreme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/b/ignore.txt", filesystem)
-	create(t, "/tmp/b/c/nope.txt", filesystem)
-	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
+	fs.Create(t, "/tmp/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/ignore.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/nope.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -936,13 +800,13 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/b/c/new/findme.txt", filesystem)
-	create(t, "/tmp/b/c/new/new2/findme.txt", filesystem)
-	create(t, "/tmp/b/c/new/new2/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/new/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/new/new2/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/new/new2/ignoreme.txt", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -950,11 +814,11 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/a/findme.txt", "/tmp/b/c/new/findme.txt", "/tmp/b/c/new/new2/findme.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b/c", "/tmp/b/c/new", "/tmp/b/c/new/new2"})
 
 	finder2.Shutdown()
 }
@@ -962,8 +826,8 @@
 func TestDirectoryAndSubdirectoryBothUpdated(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/hi1.txt", filesystem)
-	create(t, "/tmp/a/hi1.txt", filesystem)
+	fs.Create(t, "/tmp/hi1.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi1.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -977,12 +841,12 @@
 	foundPaths := finder.FindNamedAt("/tmp", "hi1.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/a/hi1.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi1.txt", "/tmp/a/hi1.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/hi2.txt", filesystem)
-	create(t, "/tmp/a/hi2.txt", filesystem)
+	fs.Create(t, "/tmp/hi2.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi2.txt", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -990,11 +854,11 @@
 	foundPaths = finder2.FindAll()
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/hi1.txt", "/tmp/hi2.txt", "/tmp/a/hi1.txt", "/tmp/a/hi2.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a"})
 
 	finder2.Shutdown()
 }
@@ -1002,11 +866,11 @@
 func TestFileDeleted(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/ignoreme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
-	create(t, "/tmp/b/c/nope.txt", filesystem)
-	create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
+	fs.Create(t, "/tmp/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/nope.txt", filesystem)
+	fs.Create(t, "/tmp/b/c/d/irrelevant.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1020,11 +884,11 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt", "/tmp/b/findme.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	delete(t, "/tmp/b/findme.txt", filesystem)
+	fs.Delete(t, "/tmp/b/findme.txt", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -1032,9 +896,9 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/findme.txt"})
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/c", "/tmp/b/c/d"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
 
 	finder2.Shutdown()
 }
@@ -1042,11 +906,11 @@
 func TestDirectoriesDeleted(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/1/findme.txt", filesystem)
-	create(t, "/tmp/a/1/2/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1060,7 +924,7 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/a/findme.txt",
 			"/tmp/a/1/findme.txt",
@@ -1069,7 +933,7 @@
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	removeAll(t, "/tmp/a/1", filesystem)
+	fs.RemoveAll(t, "/tmp/a/1", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -1077,7 +941,7 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/b/findme.txt"})
 	// Technically, we don't care whether /tmp/a/1/2 gets Statted or gets skipped
 	// if the Finder detects the nonexistence of /tmp/a/1
@@ -1087,9 +951,9 @@
 	// The Finder is currently implemented to always restat every dir and
 	// to not short-circuit due to nonexistence of parents (but it will remove
 	// missing dirs from the cache for next time)
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/a"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/a"})
 
 	finder2.Shutdown()
 }
@@ -1097,11 +961,11 @@
 func TestDirectoriesMoved(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/1/findme.txt", filesystem)
-	create(t, "/tmp/a/1/2/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1115,7 +979,7 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/a/findme.txt",
 			"/tmp/a/1/findme.txt",
@@ -1124,7 +988,7 @@
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	move(t, "/tmp/a", "/tmp/c", filesystem)
+	fs.Move(t, "/tmp/a", "/tmp/c", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -1132,7 +996,7 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/b/findme.txt",
 			"/tmp/c/findme.txt",
@@ -1146,20 +1010,20 @@
 	// The Finder is currently implemented to always restat every dir and
 	// to not short-circuit due to nonexistence of parents (but it will remove
 	// missing dirs from the cache for next time)
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/c", "/tmp/c/1", "/tmp/c/1/2"})
 	finder2.Shutdown()
 }
 
 func TestDirectoriesSwapped(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/1/findme.txt", filesystem)
-	create(t, "/tmp/a/1/2/findme.txt", filesystem)
-	create(t, "/tmp/b/findme.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/1/2/findme.txt", filesystem)
+	fs.Create(t, "/tmp/b/findme.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1173,7 +1037,7 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/a/findme.txt",
 			"/tmp/a/1/findme.txt",
@@ -1182,9 +1046,9 @@
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	move(t, "/tmp/a", "/tmp/temp", filesystem)
-	move(t, "/tmp/b", "/tmp/a", filesystem)
-	move(t, "/tmp/temp", "/tmp/b", filesystem)
+	fs.Move(t, "/tmp/a", "/tmp/temp", filesystem)
+	fs.Move(t, "/tmp/b", "/tmp/a", filesystem)
+	fs.Move(t, "/tmp/temp", "/tmp/b", filesystem)
 	filesystem.ClearMetrics()
 
 	// run the second finder
@@ -1192,7 +1056,7 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt",
 			"/tmp/a/findme.txt",
 			"/tmp/b/findme.txt",
@@ -1206,9 +1070,9 @@
 	// The Finder is currently implemented to always restat every dir and
 	// to not short-circuit due to nonexistence of parents (but it will remove
 	// missing dirs from the cache for next time)
-	assertSameStatCalls(t, filesystem.StatCalls,
+	fs.AssertSameStatCalls(t, filesystem.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/1", "/tmp/a/1/2", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp", "/tmp/a", "/tmp/b", "/tmp/b/1", "/tmp/b/1/2"})
 	finder2.Shutdown()
 }
 
@@ -1217,15 +1081,15 @@
 // runFsReplacementTest is a helper method called by other tests
 func runFsReplacementTest(t *testing.T, fs1 *fs.MockFs, fs2 *fs.MockFs) {
 	// setup fs1
-	create(t, "/tmp/findme.txt", fs1)
-	create(t, "/tmp/a/findme.txt", fs1)
-	create(t, "/tmp/a/a/findme.txt", fs1)
+	fs.Create(t, "/tmp/findme.txt", fs1)
+	fs.Create(t, "/tmp/a/findme.txt", fs1)
+	fs.Create(t, "/tmp/a/a/findme.txt", fs1)
 
 	// setup fs2 to have the same directories but different files
-	create(t, "/tmp/findme.txt", fs2)
-	create(t, "/tmp/a/findme.txt", fs2)
-	create(t, "/tmp/a/a/ignoreme.txt", fs2)
-	create(t, "/tmp/a/b/findme.txt", fs2)
+	fs.Create(t, "/tmp/findme.txt", fs2)
+	fs.Create(t, "/tmp/a/findme.txt", fs2)
+	fs.Create(t, "/tmp/a/a/ignoreme.txt", fs2)
+	fs.Create(t, "/tmp/a/b/findme.txt", fs2)
 
 	// run the first finder
 	finder := newFinder(
@@ -1239,12 +1103,12 @@
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
 	finder.Shutdown()
 	// check the response of the first finder
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/a/findme.txt"})
 
 	// copy the cache data from the first filesystem to the second
-	cacheContent := read(t, finder.DbPath, fs1)
-	write(t, finder.DbPath, cacheContent, fs2)
+	cacheContent := fs.Read(t, finder.DbPath, fs1)
+	fs.Write(t, finder.DbPath, cacheContent, fs2)
 
 	// run the second finder, with the same config and same cache contents but a different filesystem
 	finder2 := newFinder(
@@ -1258,11 +1122,11 @@
 	foundPaths = finder2.FindNamedAt("/tmp", "findme.txt")
 
 	// check results
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/findme.txt", "/tmp/a/findme.txt", "/tmp/a/b/findme.txt"})
-	assertSameStatCalls(t, fs2.StatCalls,
+	fs.AssertSameStatCalls(t, fs2.StatCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
-	assertSameReadDirCalls(t, fs2.ReadDirCalls,
+	fs.AssertSameReadDirCalls(t, fs2.ReadDirCalls,
 		[]string{"/tmp", "/tmp/a", "/tmp/a/a", "/tmp/a/b"})
 	finder2.Shutdown()
 }
@@ -1292,7 +1156,7 @@
 	// setup filesystem
 	filesystem := newFs()
 	for i := 0; i < 5; i++ {
-		create(t, fmt.Sprintf("/tmp/%v/findme.txt", i), filesystem)
+		fs.Create(t, fmt.Sprintf("/tmp/%v/findme.txt", i), filesystem)
 	}
 
 	// run the first finder
@@ -1308,7 +1172,7 @@
 	finder.Shutdown()
 
 	// read db file
-	string1 := read(t, finder.DbPath, filesystem)
+	string1 := fs.Read(t, finder.DbPath, filesystem)
 
 	err := filesystem.Remove(finder.DbPath)
 	if err != nil {
@@ -1320,7 +1184,7 @@
 	finder2.FindNamedAt("/tmp", "findme.txt")
 	finder2.Shutdown()
 
-	string2 := read(t, finder.DbPath, filesystem)
+	string2 := fs.Read(t, finder.DbPath, filesystem)
 
 	if string1 != string2 {
 		t.Errorf("Running Finder twice generated two dbs not having identical contents.\n"+
@@ -1343,9 +1207,9 @@
 func TestNumSyscallsOfSecondFind(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/misc.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/misc.txt", filesystem)
 
 	// set up the finder and run it once
 	finder := newFinder(
@@ -1357,15 +1221,15 @@
 		},
 	)
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
 
 	filesystem.ClearMetrics()
 
 	// run the finder again and confirm it doesn't check the filesystem
 	refoundPaths := finder.FindNamedAt("/tmp", "findme.txt")
-	assertSameResponse(t, refoundPaths, foundPaths)
-	assertSameStatCalls(t, filesystem.StatCalls, []string{})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
+	fs.AssertSameResponse(t, refoundPaths, foundPaths)
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
 
 	finder.Shutdown()
 }
@@ -1373,9 +1237,9 @@
 func TestChangingParamsOfSecondFind(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/findme.txt", filesystem)
-	create(t, "/tmp/a/findme.txt", filesystem)
-	create(t, "/tmp/a/metoo.txt", filesystem)
+	fs.Create(t, "/tmp/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/findme.txt", filesystem)
+	fs.Create(t, "/tmp/a/metoo.txt", filesystem)
 
 	// set up the finder and run it once
 	finder := newFinder(
@@ -1387,15 +1251,15 @@
 		},
 	)
 	foundPaths := finder.FindNamedAt("/tmp", "findme.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/findme.txt", "/tmp/a/findme.txt"})
 
 	filesystem.ClearMetrics()
 
 	// run the finder again and confirm it gets the right answer without asking the filesystem
 	refoundPaths := finder.FindNamedAt("/tmp", "metoo.txt")
-	assertSameResponse(t, refoundPaths, []string{"/tmp/a/metoo.txt"})
-	assertSameStatCalls(t, filesystem.StatCalls, []string{})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
+	fs.AssertSameResponse(t, refoundPaths, []string{"/tmp/a/metoo.txt"})
+	fs.AssertSameStatCalls(t, filesystem.StatCalls, []string{})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
 
 	finder.Shutdown()
 }
@@ -1403,15 +1267,15 @@
 func TestSymlinkPointingToFile(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/a/hi.txt", filesystem)
-	create(t, "/tmp/a/ignoreme.txt", filesystem)
-	link(t, "/tmp/hi.txt", "a/hi.txt", filesystem)
-	link(t, "/tmp/b/hi.txt", "../a/hi.txt", filesystem)
-	link(t, "/tmp/c/hi.txt", "/tmp/hi.txt", filesystem)
-	link(t, "/tmp/d/hi.txt", "../a/bye.txt", filesystem)
-	link(t, "/tmp/d/bye.txt", "../a/hi.txt", filesystem)
-	link(t, "/tmp/e/bye.txt", "../a/bye.txt", filesystem)
-	link(t, "/tmp/f/hi.txt", "somethingThatDoesntExist", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/ignoreme.txt", filesystem)
+	fs.Link(t, "/tmp/hi.txt", "a/hi.txt", filesystem)
+	fs.Link(t, "/tmp/b/hi.txt", "../a/hi.txt", filesystem)
+	fs.Link(t, "/tmp/c/hi.txt", "/tmp/hi.txt", filesystem)
+	fs.Link(t, "/tmp/d/hi.txt", "../a/bye.txt", filesystem)
+	fs.Link(t, "/tmp/d/bye.txt", "../a/hi.txt", filesystem)
+	fs.Link(t, "/tmp/e/bye.txt", "../a/bye.txt", filesystem)
+	fs.Link(t, "/tmp/f/hi.txt", "somethingThatDoesntExist", filesystem)
 
 	// set up the finder and run it once
 	finder := newFinder(
@@ -1432,20 +1296,21 @@
 		"/tmp/d/hi.txt",
 		"/tmp/f/hi.txt",
 	}
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 
 }
 
 func TestSymlinkPointingToDirectory(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/dir/hi.txt", filesystem)
-	create(t, "/tmp/dir/ignoreme.txt", filesystem)
+	fs.Create(t, "/tmp/dir/hi.txt", filesystem)
+	fs.Create(t, "/tmp/dir/ignoreme.txt", filesystem)
 
-	link(t, "/tmp/links/dir", "../dir", filesystem)
-	link(t, "/tmp/links/link", "../dir", filesystem)
-	link(t, "/tmp/links/broken", "nothingHere", filesystem)
-	link(t, "/tmp/links/recursive", "recursive", filesystem)
+	fs.Link(t, "/tmp/links/dir", "../dir", filesystem)
+	fs.Link(t, "/tmp/links/link", "../dir", filesystem)
+	fs.Link(t, "/tmp/links/hi.txt", "../dir", filesystem)
+	fs.Link(t, "/tmp/links/broken", "nothingHere", filesystem)
+	fs.Link(t, "/tmp/links/recursive", "recursive", filesystem)
 
 	// set up the finder and run it once
 	finder := newFinder(
@@ -1463,7 +1328,7 @@
 	correctResponse := []string{
 		"/tmp/dir/hi.txt",
 	}
-	assertSameResponse(t, foundPaths, correctResponse)
+	fs.AssertSameResponse(t, foundPaths, correctResponse)
 
 }
 
@@ -1472,9 +1337,9 @@
 func TestAddPruneFile(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/out/hi.txt", filesystem)
-	create(t, "/tmp/out/a/hi.txt", filesystem)
-	create(t, "/tmp/hi.txt", filesystem)
+	fs.Create(t, "/tmp/out/hi.txt", filesystem)
+	fs.Create(t, "/tmp/out/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/hi.txt", filesystem)
 
 	// do find
 	finder := newFinder(
@@ -1490,7 +1355,7 @@
 	foundPaths := finder.FindNamedAt("/tmp", "hi.txt")
 
 	// check result
-	assertSameResponse(t, foundPaths,
+	fs.AssertSameResponse(t, foundPaths,
 		[]string{"/tmp/hi.txt",
 			"/tmp/out/hi.txt",
 			"/tmp/out/a/hi.txt"},
@@ -1499,19 +1364,19 @@
 
 	// modify filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/out/.ignore-out-dir", filesystem)
+	fs.Create(t, "/tmp/out/.ignore-out-dir", filesystem)
 	// run another find and check its result
 	finder2 := finderWithSameParams(t, finder)
 	foundPaths = finder2.FindNamedAt("/tmp", "hi.txt")
-	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
 	finder2.Shutdown()
 }
 
 func TestUpdatingDbIffChanged(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/a/hi.txt", filesystem)
-	create(t, "/tmp/b/bye.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/bye.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1526,11 +1391,11 @@
 	foundPaths := finder.FindAll()
 	finder.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
-	create(t, "/tmp/b/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/hi.txt", filesystem)
 	filesystem.Clock.Tick()
 	filesystem.ClearMetrics()
 
@@ -1539,10 +1404,10 @@
 	foundPaths = finder2.FindAll()
 	finder2.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{"/tmp/b"})
 	expectedDbWriteTime := filesystem.Clock.Time()
-	actualDbWriteTime := modTime(t, finder2.DbPath, filesystem)
+	actualDbWriteTime := fs.ModTime(t, finder2.DbPath, filesystem)
 	if actualDbWriteTime != expectedDbWriteTime {
 		t.Fatalf("Expected to write db at %v, actually wrote db at %v\n",
 			expectedDbWriteTime, actualDbWriteTime)
@@ -1556,10 +1421,10 @@
 	foundPaths = finder3.FindAll()
 
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
-	assertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt", "/tmp/b/hi.txt"})
+	fs.AssertSameReadDirCalls(t, filesystem.ReadDirCalls, []string{})
 	finder3.Shutdown()
-	actualDbWriteTime = modTime(t, finder3.DbPath, filesystem)
+	actualDbWriteTime = fs.ModTime(t, finder3.DbPath, filesystem)
 	if actualDbWriteTime != expectedDbWriteTime {
 		t.Fatalf("Re-wrote db even when contents did not change")
 	}
@@ -1569,10 +1434,10 @@
 func TestDirectoryNotPermitted(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/hi.txt", filesystem)
-	create(t, "/tmp/a/hi.txt", filesystem)
-	create(t, "/tmp/a/a/hi.txt", filesystem)
-	create(t, "/tmp/b/hi.txt", filesystem)
+	fs.Create(t, "/tmp/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/b/hi.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1588,12 +1453,12 @@
 	finder.Shutdown()
 	allPaths := []string{"/tmp/hi.txt", "/tmp/a/hi.txt", "/tmp/a/a/hi.txt", "/tmp/b/hi.txt"}
 	// check results
-	assertSameResponse(t, foundPaths, allPaths)
+	fs.AssertSameResponse(t, foundPaths, allPaths)
 
 	// modify the filesystem
 	filesystem.Clock.Tick()
 
-	setReadable(t, "/tmp/a", false, filesystem)
+	fs.SetReadable(t, "/tmp/a", false, filesystem)
 	filesystem.Clock.Tick()
 
 	// run the second finder
@@ -1601,24 +1466,24 @@
 	foundPaths = finder2.FindAll()
 	finder2.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/b/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt", "/tmp/b/hi.txt"})
 
 	// modify the filesystem back
-	setReadable(t, "/tmp/a", true, filesystem)
+	fs.SetReadable(t, "/tmp/a", true, filesystem)
 
 	// run the third finder
 	finder3 := finderWithSameParams(t, finder2)
 	foundPaths = finder3.FindAll()
 	finder3.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, allPaths)
+	fs.AssertSameResponse(t, foundPaths, allPaths)
 }
 
 func TestFileNotPermitted(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/hi.txt", filesystem)
-	setReadable(t, "/tmp/hi.txt", false, filesystem)
+	fs.Create(t, "/tmp/hi.txt", filesystem)
+	fs.SetReadable(t, "/tmp/hi.txt", false, filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1633,13 +1498,13 @@
 	foundPaths := finder.FindAll()
 	finder.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/hi.txt"})
 }
 
 func TestCacheEntryPathUnexpectedError(t *testing.T) {
 	// setup filesystem
 	filesystem := newFs()
-	create(t, "/tmp/a/hi.txt", filesystem)
+	fs.Create(t, "/tmp/a/hi.txt", filesystem)
 
 	// run the first finder
 	finder := newFinder(
@@ -1654,14 +1519,14 @@
 	foundPaths := finder.FindAll()
 	finder.Shutdown()
 	// check results
-	assertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
+	fs.AssertSameResponse(t, foundPaths, []string{"/tmp/a/hi.txt"})
 
 	// make the directory not readable
-	setReadErr(t, "/tmp/a", os.ErrInvalid, filesystem)
+	fs.SetReadErr(t, "/tmp/a", os.ErrInvalid, filesystem)
 
 	// run the second finder
 	_, err := finderAndErrorWithSameParams(t, finder)
 	if err == nil {
-		fatal(t, "Failed to detect unexpected filesystem error")
+		t.Fatal("Failed to detect unexpected filesystem error")
 	}
 }
diff --git a/finder/fs/Android.bp b/finder/fs/Android.bp
index cf996ab..85929ae 100644
--- a/finder/fs/Android.bp
+++ b/finder/fs/Android.bp
@@ -22,8 +22,10 @@
     srcs: [
         "fs.go",
         "readdir.go",
+        "test.go",
     ],
     testSrcs: [
+        "fs_test.go",
         "readdir_test.go",
     ],
     darwin: {
diff --git a/finder/fs/fs.go b/finder/fs/fs.go
index 071f764..d2e3e4a 100644
--- a/finder/fs/fs.go
+++ b/finder/fs/fs.go
@@ -51,6 +51,7 @@
 	// getting information about files
 	Open(name string) (file io.ReadCloser, err error)
 	Lstat(path string) (stats os.FileInfo, err error)
+	Stat(path string) (stats os.FileInfo, err error)
 	ReadDir(path string) (contents []DirEntryInfo, err error)
 
 	InodeNumber(info os.FileInfo) (number uint64, err error)
@@ -99,6 +100,10 @@
 	return os.Lstat(path)
 }
 
+func (osFs) Stat(path string) (stats os.FileInfo, err error) {
+	return os.Stat(path)
+}
+
 func (osFs) ReadDir(path string) (contents []DirEntryInfo, err error) {
 	entries, err := readdir(path)
 	if err != nil {
@@ -376,7 +381,7 @@
 	size         int64
 	modTime      time.Time // time at which the inode's contents were modified
 	permTime     time.Time // time at which the inode's permissions were modified
-	isDir        bool
+	mode         os.FileMode
 	inodeNumber  uint64
 	deviceNumber uint64
 }
@@ -390,7 +395,7 @@
 }
 
 func (m *mockFileInfo) Mode() os.FileMode {
-	return 0
+	return m.mode
 }
 
 func (m *mockFileInfo) ModTime() time.Time {
@@ -398,7 +403,7 @@
 }
 
 func (m *mockFileInfo) IsDir() bool {
-	return m.isDir
+	return m.mode&os.ModeDir != 0
 }
 
 func (m *mockFileInfo) Sys() interface{} {
@@ -407,11 +412,11 @@
 
 func (m *MockFs) dirToFileInfo(d *mockDir, path string) (info *mockFileInfo) {
 	return &mockFileInfo{
-		path:         path,
+		path:         filepath.Base(path),
 		size:         1,
 		modTime:      d.modTime,
 		permTime:     d.permTime,
-		isDir:        true,
+		mode:         os.ModeDir,
 		inodeNumber:  d.inodeNumber,
 		deviceNumber: m.deviceNumber,
 	}
@@ -420,11 +425,11 @@
 
 func (m *MockFs) fileToFileInfo(f *mockFile, path string) (info *mockFileInfo) {
 	return &mockFileInfo{
-		path:         path,
+		path:         filepath.Base(path),
 		size:         1,
 		modTime:      f.modTime,
 		permTime:     f.permTime,
-		isDir:        false,
+		mode:         0,
 		inodeNumber:  f.inodeNumber,
 		deviceNumber: m.deviceNumber,
 	}
@@ -432,11 +437,11 @@
 
 func (m *MockFs) linkToFileInfo(l *mockLink, path string) (info *mockFileInfo) {
 	return &mockFileInfo{
-		path:         path,
+		path:         filepath.Base(path),
 		size:         1,
 		modTime:      l.modTime,
 		permTime:     l.permTime,
-		isDir:        false,
+		mode:         os.ModeSymlink,
 		inodeNumber:  l.inodeNumber,
 		deviceNumber: m.deviceNumber,
 	}
@@ -485,6 +490,16 @@
 	}
 }
 
+func (m *MockFs) Stat(path string) (stats os.FileInfo, err error) {
+	// resolve symlinks
+	path, err = m.resolve(path, true)
+	if err != nil {
+		return nil, err
+	}
+
+	return m.Lstat(path)
+}
+
 func (m *MockFs) InodeNumber(info os.FileInfo) (number uint64, err error) {
 	mockInfo, ok := info.(*mockFileInfo)
 	if ok {
diff --git a/finder/fs/fs_test.go b/finder/fs/fs_test.go
new file mode 100644
index 0000000..22a4d7a
--- /dev/null
+++ b/finder/fs/fs_test.go
@@ -0,0 +1,76 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+	"os"
+	"testing"
+)
+
+func TestMockFs_LstatStatSymlinks(t *testing.T) {
+	// setup filesystem
+	filesystem := NewMockFs(nil)
+	Create(t, "/tmp/realdir/hi.txt", filesystem)
+	Create(t, "/tmp/realdir/ignoreme.txt", filesystem)
+
+	Link(t, "/tmp/links/dir", "../realdir", filesystem)
+	Link(t, "/tmp/links/file", "../realdir/hi.txt", filesystem)
+	Link(t, "/tmp/links/broken", "nothingHere", filesystem)
+	Link(t, "/tmp/links/recursive", "recursive", filesystem)
+
+	assertStat := func(t *testing.T, stat os.FileInfo, err error, wantName string, wantMode os.FileMode) {
+		t.Helper()
+		if err != nil {
+			t.Error(err)
+			return
+		}
+		if g, w := stat.Name(), wantName; g != w {
+			t.Errorf("want name %q, got %q", w, g)
+		}
+		if g, w := stat.Mode(), wantMode; g != w {
+			t.Errorf("%s: want mode %q, got %q", wantName, w, g)
+		}
+	}
+
+	assertErr := func(t *testing.T, err error, wantErr string) {
+		if err == nil || err.Error() != wantErr {
+			t.Errorf("want error %q, got %q", wantErr, err)
+		}
+	}
+
+	stat, err := filesystem.Lstat("/tmp/links/dir")
+	assertStat(t, stat, err, "dir", os.ModeSymlink)
+
+	stat, err = filesystem.Stat("/tmp/links/dir")
+	assertStat(t, stat, err, "realdir", os.ModeDir)
+
+	stat, err = filesystem.Lstat("/tmp/links/file")
+	assertStat(t, stat, err, "file", os.ModeSymlink)
+
+	stat, err = filesystem.Stat("/tmp/links/file")
+	assertStat(t, stat, err, "hi.txt", 0)
+
+	stat, err = filesystem.Lstat("/tmp/links/broken")
+	assertStat(t, stat, err, "broken", os.ModeSymlink)
+
+	stat, err = filesystem.Stat("/tmp/links/broken")
+	assertErr(t, err, "stat /tmp/links/nothingHere: file does not exist")
+
+	stat, err = filesystem.Lstat("/tmp/links/recursive")
+	assertStat(t, stat, err, "recursive", os.ModeSymlink)
+
+	stat, err = filesystem.Stat("/tmp/links/recursive")
+	assertErr(t, err, "read /tmp/links/recursive: too many levels of symbolic links")
+}
diff --git a/finder/fs/test.go b/finder/fs/test.go
new file mode 100644
index 0000000..cb2140e
--- /dev/null
+++ b/finder/fs/test.go
@@ -0,0 +1,146 @@
+// Copyright 2020 Google Inc. All rights reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package fs
+
+import (
+	"io/ioutil"
+	"path/filepath"
+	"reflect"
+	"sort"
+	"testing"
+	"time"
+)
+
+func Write(t *testing.T, path string, content string, filesystem *MockFs) {
+	parent := filepath.Dir(path)
+	filesystem.MkDirs(parent)
+	err := filesystem.WriteFile(path, []byte(content), 0777)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func Create(t *testing.T, path string, filesystem *MockFs) {
+	Write(t, path, "hi", filesystem)
+}
+
+func Delete(t *testing.T, path string, filesystem *MockFs) {
+	err := filesystem.Remove(path)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func RemoveAll(t *testing.T, path string, filesystem *MockFs) {
+	err := filesystem.RemoveAll(path)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func Move(t *testing.T, oldPath string, newPath string, filesystem *MockFs) {
+	err := filesystem.Rename(oldPath, newPath)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func Link(t *testing.T, newPath string, oldPath string, filesystem *MockFs) {
+	parentPath := filepath.Dir(newPath)
+	err := filesystem.MkDirs(parentPath)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	err = filesystem.Symlink(oldPath, newPath)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func Read(t *testing.T, path string, filesystem *MockFs) string {
+	reader, err := filesystem.Open(path)
+	if err != nil {
+		t.Fatalf(err.Error())
+	}
+	bytes, err := ioutil.ReadAll(reader)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	return string(bytes)
+}
+
+func ModTime(t *testing.T, path string, filesystem *MockFs) time.Time {
+	stats, err := filesystem.Lstat(path)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+	return stats.ModTime()
+}
+
+func SetReadable(t *testing.T, path string, readable bool, filesystem *MockFs) {
+	err := filesystem.SetReadable(path, readable)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func SetReadErr(t *testing.T, path string, readErr error, filesystem *MockFs) {
+	err := filesystem.SetReadErr(path, readErr)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+}
+
+func AssertSameResponse(t *testing.T, actual []string, expected []string) {
+	t.Helper()
+	sort.Strings(actual)
+	sort.Strings(expected)
+	if !reflect.DeepEqual(actual, expected) {
+		t.Fatalf("Expected Finder to return these %v paths:\n  %v,\ninstead returned these %v paths:  %v\n",
+			len(expected), expected, len(actual), actual)
+	}
+}
+
+func AssertSameStatCalls(t *testing.T, actual []string, expected []string) {
+	t.Helper()
+	sort.Strings(actual)
+	sort.Strings(expected)
+
+	if !reflect.DeepEqual(actual, expected) {
+		t.Fatalf("Finder made incorrect Stat calls.\n"+
+			"Actual:\n"+
+			"%v\n"+
+			"Expected:\n"+
+			"%v\n"+
+			"\n",
+			actual, expected)
+	}
+}
+
+func AssertSameReadDirCalls(t *testing.T, actual []string, expected []string) {
+	t.Helper()
+	sort.Strings(actual)
+	sort.Strings(expected)
+
+	if !reflect.DeepEqual(actual, expected) {
+		t.Fatalf("Finder made incorrect ReadDir calls.\n"+
+			"Actual:\n"+
+			"%v\n"+
+			"Expected:\n"+
+			"%v\n"+
+			"\n",
+			actual, expected)
+	}
+}
diff --git a/genrule/genrule.go b/genrule/genrule.go
index 8b80871..00baa57 100644
--- a/genrule/genrule.go
+++ b/genrule/genrule.go
@@ -83,7 +83,6 @@
 
 type generatorProperties struct {
 	// The command to run on one or more input files. Cmd supports substitution of a few variables
-	// (the actual substitution is implemented in GenerateAndroidBuildActions below)
 	//
 	// Available variables for substitution:
 	//
@@ -94,9 +93,6 @@
 	//  $(depfile): a file to which dependencies will be written, if the depfile property is set to true
 	//  $(genDir): the sandbox directory for this tool; contains $(out)
 	//  $$: a literal $
-	//
-	// All files used must be declared as inputs (to ensure proper up-to-date checks).
-	// Use "$(in)" directly in Cmd to ensure that all inputs used are declared.
 	Cmd *string
 
 	// Enable reading a file containing dependencies in gcc format after the command completes
diff --git a/java/config/config.go b/java/config/config.go
index ffe219a..0fe74c8 100644
--- a/java/config/config.go
+++ b/java/config/config.go
@@ -28,12 +28,11 @@
 var (
 	pctx = android.NewPackageContext("android/soong/java/config")
 
-	// TODO(b/157640067): Don't depend on the legacy API by default in the long term.
-	DefaultBootclasspathLibraries = []string{"legacy.core.platform.api.stubs", "core-lambda-stubs"}
-	DefaultSystemModules          = "legacy-core-platform-api-stubs-system-modules"
-	DefaultLibraries              = []string{"ext", "framework"}
-	DefaultLambdaStubsLibrary     = "core-lambda-stubs"
-	SdkLambdaStubsPath            = "prebuilts/sdk/tools/core-lambda-stubs.jar"
+	LegacyCorePlatformBootclasspathLibraries = []string{"legacy.core.platform.api.stubs", "core-lambda-stubs"}
+	LegacyCorePlatformSystemModules          = "legacy-core-platform-api-stubs-system-modules"
+	FrameworkLibraries                       = []string{"ext", "framework"}
+	DefaultLambdaStubsLibrary                = "core-lambda-stubs"
+	SdkLambdaStubsPath                       = "prebuilts/sdk/tools/core-lambda-stubs.jar"
 
 	DefaultMakeJacocoExcludeFilter = []string{"org.junit.*", "org.jacoco.*", "org.mockito.*"}
 	DefaultJacocoExcludeFilter     = []string{"org.junit.**", "org.jacoco.**", "org.mockito.**"}
diff --git a/java/config/makevars.go b/java/config/makevars.go
index b355fad..708a72a 100644
--- a/java/config/makevars.go
+++ b/java/config/makevars.go
@@ -25,9 +25,11 @@
 }
 
 func makeVarsProvider(ctx android.MakeVarsContext) {
-	ctx.Strict("TARGET_DEFAULT_JAVA_LIBRARIES", strings.Join(DefaultLibraries, " "))
-	ctx.Strict("TARGET_DEFAULT_BOOTCLASSPATH_LIBRARIES", strings.Join(DefaultBootclasspathLibraries, " "))
-	ctx.Strict("DEFAULT_SYSTEM_MODULES", DefaultSystemModules)
+	ctx.Strict("FRAMEWORK_LIBRARIES", strings.Join(FrameworkLibraries, " "))
+
+	// These are used by make when LOCAL_PRIVATE_PLATFORM_APIS is set (equivalent to platform_apis in blueprint):
+	ctx.Strict("LEGACY_CORE_PLATFORM_BOOTCLASSPATH_LIBRARIES", strings.Join(LegacyCorePlatformBootclasspathLibraries, " "))
+	ctx.Strict("LEGACY_CORE_PLATFORM_SYSTEM_MODULES", LegacyCorePlatformSystemModules)
 
 	ctx.Strict("ANDROID_JAVA_HOME", "${JavaHome}")
 	ctx.Strict("ANDROID_JAVA8_HOME", "prebuilts/jdk/jdk8/${hostPrebuiltTag}")
diff --git a/java/droiddoc.go b/java/droiddoc.go
index 73b897a..f0b2e63 100644
--- a/java/droiddoc.go
+++ b/java/droiddoc.go
@@ -441,10 +441,10 @@
 	if ctx.Device() {
 		sdkDep := decodeSdkDep(ctx, sdkContext(j))
 		if sdkDep.useDefaultLibs {
-			ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...)
-			ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules)
+			ctx.AddVariationDependencies(nil, bootClasspathTag, config.LegacyCorePlatformBootclasspathLibraries...)
+			ctx.AddVariationDependencies(nil, systemModulesTag, config.LegacyCorePlatformSystemModules)
 			if sdkDep.hasFrameworkLibs() {
-				ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...)
+				ctx.AddVariationDependencies(nil, libTag, config.FrameworkLibraries...)
 			}
 		} else if sdkDep.useModule {
 			ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
diff --git a/java/java.go b/java/java.go
index aa843ee..be60cca 100644
--- a/java/java.go
+++ b/java/java.go
@@ -701,17 +701,17 @@
 
 		sdkDep := decodeSdkDep(ctx, sdkContext(j))
 		if sdkDep.useDefaultLibs {
-			ctx.AddVariationDependencies(nil, bootClasspathTag, config.DefaultBootclasspathLibraries...)
-			ctx.AddVariationDependencies(nil, systemModulesTag, config.DefaultSystemModules)
+			ctx.AddVariationDependencies(nil, bootClasspathTag, config.LegacyCorePlatformBootclasspathLibraries...)
+			ctx.AddVariationDependencies(nil, systemModulesTag, config.LegacyCorePlatformSystemModules)
 			if sdkDep.hasFrameworkLibs() {
-				ctx.AddVariationDependencies(nil, libTag, config.DefaultLibraries...)
+				ctx.AddVariationDependencies(nil, libTag, config.FrameworkLibraries...)
 			}
 		} else if sdkDep.useModule {
 			ctx.AddVariationDependencies(nil, bootClasspathTag, sdkDep.bootclasspath...)
 			ctx.AddVariationDependencies(nil, java9LibTag, sdkDep.java9Classpath...)
 			if j.deviceProperties.EffectiveOptimizeEnabled() && sdkDep.hasStandardLibs() {
-				ctx.AddVariationDependencies(nil, proguardRaiseTag, config.DefaultBootclasspathLibraries...)
-				ctx.AddVariationDependencies(nil, proguardRaiseTag, config.DefaultLibraries...)
+				ctx.AddVariationDependencies(nil, proguardRaiseTag, config.LegacyCorePlatformBootclasspathLibraries...)
+				ctx.AddVariationDependencies(nil, proguardRaiseTag, config.FrameworkLibraries...)
 			}
 		}
 		if sdkDep.systemModules != "" {
diff --git a/java/sdk_test.go b/java/sdk_test.go
index e5d322c..773a70c 100644
--- a/java/sdk_test.go
+++ b/java/sdk_test.go
@@ -49,27 +49,27 @@
 	}{
 		{
 			name:           "default",
-			bootclasspath:  config.DefaultBootclasspathLibraries,
-			system:         config.DefaultSystemModules,
-			java8classpath: config.DefaultLibraries,
-			java9classpath: config.DefaultLibraries,
+			bootclasspath:  config.LegacyCorePlatformBootclasspathLibraries,
+			system:         config.LegacyCorePlatformSystemModules,
+			java8classpath: config.FrameworkLibraries,
+			java9classpath: config.FrameworkLibraries,
 			aidl:           "-Iframework/aidl",
 		},
 		{
 			name:           `sdk_version:"core_platform"`,
 			properties:     `sdk_version:"core_platform"`,
-			bootclasspath:  config.DefaultBootclasspathLibraries,
-			system:         config.DefaultSystemModules,
+			bootclasspath:  config.LegacyCorePlatformBootclasspathLibraries,
+			system:         config.LegacyCorePlatformSystemModules,
 			java8classpath: []string{},
 			aidl:           "",
 		},
 		{
 			name:           "blank sdk version",
 			properties:     `sdk_version: "",`,
-			bootclasspath:  config.DefaultBootclasspathLibraries,
-			system:         config.DefaultSystemModules,
-			java8classpath: config.DefaultLibraries,
-			java9classpath: config.DefaultLibraries,
+			bootclasspath:  config.LegacyCorePlatformBootclasspathLibraries,
+			system:         config.LegacyCorePlatformSystemModules,
+			java8classpath: config.FrameworkLibraries,
+			java9classpath: config.FrameworkLibraries,
 			aidl:           "-Iframework/aidl",
 		},
 		{
diff --git a/java/testing.go b/java/testing.go
index 6fc10da..f5688e6 100644
--- a/java/testing.go
+++ b/java/testing.go
@@ -197,6 +197,7 @@
 	systemModules := []string{
 		"core-current-stubs-system-modules",
 		"legacy-core-platform-api-stubs-system-modules",
+		"stable-core-platform-api-stubs-system-modules",
 	}
 
 	for _, extra := range systemModules {
diff --git a/rust/rust.go b/rust/rust.go
index 03efb75..6c60348 100644
--- a/rust/rust.go
+++ b/rust/rust.go
@@ -886,6 +886,18 @@
 	return name
 }
 
+var _ android.HostToolProvider = (*Module)(nil)
+
+func (mod *Module) HostToolPath() android.OptionalPath {
+	if !mod.Host() {
+		return android.OptionalPath{}
+	}
+	if _, ok := mod.compiler.(*binaryDecorator); ok {
+		return mod.outputFile
+	}
+	return android.OptionalPath{}
+}
+
 var Bool = proptools.Bool
 var BoolDefault = proptools.BoolDefault
 var String = proptools.String
diff --git a/scripts/build-mainline-modules.sh b/scripts/build-mainline-modules.sh
index 0520d31..1bc78e6 100755
--- a/scripts/build-mainline-modules.sh
+++ b/scripts/build-mainline-modules.sh
@@ -24,6 +24,7 @@
   i18n-module-test-exports
   i18n-module-sdk
   platform-mainline-sdk
+  platform-mainline-host-exports
 )
 
 # List of libraries installed on the platform that are needed for ART chroot