[testsharder] Greatly simplify test spec loading

As of https://fuchsia-review.googlesource.com/c/fuchsia/+/249798, all
test spec information may be found in a top-level tests.json; we no
longer need to walk the build directory, consulting packages.json and
host_tests.json.

Test: ran locally successfully

Change-Id: Ia8957557c1f7fb664ae38ee726628a11321aaa62
diff --git a/build/build.go b/build/build.go
index 9d17e3d..bb19532 100644
--- a/build/build.go
+++ b/build/build.go
@@ -6,72 +6,11 @@
 
 package build
 
-import (
-	"encoding/json"
-	"os"
-	"path/filepath"
-)
-
 const (
-	// PackageManifestName is the name of the manifest of fuchsia package
-	// targets included in a build.
-	packageManifestName = "packages.json"
-
-	// HostTestManifestName is the name of the manifest of host-side test
-	// targets included in a build.
-	hostTestManifestName = "host_tests.json"
+	// TestSpecManifestName is the name of the manifest of test specs produced by the build.
+	TestSpecManifestName = "tests.json"
 
 	// PlatformManifestName is the name of the manifest of available test
 	// platforms.
 	PlatformManifestName = "platforms.json"
 )
-
-// Target provides information about a GN target.
-type Target struct {
-	// BuildDir is a relative path in the Fuchsia build directory to the location
-	// of the target's generated file directory.
-	BuildDir string `json:"build_dir"`
-
-	// Dir is the source-relative directory of the target (e.g.,
-	// //garnet/dir/of/build-dot-gn-file).
-	Dir string `json:"dir"`
-
-	// Name is the name of the target.
-	Name string `json:"name"`
-}
-
-type pkgManifest struct {
-	Pkgs []Target `json:"packages"`
-}
-
-// LoadPackages loads a list of packages targets from JSON package manifest
-// produced by the build, given the root of the build directory.
-func LoadPackages(fuchsiaBuildDir string) ([]Target, error) {
-	manifestPath := filepath.Join(fuchsiaBuildDir, packageManifestName)
-	manifestFile, err := os.Open(manifestPath)
-	if err != nil {
-		return nil, err
-	}
-	defer manifestFile.Close()
-	var pkgManifest pkgManifest
-	err = json.NewDecoder(manifestFile).Decode(&pkgManifest)
-	return pkgManifest.Pkgs, err
-}
-
-// LoadHostTests loads a list of host test targets from a JSON host test
-// manifest produced by the build, given the root of the build directory.
-func LoadHostTests(fuchsiaBuildDir string) ([]Target, error) {
-	manifestPath := filepath.Join(fuchsiaBuildDir, hostTestManifestName)
-	manifestFile, err := os.Open(manifestPath)
-	// TODO(IN-823): As of writing this, the host test manifest does not exist. Until it
-	// exists make non-existence not a point of failure.
-	if os.IsNotExist(err) {
-		return []Target{}, nil
-	} else if err != nil {
-		return nil, err
-	}
-	defer manifestFile.Close()
-	var hostTests []Target
-	err = json.NewDecoder(manifestFile).Decode(&hostTests)
-	return hostTests, err
-}
diff --git a/cmd/testsharder/main.go b/cmd/testsharder/main.go
index c0c5f1b..62fb2fa 100644
--- a/cmd/testsharder/main.go
+++ b/cmd/testsharder/main.go
@@ -9,7 +9,6 @@
 	"log"
 	"os"
 
-	"fuchsia.googlesource.com/tools/build"
 	"fuchsia.googlesource.com/tools/testsharder"
 )
 
@@ -37,21 +36,10 @@
 		log.Fatal("must specify a Fuchsia build output directory")
 	}
 
-	// Load spec files.
-	pkgs, err := build.LoadPackages(buildDir)
+	specs, err := testsharder.LoadTestSpecs(buildDir)
 	if err != nil {
 		log.Fatal(err)
 	}
-	hostTests, err := build.LoadHostTests(buildDir)
-	if err != nil {
-		log.Fatal(err)
-	}
-	specs, err := testsharder.LoadTestSpecs(buildDir, pkgs, hostTests)
-	if err != nil {
-		log.Fatal(err)
-	}
-
-	// Load test platform environments.
 	platforms, err := testsharder.LoadPlatforms(buildDir)
 	if err != nil {
 		log.Fatal(err)
diff --git a/testsharder/test_spec.go b/testsharder/test_spec.go
index d420f9f..69538ca 100644
--- a/testsharder/test_spec.go
+++ b/testsharder/test_spec.go
@@ -4,27 +4,15 @@
 package testsharder
 
 import (
-	"bufio"
 	"encoding/json"
 	"fmt"
-	"io"
 	"io/ioutil"
 	"os"
 	"path/filepath"
-	"strings"
 
 	"fuchsia.googlesource.com/tools/build"
 )
 
-const (
-	// TestSpecSuffix is the file suffix identifying a test spec.
-	TestSpecSuffix = ".spec.json"
-
-	// TestDepsSuffix is the file suffix identifying a file giving the runtime
-	// depedencies of a test.
-	TestDepsSuffix = ".spec.data"
-)
-
 // OS is an operating system that a test may run in.
 type OS string
 
@@ -63,9 +51,13 @@
 	// Command is the command line to run to execute this test.
 	Command []string `json:"command,omitempty"`
 
-	// Deps is the list of paths to the test's runtime dependencies,
-	// relative to the build directory.
+	// DepsFile is a relative path within the build directory to a file containing a JSON
+	// list of the test's runtime dependencies,
 	// Currently this field only makes sense for Linux and Mac tests.
+	DepsFile string `json:"deps_file,omitempty"`
+
+	// Deps is the list of paths to the test's runtime dependencies within the build
+	// directory. It is read out of DepFile.
 	Deps []string `json:"deps,omitempty"`
 }
 
@@ -119,116 +111,31 @@
 	return nil
 }
 
-// PkgTestSpecDir returns the directory where associated test specs may be written,
-// given a package target.
-func PkgTestSpecDir(fuchsiaBuildDir string, pkg build.Target) string {
-	return filepath.Join(fuchsiaBuildDir, pkg.BuildDir, pkg.Name)
-}
-
-// HostTestSpecDir returns the directory where associated test specs may be written,
-// given a host test target.
-func HostTestSpecDir(fuchsiaBuildDir string, hostTest build.Target) string {
-	return filepath.Join(fuchsiaBuildDir, hostTest.BuildDir)
-}
-
-func readLinesFromFile(path string) ([]string, error) {
-	fd, err := os.Open(path)
-	defer fd.Close()
+// LoadTestSpecs loads a set of test specifications from a build.
+func LoadTestSpecs(fuchsiaBuildDir string) ([]TestSpec, error) {
+	manifestPath := filepath.Join(fuchsiaBuildDir, build.TestSpecManifestName)
+	bytes, err := ioutil.ReadFile(manifestPath)
 	if err != nil {
-		return nil, fmt.Errorf("failed to open %s: %v", path, err)
-	}
-
-	reader := bufio.NewReader(fd)
-	lines := []string{}
-	for {
-		line, err := reader.ReadString('\n')
-		if err == io.EOF {
-			break
-		} else if err != nil {
-			return nil, fmt.Errorf("failed to read line from %s: %v", path, err)
-		}
-		line = strings.TrimRight(line, "\n")
-		lines = append(lines, line)
-	}
-	return lines, nil
-}
-
-// LoadTestSpecs loads a set of test specifications from a list of Fuchsia
-// package targets and a list of host test targets.
-func LoadTestSpecs(fuchsiaBuildDir string, pkgs, hostTests []build.Target) ([]TestSpec, error) {
-	// First, load the test specs associated to the given packages.
-	//
-	// It is guaranteed that a test spec will be written to the build directory of the
-	// corresponding package its test was defined in: specifically, it will be put in
-	// <target_out_dir of the test package>/<test package name>.
-	specs := []TestSpec{}
-	processedDirs := make(map[string]bool)
-
-	decodeTestSpecs := func(targets []build.Target, testSpecDir func(string, build.Target) string) error {
-		for _, target := range targets {
-			specDir := testSpecDir(fuchsiaBuildDir, target)
-			if _, ok := processedDirs[specDir]; ok {
-				continue
-			}
-			processedDirs[specDir] = true
-
-			// If the associated test spec directory does not exist, the package specified no
-			// tests.
-			if _, err := os.Stat(specDir); os.IsNotExist(err) {
-				continue
-			}
-
-			// Non-recursively enumerate the files in this directory; it's guaranteed that
-			// the test specs will be found here if generated.
-			entries, err := ioutil.ReadDir(specDir)
-			if err != nil {
-				return err
-			}
-
-			for _, entry := range entries {
-				if entry.IsDir() {
-					continue
-				}
-
-				// Open, read, and parse any test spec found. Look for any associated
-				// runtime depedencies.
-				path := filepath.Join(specDir, entry.Name())
-				if strings.HasSuffix(path, TestSpecSuffix) {
-					specFile, err := os.Open(path)
-					defer specFile.Close()
-					if err != nil {
-						return fmt.Errorf("failed to open %s: %v", path, err)
-					}
-
-					var spec TestSpec
-					if err := json.NewDecoder(specFile).Decode(&spec); err != nil {
-						return fmt.Errorf("failed to decode %s: %v", path, err)
-					}
-
-					testDepsPath := strings.Replace(path, TestSpecSuffix, TestDepsSuffix, 1)
-					_, err = os.Stat(testDepsPath)
-					if err == nil {
-						deps, err := readLinesFromFile(testDepsPath)
-						if err != nil {
-							return err
-						}
-						spec.Test.Deps = deps
-					} else if !os.IsNotExist(err) {
-						return err
-					}
-					specs = append(specs, spec)
-				}
-			}
-		}
-		return nil
-	}
-
-	if err := decodeTestSpecs(pkgs, PkgTestSpecDir); err != nil {
 		return nil, err
 	}
-	if err := decodeTestSpecs(hostTests, HostTestSpecDir); err != nil {
+	var specs []TestSpec
+	if err = json.Unmarshal(bytes, &specs); err != nil {
 		return nil, err
 	}
 
+	for i, _ := range specs {
+		if specs[i].DepsFile == "" {
+			continue
+		}
+		path := filepath.Join(fuchsiaBuildDir, specs[i].DepsFile)
+		f, err := os.Open(path)
+		if err != nil {
+			return nil, err
+		}
+		if err = json.NewDecoder(f).Decode(&specs[i].Deps); err != nil {
+			return nil, err
+		}
+		specs[i].DepsFile = "" // No longer needed.
+	}
 	return specs, nil
 }
diff --git a/testsharder/test_spec_test.go b/testsharder/test_spec_test.go
index 92f095b..1312942 100644
--- a/testsharder/test_spec_test.go
+++ b/testsharder/test_spec_test.go
@@ -1,7 +1,7 @@
 // Copyright 2018 The Fuchsia Authors. All rights reserved.
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
-package testsharder_test
+package testsharder
 
 import (
 	"encoding/json"
@@ -14,133 +14,83 @@
 	"testing"
 
 	"fuchsia.googlesource.com/tools/build"
-	"fuchsia.googlesource.com/tools/testsharder"
 )
 
-var qemuPlatform = testsharder.DimensionSet{
+var qemuPlatform = DimensionSet{
 	DeviceType: "QEMU",
 }
 
-var nucPlatform = testsharder.DimensionSet{
+var nucPlatform = DimensionSet{
 	DeviceType: "NUC",
 }
 
-var linuxPlatform = testsharder.DimensionSet{
+var linuxPlatform = DimensionSet{
 	OS: "Linux",
 }
 
-var macPlatform = testsharder.DimensionSet{
+var macPlatform = DimensionSet{
 	OS: "Mac",
 }
 
-var qemuEnv = testsharder.Environment{
+var qemuEnv = Environment{
 	Dimensions: qemuPlatform,
 }
 
-var nucEnv = testsharder.Environment{
+var nucEnv = Environment{
 	Dimensions: nucPlatform,
 }
 
-var linuxEnv = testsharder.Environment{
+var linuxEnv = Environment{
 	Dimensions: linuxPlatform,
 }
 
-var macEnv = testsharder.Environment{
+var macEnv = Environment{
 	Dimensions: macPlatform,
 }
 
-var specFoo1 = testsharder.TestSpec{
-	Test: testsharder.Test{
+var specFoo1 = TestSpec{
+	Test: Test{
 		Name:     "//obsidian/bin/foo:foo_unittests",
 		Location: "/system/test/foo_unittests",
-		OS:       "linux",
+		OS:       Fuchsia,
 		Command:  []string{"/system/test/foo_unittests", "bar", "baz"},
 	},
-	Envs: []testsharder.Environment{qemuEnv},
+	Envs: []Environment{qemuEnv},
 }
 
-var specFoo2 = testsharder.TestSpec{
-	Test: testsharder.Test{
+var specFoo2 = TestSpec{
+	Test: Test{
 		Name:     "//obsidian/bin/foo:foo_integration_tests",
 		Location: "/system/test/foo_integration_tests",
-		OS:       "linux",
+		OS:       Fuchsia,
 	},
-	Envs: []testsharder.Environment{qemuEnv, nucEnv},
+	Envs: []Environment{qemuEnv, nucEnv},
 }
 
-var specBar = testsharder.TestSpec{
-	Test: testsharder.Test{
+var specBar = TestSpec{
+	Test: Test{
 		Name:     "//obsidian/lib/bar:bar_tests",
 		Location: "/system/test/bar_tests",
-		OS:       "linux",
+		OS:       Fuchsia,
 	},
-	Envs: []testsharder.Environment{qemuEnv},
+	Envs: []Environment{qemuEnv},
 }
 
-var specBaz = testsharder.TestSpec{
-	Test: testsharder.Test{
+var specBaz = TestSpec{
+	Test: Test{
 		Name:     "//obsidian/public/lib/baz:baz_host_tests",
 		Location: "/$root_build_dir/baz_host_tests",
-		OS:       "linux",
+		OS:       Linux,
 	},
-	Envs: []testsharder.Environment{linuxEnv, macEnv},
-}
-
-// FuchsiaBuildDir is a struct representing the root build directory of a fuchsia
-// checkout.
-type fuchsiaBuildDir struct {
-	root string
-	t    *testing.T
-}
-
-func newFuchsiaBuildDir(t *testing.T) *fuchsiaBuildDir {
-	root, err := ioutil.TempDir("", "fuchsia-build-dir")
-	if err != nil {
-		t.Fatalf("could not create fuchsia build directory: %v", err)
-	}
-	return &fuchsiaBuildDir{
-		root: root,
-		t:    t,
-	}
-}
-
-// CreateLayout takes a list of package and host test targets, and creates the associated
-// directory layout.
-func (bd fuchsiaBuildDir) createLayout(pkgs []build.Target, hostTests []build.Target) {
-	for _, pkg := range pkgs {
-		specDir := testsharder.PkgTestSpecDir(bd.root, pkg)
-		if err := os.MkdirAll(specDir, os.ModePerm); err != nil {
-			bd.t.Fatalf("could not create test spec directory for package \"%s\": %v", pkg.Name, err)
-		}
-	}
-	for _, hostTest := range hostTests {
-		specDir := testsharder.HostTestSpecDir(bd.root, hostTest)
-		if err := os.MkdirAll(specDir, os.ModePerm); err != nil {
-			bd.t.Fatalf("could not create test spec directory for host test \"%s\": %v", hostTest.Name, err)
-		}
-	}
-}
-
-func writeTestSpec(t *testing.T, spec testsharder.TestSpec, path string) {
-	bytes, err := json.Marshal(&spec)
-	if err != nil {
-		t.Fatal(err)
-	}
-	if err = ioutil.WriteFile(path, bytes, os.ModePerm); err != nil {
-		t.Fatalf("could not write test spec to %s: %v", path, err)
-	}
-}
-
-func testSpecFilename(basename string) string {
-	return basename + testsharder.TestSpecSuffix
+	Envs: []Environment{linuxEnv, macEnv},
 }
 
 func TestLoadTestSpecs(t *testing.T) {
-	areEqual := func(a, b []testsharder.TestSpec) bool {
-		stringify := func(spec testsharder.TestSpec) string {
+	areEqual := func(a, b []TestSpec) bool {
+		stringify := func(spec TestSpec) string {
 			return fmt.Sprintf("%#v", spec)
 		}
-		sort := func(list []testsharder.TestSpec) {
+		sort := func(list []TestSpec) {
 			sort.Slice(list[:], func(i, j int) bool {
 				return stringify(list[i]) < stringify(list[j])
 			})
@@ -150,197 +100,110 @@
 		return reflect.DeepEqual(a, b)
 	}
 
-	pkgFoo := build.Target{
-		BuildDir: "obj/obsidian/bin/foo",
-		Name:     "foo",
+	tmpDir, err := ioutil.TempDir("", "test-spec")
+	if err != nil {
+		t.Fatalf("failed to create temp dir: %v", err)
 	}
-	pkgBar := build.Target{
-		BuildDir: "obj/obsidian/lib/bar",
-		Name:     "bar",
-	}
-	hostTestBaz := build.Target{
-		BuildDir: "host_x64/obj/obsidian/public/lib/baz",
-		Name:     "baz",
-	}
-	pkgs := []build.Target{pkgFoo, pkgBar}
-	hostTests := []build.Target{hostTestBaz}
+	defer os.RemoveAll(tmpDir)
 
-	correctSpecsLoad := func(t *testing.T, expected []testsharder.TestSpec, fuchsiaBuildDir string) {
-		actual, err := testsharder.LoadTestSpecs(fuchsiaBuildDir, pkgs, hostTests)
-		if err != nil {
-			t.Fatalf("error while loading test specs: %v", err)
-		}
-		if !areEqual(expected, actual) {
-			t.Fatalf("test specs not properly loaded:\nexpected:\n%+v\nactual:\n%+v", expected, actual)
-		}
+	deps := []string{"path/to/first/dep", "path/to/second"}
+	depsFilepath := filepath.Join(tmpDir, "deps.json")
+	df, err := os.Create(depsFilepath)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer df.Close()
+	if err := json.NewEncoder(df).Encode(&deps); err != nil {
+		t.Fatalf("failed to create JSON encoder: %v", err)
 	}
 
-	t.Run("test specs are found", func(t *testing.T) {
-		bd := newFuchsiaBuildDir(t)
-		defer os.RemoveAll(bd.root)
-		bd.createLayout(pkgs, hostTests)
+	specBazIn := specBaz
+	specBazIn.DepsFile = "deps.json"
+	initial := []TestSpec{specBar, specBazIn}
 
-		specDirFoo := testsharder.PkgTestSpecDir(bd.root, pkgFoo)
-		specDirBar := testsharder.PkgTestSpecDir(bd.root, pkgBar)
-		specDirBaz := testsharder.HostTestSpecDir(bd.root, hostTestBaz)
-		writeTestSpec(t, specFoo1, filepath.Join(specDirFoo, testSpecFilename("foo_unittest")))
-		writeTestSpec(t, specFoo2, filepath.Join(specDirFoo, testSpecFilename("foo_integration_tests")))
-		writeTestSpec(t, specBar, filepath.Join(specDirBar, testSpecFilename("bar_tests")))
-		writeTestSpec(t, specBaz, filepath.Join(specDirBaz, testSpecFilename("baz_host_tests")))
+	manifest := filepath.Join(tmpDir, build.TestSpecManifestName)
+	m, err := os.Create(manifest)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer m.Close()
+	if err := json.NewEncoder(m).Encode(&initial); err != nil {
+		t.Fatal(err)
+	}
 
-		expected := []testsharder.TestSpec{specFoo1, specFoo2, specBar, specBaz}
-		correctSpecsLoad(t, expected, bd.root)
-	})
+	actual, err := LoadTestSpecs(tmpDir)
+	if err != nil {
+		t.Fatalf("failed to load test specs: %v", err)
+	}
 
-	t.Run("test specs in wrong location are ignored", func(t *testing.T) {
-		bd := newFuchsiaBuildDir(t)
-		defer os.RemoveAll(bd.root)
-		bd.createLayout(pkgs, hostTests)
+	specBazOut := specBaz
+	specBazOut.Deps = deps
+	expected := []TestSpec{specBar, specBazOut}
 
-		specDirFoo := testsharder.PkgTestSpecDir(bd.root, pkgFoo)
-		specDirBar := testsharder.PkgTestSpecDir(bd.root, pkgBar)
-		specDirBaz := testsharder.HostTestSpecDir(bd.root, hostTestBaz)
-		nonSpecDir := filepath.Join(bd.root, "other-package")
-		if err := os.MkdirAll(nonSpecDir, os.ModePerm); err != nil {
-			t.Fatalf("failed to create a directory outside of the package manifest: %v", err)
-		}
-
-		writeTestSpec(t, specFoo1, filepath.Join(specDirFoo, testSpecFilename("foo_unittests")))
-		writeTestSpec(t, specFoo2, filepath.Join(nonSpecDir, testSpecFilename("other_tests")))
-		writeTestSpec(t, specBar, filepath.Join(specDirBar, testSpecFilename("bar_tests")))
-		writeTestSpec(t, specBaz, filepath.Join(specDirBaz, testSpecFilename("baz_host_tests")))
-
-		expected := []testsharder.TestSpec{specFoo1, specBar, specBaz}
-		correctSpecsLoad(t, expected, bd.root)
-	})
-
-	t.Run("test specs with wrong extension are ignored", func(t *testing.T) {
-		bd := newFuchsiaBuildDir(t)
-		defer os.RemoveAll(bd.root)
-		bd.createLayout(pkgs, hostTests)
-
-		specDirFoo := testsharder.PkgTestSpecDir(bd.root, pkgFoo)
-		specDirBar := testsharder.PkgTestSpecDir(bd.root, pkgBar)
-		specDirBaz := testsharder.HostTestSpecDir(bd.root, hostTestBaz)
-		writeTestSpec(t, specFoo1, filepath.Join(specDirFoo, "bad_extension1.json"))
-		writeTestSpec(t, specFoo2, filepath.Join(specDirFoo, testSpecFilename("good extension")))
-		writeTestSpec(t, specBar, filepath.Join(specDirBar, "bad_extension2.spec"))
-		writeTestSpec(t, specBaz, filepath.Join(specDirBaz, testSpecFilename("another_good_extension")))
-
-		expected := []testsharder.TestSpec{specFoo2, specBaz}
-		correctSpecsLoad(t, expected, bd.root)
-	})
-
-	t.Run("malformed test specs raise error", func(t *testing.T) {
-		bd := newFuchsiaBuildDir(t)
-		defer os.RemoveAll(bd.root)
-		bd.createLayout(pkgs, hostTests)
-
-		specDirFoo := testsharder.PkgTestSpecDir(bd.root, pkgFoo)
-		specDirBar := testsharder.PkgTestSpecDir(bd.root, pkgBar)
-
-		writeTestSpec(t, specFoo1, filepath.Join(specDirFoo, testSpecFilename("foo_unittests")))
-		if err := ioutil.WriteFile(filepath.Join(specDirFoo, testSpecFilename("foo_integration_tests")),
-			[]byte("{I am not a test spec}"), os.ModePerm); err != nil {
-			t.Fatalf("could not write malformed test spec: %v", err)
-		}
-		writeTestSpec(t, specBar, filepath.Join(specDirBar, testSpecFilename("bar_tests")))
-
-		_, err := testsharder.LoadTestSpecs(bd.root, pkgs, hostTests)
-		if err == nil {
-			t.Fatalf("malformed test spec did not raise an error")
-		}
-	})
-
-	t.Run("deps are loaded", func(t *testing.T) {
-		bd := newFuchsiaBuildDir(t)
-		defer os.RemoveAll(bd.root)
-		bd.createLayout(pkgs, hostTests)
-
-		specDirBaz := testsharder.HostTestSpecDir(bd.root, hostTestBaz)
-		writeTestSpec(t, specBaz, filepath.Join(specDirBaz, testSpecFilename("baz_host_tests")))
-
-		deps := []string{"path/to/dep/1", "path/to/dep/2"}
-		DepsPath := filepath.Join(specDirBaz, "baz_host_tests"+testsharder.TestDepsSuffix)
-		fd, err := os.Create(DepsPath)
-		defer fd.Close()
-		if err != nil {
-			t.Fatal(err)
-		}
-		for _, dep := range deps {
-			_, err := fd.WriteString(dep + "\n")
-			if err != nil {
-				t.Fatal(err)
-			}
-		}
-		specBazWithDeps := specBaz
-		specBazWithDeps.Test.Deps = deps
-
-		expected := []testsharder.TestSpec{specBazWithDeps}
-		correctSpecsLoad(t, expected, bd.root)
-	})
-
+	if !areEqual(expected, actual) {
+		t.Fatalf("test specs not properly loaded:\nexpected:\n%+v\nactual:\n%+v", expected, actual)
+	}
 }
 
 func TestValidateTestSpecs(t *testing.T) {
-	noTestNameSpec := testsharder.TestSpec{
-		Test: testsharder.Test{
+	noTestNameSpec := TestSpec{
+		Test: Test{
 			Location: "/system/test/baz_tests",
-			OS:       "linux",
+			OS:       Linux,
 		},
-		Envs: []testsharder.Environment{qemuEnv},
+		Envs: []Environment{qemuEnv},
 	}
-	noTestLocationSpec := testsharder.TestSpec{
-		Test: testsharder.Test{
+	noTestLocationSpec := TestSpec{
+		Test: Test{
 			Name: "//obsidian/public/lib/baz:baz_tests",
-			OS:   "linux",
+			OS:   Linux,
 		},
-		Envs: []testsharder.Environment{qemuEnv},
+		Envs: []Environment{qemuEnv},
 	}
-	noOSSpec := testsharder.TestSpec{
-		Test: testsharder.Test{
+	noOSSpec := TestSpec{
+		Test: Test{
 			Name:     "//obsidian/bin/foo:foo_unittests",
 			Location: "/system/test/foo_unittests",
 		},
 	}
-	badEnvSpec := testsharder.TestSpec{
-		Test: testsharder.Test{
+	badEnvSpec := TestSpec{
+		Test: Test{
 			Name:     "//obsidian/public/lib/baz:baz_tests",
 			Location: "/system/test/baz_tests",
-			OS:       "linux",
+			OS:       Linux,
 		},
-		Envs: []testsharder.Environment{
-			testsharder.Environment{
-				Dimensions: testsharder.DimensionSet{
+		Envs: []Environment{
+			Environment{
+				Dimensions: DimensionSet{
 					DeviceType: "NON-EXISTENT-DEVICE",
 				},
 			},
 		},
 	}
-	platforms := []testsharder.DimensionSet{qemuPlatform, nucPlatform}
+	platforms := []DimensionSet{qemuPlatform, nucPlatform}
 
 	t.Run("valid specs are validated", func(t *testing.T) {
-		validSpecLists := [][]testsharder.TestSpec{
+		validSpecLists := [][]TestSpec{
 			{specFoo1}, {specFoo2}, {specBar},
 			{specFoo1, specFoo2}, {specFoo1, specBar}, {specFoo2, specBar},
 			{specFoo1, specFoo2, specBar},
 		}
 		for _, list := range validSpecLists {
-			if err := testsharder.ValidateTestSpecs(list, platforms); err != nil {
+			if err := ValidateTestSpecs(list, platforms); err != nil {
 				t.Fatalf("valid specs marked as invalid: %+v: %v", list, err)
 			}
 		}
 	})
 
 	t.Run("invalid specs are invalidated", func(t *testing.T) {
-		invalidSpecLists := [][]testsharder.TestSpec{
+		invalidSpecLists := [][]TestSpec{
 			{noOSSpec}, {noTestNameSpec}, {noTestLocationSpec}, {badEnvSpec},
 			{noTestNameSpec, noTestLocationSpec}, {noTestNameSpec, badEnvSpec},
 			{noTestLocationSpec, badEnvSpec},
 			{noTestNameSpec, noTestLocationSpec, badEnvSpec},
 		}
 		for _, list := range invalidSpecLists {
-			if err := testsharder.ValidateTestSpecs(list, platforms); err == nil {
+			if err := ValidateTestSpecs(list, platforms); err == nil {
 				t.Fatalf("invalid specs marked as valid: %+v", list)
 			}
 		}