Allow primary builder to change working directory

Bug: 146437378
Test: pathtools/fs_test.go
Change-Id: I513ceb9b8b0b4f18223bc34ecad9846fe220709b
diff --git a/pathtools/fs.go b/pathtools/fs.go
index 8329392..b45c4a5 100644
--- a/pathtools/fs.go
+++ b/pathtools/fs.go
@@ -36,7 +36,7 @@
 	DontFollowSymlinks = ShouldFollowSymlinks(false)
 )
 
-var OsFs FileSystem = osFs{}
+var OsFs FileSystem = &osFs{}
 
 func MockFs(files map[string][]byte) FileSystem {
 	fs := &mockFs{
@@ -123,11 +123,45 @@
 }
 
 // osFs implements FileSystem using the local disk.
-type osFs struct{}
+type osFs struct {
+	srcDir string
+}
 
-func (osFs) Open(name string) (ReaderAtSeekerCloser, error) { return os.Open(name) }
-func (osFs) Exists(name string) (bool, bool, error) {
-	stat, err := os.Stat(name)
+func NewOsFs(path string) FileSystem {
+	return &osFs{srcDir: path}
+}
+
+func (fs *osFs) removeSrcDirPrefix(path string) string {
+	if fs.srcDir == "" {
+		return path
+	}
+	rel, err := filepath.Rel(fs.srcDir, path)
+	if err != nil {
+		panic(fmt.Errorf("unexpected failure in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
+			fs.srcDir, path, err))
+	}
+	if strings.HasPrefix(rel, "../") {
+		panic(fmt.Errorf("unexpected relative path outside directory in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
+			fs.srcDir, path, rel))
+	}
+	return rel
+}
+
+func (fs *osFs) removeSrcDirPrefixes(paths []string) []string {
+	if fs.srcDir != "" {
+		for i, path := range paths {
+			paths[i] = fs.removeSrcDirPrefix(path)
+		}
+	}
+	return paths
+}
+
+func (fs *osFs) Open(name string) (ReaderAtSeekerCloser, error) {
+	return os.Open(filepath.Join(fs.srcDir, name))
+}
+
+func (fs *osFs) Exists(name string) (bool, bool, error) {
+	stat, err := os.Stat(filepath.Join(fs.srcDir, name))
 	if err == nil {
 		return true, stat.IsDir(), nil
 	} else if os.IsNotExist(err) {
@@ -137,45 +171,47 @@
 	}
 }
 
-func (osFs) IsDir(name string) (bool, error) {
-	info, err := os.Stat(name)
+func (fs *osFs) IsDir(name string) (bool, error) {
+	info, err := os.Stat(filepath.Join(fs.srcDir, name))
 	if err != nil {
 		return false, err
 	}
 	return info.IsDir(), nil
 }
 
-func (osFs) IsSymlink(name string) (bool, error) {
-	if info, err := os.Lstat(name); err != nil {
+func (fs *osFs) IsSymlink(name string) (bool, error) {
+	if info, err := os.Lstat(filepath.Join(fs.srcDir, name)); err != nil {
 		return false, err
 	} else {
 		return info.Mode()&os.ModeSymlink != 0, nil
 	}
 }
 
-func (fs osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
+func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
 	return startGlob(fs, pattern, excludes, follow)
 }
 
-func (osFs) glob(pattern string) ([]string, error) {
-	return filepath.Glob(pattern)
+func (fs *osFs) glob(pattern string) ([]string, error) {
+	paths, err := filepath.Glob(filepath.Join(fs.srcDir, pattern))
+	fs.removeSrcDirPrefixes(paths)
+	return paths, err
 }
 
-func (osFs) Lstat(path string) (stats os.FileInfo, err error) {
-	return os.Lstat(path)
+func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) {
+	return os.Lstat(filepath.Join(fs.srcDir, path))
 }
 
-func (osFs) Stat(path string) (stats os.FileInfo, err error) {
-	return os.Stat(path)
+func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) {
+	return os.Stat(filepath.Join(fs.srcDir, path))
 }
 
 // Returns a list of all directories under dir
-func (osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
-	return listDirsRecursive(OsFs, name, follow)
+func (fs *osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
+	return listDirsRecursive(fs, name, follow)
 }
 
-func (osFs) ReadDirNames(name string) ([]string, error) {
-	dir, err := os.Open(name)
+func (fs *osFs) ReadDirNames(name string) ([]string, error) {
+	dir, err := os.Open(filepath.Join(fs.srcDir, name))
 	if err != nil {
 		return nil, err
 	}
@@ -190,8 +226,8 @@
 	return contents, nil
 }
 
-func (osFs) Readlink(name string) (string, error) {
-	return os.Readlink(name)
+func (fs *osFs) Readlink(name string) (string, error) {
+	return os.Readlink(filepath.Join(fs.srcDir, name))
 }
 
 type mockFs struct {
diff --git a/pathtools/fs_test.go b/pathtools/fs_test.go
index 1b5c458..a9a7060 100644
--- a/pathtools/fs_test.go
+++ b/pathtools/fs_test.go
@@ -22,6 +22,8 @@
 	"testing"
 )
 
+const testdataDir = "testdata/dangling"
+
 func symlinkMockFs() *mockFs {
 	files := []string{
 		"a/a/a",
@@ -149,25 +151,37 @@
 	}
 
 	mock := symlinkMockFs()
-	fsList := []FileSystem{mock, OsFs}
-	names := []string{"mock", "os"}
 
-	os.Chdir("testdata/dangling")
-	defer os.Chdir("../..")
-
-	for i, fs := range fsList {
-		t.Run(names[i], func(t *testing.T) {
-			for _, test := range testCases {
-				t.Run(test.name, func(t *testing.T) {
-					got, err := fs.IsDir(test.name)
-					checkErr(t, test.err, err)
-					if got != test.isDir {
-						t.Errorf("want: %v, got %v", test.isDir, got)
-					}
-				})
-			}
-		})
+	run := func(t *testing.T, fs FileSystem) {
+		for _, test := range testCases {
+			t.Run(test.name, func(t *testing.T) {
+				got, err := fs.IsDir(test.name)
+				checkErr(t, test.err, err)
+				if got != test.isDir {
+					t.Errorf("want: %v, got %v", test.isDir, got)
+				}
+			})
+		}
 	}
+
+	t.Run("mock", func(t *testing.T) {
+		run(t, mock)
+	})
+
+	t.Run("os", func(t *testing.T) {
+		os.Chdir("testdata/dangling")
+		defer os.Chdir("../..")
+		run(t, OsFs)
+	})
+
+	t.Run("os relative srcDir", func(t *testing.T) {
+		run(t, NewOsFs(testdataDir))
+	})
+
+	t.Run("os absolute srcDir", func(t *testing.T) {
+		wd, _ := os.Getwd()
+		run(t, NewOsFs(filepath.Join(wd, testdataDir)))
+	})
 }
 
 func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) {
@@ -200,26 +214,37 @@
 	}
 
 	mock := symlinkMockFs()
-	fsList := []FileSystem{mock, OsFs}
-	names := []string{"mock", "os"}
 
-	os.Chdir("testdata/dangling")
-	defer os.Chdir("../..")
-
-	for i, fs := range fsList {
-		t.Run(names[i], func(t *testing.T) {
-
-			for _, test := range testCases {
-				t.Run(test.name, func(t *testing.T) {
-					got, err := fs.ListDirsRecursive(test.name, FollowSymlinks)
-					checkErr(t, test.err, err)
-					if !reflect.DeepEqual(got, test.dirs) {
-						t.Errorf("want: %v, got %v", test.dirs, got)
-					}
-				})
-			}
-		})
+	run := func(t *testing.T, fs FileSystem) {
+		for _, test := range testCases {
+			t.Run(test.name, func(t *testing.T) {
+				got, err := fs.ListDirsRecursive(test.name, FollowSymlinks)
+				checkErr(t, test.err, err)
+				if !reflect.DeepEqual(got, test.dirs) {
+					t.Errorf("want: %v, got %v", test.dirs, got)
+				}
+			})
+		}
 	}
+
+	t.Run("mock", func(t *testing.T) {
+		run(t, mock)
+	})
+
+	t.Run("os", func(t *testing.T) {
+		os.Chdir("testdata/dangling")
+		defer os.Chdir("../..")
+		run(t, OsFs)
+	})
+
+	t.Run("os relative srcDir", func(t *testing.T) {
+		run(t, NewOsFs(testdataDir))
+	})
+
+	t.Run("os absolute srcDir", func(t *testing.T) {
+		wd, _ := os.Getwd()
+		run(t, NewOsFs(filepath.Join(wd, testdataDir)))
+	})
 }
 
 func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) {
@@ -252,26 +277,37 @@
 	}
 
 	mock := symlinkMockFs()
-	fsList := []FileSystem{mock, OsFs}
-	names := []string{"mock", "os"}
 
-	os.Chdir("testdata/dangling")
-	defer os.Chdir("../..")
-
-	for i, fs := range fsList {
-		t.Run(names[i], func(t *testing.T) {
-
-			for _, test := range testCases {
-				t.Run(test.name, func(t *testing.T) {
-					got, err := fs.ListDirsRecursive(test.name, DontFollowSymlinks)
-					checkErr(t, test.err, err)
-					if !reflect.DeepEqual(got, test.dirs) {
-						t.Errorf("want: %v, got %v", test.dirs, got)
-					}
-				})
-			}
-		})
+	run := func(t *testing.T, fs FileSystem) {
+		for _, test := range testCases {
+			t.Run(test.name, func(t *testing.T) {
+				got, err := fs.ListDirsRecursive(test.name, DontFollowSymlinks)
+				checkErr(t, test.err, err)
+				if !reflect.DeepEqual(got, test.dirs) {
+					t.Errorf("want: %v, got %v", test.dirs, got)
+				}
+			})
+		}
 	}
+
+	t.Run("mock", func(t *testing.T) {
+		run(t, mock)
+	})
+
+	t.Run("os", func(t *testing.T) {
+		os.Chdir("testdata/dangling")
+		defer os.Chdir("../..")
+		run(t, OsFs)
+	})
+
+	t.Run("os relative srcDir", func(t *testing.T) {
+		run(t, NewOsFs(testdataDir))
+	})
+
+	t.Run("os absolute srcDir", func(t *testing.T) {
+		wd, _ := os.Getwd()
+		run(t, NewOsFs(filepath.Join(wd, testdataDir)))
+	})
 }
 
 func TestFs_Readlink(t *testing.T) {
@@ -321,26 +357,37 @@
 	}
 
 	mock := symlinkMockFs()
-	fsList := []FileSystem{mock, OsFs}
-	names := []string{"mock", "os"}
 
-	os.Chdir("testdata/dangling")
-	defer os.Chdir("../..")
-
-	for i, fs := range fsList {
-		t.Run(names[i], func(t *testing.T) {
-
-			for _, test := range testCases {
-				t.Run(test.from, func(t *testing.T) {
-					got, err := fs.Readlink(test.from)
-					checkErr(t, test.err, err)
-					if got != test.to {
-						t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got)
-					}
-				})
-			}
-		})
+	run := func(t *testing.T, fs FileSystem) {
+		for _, test := range testCases {
+			t.Run(test.from, func(t *testing.T) {
+				got, err := fs.Readlink(test.from)
+				checkErr(t, test.err, err)
+				if got != test.to {
+					t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got)
+				}
+			})
+		}
 	}
+
+	t.Run("mock", func(t *testing.T) {
+		run(t, mock)
+	})
+
+	t.Run("os", func(t *testing.T) {
+		os.Chdir("testdata/dangling")
+		defer os.Chdir("../..")
+		run(t, OsFs)
+	})
+
+	t.Run("os relative srcDir", func(t *testing.T) {
+		run(t, NewOsFs(testdataDir))
+	})
+
+	t.Run("os absolute srcDir", func(t *testing.T) {
+		wd, _ := os.Getwd()
+		run(t, NewOsFs(filepath.Join(wd, testdataDir)))
+	})
 }
 
 func TestFs_Lstat(t *testing.T) {
@@ -392,33 +439,44 @@
 	}
 
 	mock := symlinkMockFs()
-	fsList := []FileSystem{mock, OsFs}
-	names := []string{"mock", "os"}
 
-	os.Chdir("testdata/dangling")
-	defer os.Chdir("../..")
-
-	for i, fs := range fsList {
-		t.Run(names[i], func(t *testing.T) {
-
-			for _, test := range testCases {
-				t.Run(test.name, func(t *testing.T) {
-					got, err := fs.Lstat(test.name)
-					checkErr(t, test.err, err)
-					if err != nil {
-						return
-					}
-					if got.Mode()&os.ModeType != test.mode {
-						t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x",
-							test.name, test.mode, got.Mode()&os.ModeType)
-					}
-					if test.mode == 0 && got.Size() != test.size {
-						t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
-					}
-				})
-			}
-		})
+	run := func(t *testing.T, fs FileSystem) {
+		for _, test := range testCases {
+			t.Run(test.name, func(t *testing.T) {
+				got, err := fs.Lstat(test.name)
+				checkErr(t, test.err, err)
+				if err != nil {
+					return
+				}
+				if got.Mode()&os.ModeType != test.mode {
+					t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x",
+						test.name, test.mode, got.Mode()&os.ModeType)
+				}
+				if test.mode == 0 && got.Size() != test.size {
+					t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
+				}
+			})
+		}
 	}
+
+	t.Run("mock", func(t *testing.T) {
+		run(t, mock)
+	})
+
+	t.Run("os", func(t *testing.T) {
+		os.Chdir("testdata/dangling")
+		defer os.Chdir("../..")
+		run(t, OsFs)
+	})
+
+	t.Run("os relative srcDir", func(t *testing.T) {
+		run(t, NewOsFs(testdataDir))
+	})
+
+	t.Run("os absolute srcDir", func(t *testing.T) {
+		wd, _ := os.Getwd()
+		run(t, NewOsFs(filepath.Join(wd, testdataDir)))
+	})
 }
 
 func TestFs_Stat(t *testing.T) {
@@ -470,33 +528,44 @@
 	}
 
 	mock := symlinkMockFs()
-	fsList := []FileSystem{mock, OsFs}
-	names := []string{"mock", "os"}
 
-	os.Chdir("testdata/dangling")
-	defer os.Chdir("../..")
-
-	for i, fs := range fsList {
-		t.Run(names[i], func(t *testing.T) {
-
-			for _, test := range testCases {
-				t.Run(test.name, func(t *testing.T) {
-					got, err := fs.Stat(test.name)
-					checkErr(t, test.err, err)
-					if err != nil {
-						return
-					}
-					if got.Mode()&os.ModeType != test.mode {
-						t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x",
-							test.name, test.mode, got.Mode()&os.ModeType)
-					}
-					if test.mode == 0 && got.Size() != test.size {
-						t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
-					}
-				})
-			}
-		})
+	run := func(t *testing.T, fs FileSystem) {
+		for _, test := range testCases {
+			t.Run(test.name, func(t *testing.T) {
+				got, err := fs.Stat(test.name)
+				checkErr(t, test.err, err)
+				if err != nil {
+					return
+				}
+				if got.Mode()&os.ModeType != test.mode {
+					t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x",
+						test.name, test.mode, got.Mode()&os.ModeType)
+				}
+				if test.mode == 0 && got.Size() != test.size {
+					t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
+				}
+			})
+		}
 	}
+
+	t.Run("mock", func(t *testing.T) {
+		run(t, mock)
+	})
+
+	t.Run("os", func(t *testing.T) {
+		os.Chdir("testdata/dangling")
+		defer os.Chdir("../..")
+		run(t, OsFs)
+	})
+
+	t.Run("os relative srcDir", func(t *testing.T) {
+		run(t, NewOsFs(testdataDir))
+	})
+
+	t.Run("os absolute srcDir", func(t *testing.T) {
+		wd, _ := os.Getwd()
+		run(t, NewOsFs(filepath.Join(wd, testdataDir)))
+	})
 }
 
 func TestMockFs_glob(t *testing.T) {
@@ -538,27 +607,39 @@
 	}
 
 	mock := symlinkMockFs()
-	fsList := []FileSystem{mock, OsFs}
-	names := []string{"mock", "os"}
 
-	os.Chdir("testdata/dangling")
-	defer os.Chdir("../..")
-
-	for i, fs := range fsList {
-		t.Run(names[i], func(t *testing.T) {
-			for _, test := range testCases {
-				t.Run(test.pattern, func(t *testing.T) {
-					got, err := fs.glob(test.pattern)
-					if err != nil {
-						t.Fatal(err)
-					}
-					if !reflect.DeepEqual(got, test.files) {
-						t.Errorf("want: %v, got %v", test.files, got)
-					}
-				})
-			}
-		})
+	run := func(t *testing.T, fs FileSystem) {
+		for _, test := range testCases {
+			t.Run(test.pattern, func(t *testing.T) {
+				got, err := fs.glob(test.pattern)
+				if err != nil {
+					t.Fatal(err)
+				}
+				if !reflect.DeepEqual(got, test.files) {
+					t.Errorf("want: %v, got %v", test.files, got)
+				}
+			})
+		}
 	}
+
+	t.Run("mock", func(t *testing.T) {
+		run(t, mock)
+	})
+
+	t.Run("os", func(t *testing.T) {
+		os.Chdir("testdata/dangling")
+		defer os.Chdir("../..")
+		run(t, OsFs)
+	})
+
+	t.Run("os relative srcDir", func(t *testing.T) {
+		run(t, NewOsFs(testdataDir))
+	})
+
+	t.Run("os absolute srcDir", func(t *testing.T) {
+		wd, _ := os.Getwd()
+		run(t, NewOsFs(filepath.Join(wd, testdataDir)))
+	})
 }
 
 func syscallError(err error) error {