[testsharder] Introduce top-level Dimensions

Have an Environment only specify Swarming dimensions in a single
Dimensions struct.

Bug: IN-497
Change-Id: I8a6c91a1d457d7c676e3c46c65026b9ae4ea5d8e
diff --git a/fuchsia/testexec/environment.go b/fuchsia/testexec/environment.go
index acb3af9..0ae28a3 100644
--- a/fuchsia/testexec/environment.go
+++ b/fuchsia/testexec/environment.go
@@ -2,96 +2,55 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// We say that an Environment (or FooSpec) "resolves to" another if the
-// properties or constraints given by the former are also given by the latter.
-// This gives a partial ordering on environments expressing a subobject
-// relationship.
-//
-// The utility of being able to express that one environment is a
-// subenvironment of another lies in validation: an environment is valid if
-// it resolves to any member of a golden set of environments we refer to as
-// "platforms", as they correspond to the currently available test platforms
-// supported by the infrastructure.
-//
 // The GN environments specified by test authors in the Fuchsia source
 // correspond directly to the Environment struct defined here.
+//
+// Note that by "platforms" we mean a specific group of dimension sets which
+// correspond to the currently available test platforms supported by the
+// infrastructure.
 package testexec
 
 import (
 	"encoding/json"
 	"io/ioutil"
-	"strings"
 )
 
-// NameBuilder is a helper struct used to give names to environments. It
-// effectively concatenates all given tags with a dash, having replaced each
-// space with an underscore.
-type nameBuilder []string
-
-func (b *nameBuilder) addTags(tags ...string) {
-	*b = append(*b, tags...)
-}
-
-func (b nameBuilder) getName() string {
-	return strings.Replace(strings.Join(b, "-"), " ", "_", -1)
-}
-
 // Environment describes the full environment a test requires.
 type Environment struct {
-	// Device represents properties of the device that are part of the test
-	// environment.
-	Device DeviceSpec `json:"device"`
+	// Dimensions gives the Swarming dimensions a test wishes to target.
+	Dimensions DimensionSet `json:"dimensions"`
 }
 
-// Name returns a name calculated from its specfied properties. It is used to
-// give a human-readable identifier to a shard.
-// Names of different shards might coincide.
+// Name returns a name calculated from its specfied properties.
+// In the first iteration, this just returns the device type.
 func (env Environment) Name() string {
-	var b nameBuilder
-	b.addTags(env.Device.name())
-	return b.getName()
+	return env.Dimensions.DeviceType
 }
 
-// ResolvesTo gives a partial ordering on environments in which one environment
-// resolves to another if the properties given by the former are also given by
-// the latter.
-func (env Environment) resolvesTo(other Environment) bool {
-	if !env.Device.resolvesTo(other.Device) {
-		return false
-	}
-	return true
-}
-
-// DeviceSpec describes the device environment a test requires.
-type DeviceSpec struct {
-	// Type represents the class of device the test should run on.
+// DimensionSet encapsulate the Swarming dimensions a test wishes to target.
+type DimensionSet struct {
+	// DeviceType represents the class of device the test should run on.
 	// This is a required field.
-	Type string `json:"type"`
+	DeviceType string `json:"device_type"`
 }
 
-func (ds DeviceSpec) name() string {
-	var b nameBuilder
-	if ds.Type != "" {
-		b.addTags(ds.Type)
-	}
-	return b.getName()
-}
-
-func (ds DeviceSpec) resolvesTo(other DeviceSpec) bool {
-	if ds.Type != "" && ds.Type != other.Type {
+// ResolvesTo gives a partial ordering on DimensionSets in which one resolves to
+// another if the former's dimensions are given the latter.
+func (dims DimensionSet) resolvesTo(other DimensionSet) bool {
+	if dims.DeviceType != "" && dims.DeviceType != other.DeviceType {
 		return false
 	}
 	return true
 }
 
-// LoadPlatforms loads the list of test platform environments specified as a
+// LoadPlatforms loads the list of test platform dimension sets specified as a
 // JSON list at a given filepath.
-func LoadPlatforms(platformManifestPath string) ([]Environment, error) {
+func LoadPlatforms(platformManifestPath string) ([]DimensionSet, error) {
 	bytes, err := ioutil.ReadFile(platformManifestPath)
 	if err != nil {
 		return nil, err
 	}
-	var platforms []Environment
+	var platforms []DimensionSet
 	if err = json.Unmarshal(bytes, &platforms); err != nil {
 		return nil, err
 	}
diff --git a/fuchsia/testexec/shard_test.go b/fuchsia/testexec/shard_test.go
index 8575cb2..46d319f 100644
--- a/fuchsia/testexec/shard_test.go
+++ b/fuchsia/testexec/shard_test.go
@@ -14,10 +14,10 @@
 	test1 := testexec.Test{Location: "/path/to/binary"}
 	test2 := testexec.Test{Location: "/path/to/binary2"}
 	env1 := testexec.Environment{
-		Device: testexec.DeviceSpec{Type: "QEMU"},
+		Dimensions: testexec.DimensionSet{DeviceType: "QEMU"},
 	}
 	env2 := testexec.Environment{
-		Device: testexec.DeviceSpec{Type: "NUC"},
+		Dimensions: testexec.DimensionSet{DeviceType: "NUC"},
 	}
 	spec1 := testexec.TestSpec{
 		Test: test1,
diff --git a/fuchsia/testexec/test_spec.go b/fuchsia/testexec/test_spec.go
index 94e6b48..159683d 100644
--- a/fuchsia/testexec/test_spec.go
+++ b/fuchsia/testexec/test_spec.go
@@ -13,28 +13,10 @@
 	"fuchsia.googlesource.com/infra/infra/fuchsia"
 )
 
-// These are the default environments to be used if an author specifies none.
-//
-// TODO(IN-497) We include the NUC here as a tranistional policy. We will give
-// people a week from flag day to begin explicitly specifying their
-// environments, after which we will set the default as only QEMU.
-var defaultEnvs = []Environment{
-	Environment{
-		Device: DeviceSpec{
-			Type: "QEMU",
-		},
-	},
-	Environment{
-		Device: DeviceSpec{
-			Type: "Intel NUC Kit NUC7i5DNHE",
-		},
-	},
-}
-
 // TestSpec is the specification for a single test and the environments it
 // should be executed in.
 type TestSpec struct {
-	// Test is the test that this specuration is for.
+	// Test is the test that this specification is for.
 	Test `json:"test"`
 
 	// Envs is a set of environments that the test should be executed in.
@@ -52,7 +34,7 @@
 	Location string `json:"location"`
 }
 
-func (spec TestSpec) validateAgainst(platforms []Environment) error {
+func (spec TestSpec) validateAgainst(platforms []DimensionSet) error {
 	if spec.Test.Name == "" {
 		return fmt.Errorf("A test spec's test must have a non-empty name")
 	}
@@ -60,9 +42,9 @@
 		return fmt.Errorf("A test spec's test must have a non-empty location")
 	}
 
-	resolvesToOneOf := func(env Environment, platforms []Environment) bool {
+	resolvesToOneOf := func(env Environment, platforms []DimensionSet) bool {
 		for _, platform := range platforms {
-			if env.resolvesTo(platform) {
+			if env.Dimensions.resolvesTo(platform) {
 				return true
 			}
 		}
@@ -85,8 +67,8 @@
 }
 
 // ValidateTestSpecs validates a list of test specs against a list of test
-// platform environments.
-func ValidateTestSpecs(specs []TestSpec, platforms []Environment) error {
+// platform dimension sets.
+func ValidateTestSpecs(specs []TestSpec, platforms []DimensionSet) error {
 	errMsg := ""
 	for _, spec := range specs {
 		if err := spec.validateAgainst(platforms); err != nil {
@@ -159,9 +141,6 @@
 			if err := json.NewDecoder(f).Decode(&spec); err != nil {
 				return nil, fmt.Errorf("failed to decode %s: %s", path, err.Error())
 			}
-			if len(spec.Envs) == 0 {
-				spec.Envs = defaultEnvs
-			}
 			specs = append(specs, spec)
 		}
 	}
diff --git a/fuchsia/testexec/test_spec_test.go b/fuchsia/testexec/test_spec_test.go
index 6a4288a..46862ad 100644
--- a/fuchsia/testexec/test_spec_test.go
+++ b/fuchsia/testexec/test_spec_test.go
@@ -17,16 +17,20 @@
 	"fuchsia.googlesource.com/infra/infra/fuchsia/testexec"
 )
 
+var qemuPlatform = testexec.DimensionSet{
+	DeviceType: "QEMU",
+}
+
+var nucPlatform = testexec.DimensionSet{
+	DeviceType: "NUC",
+}
+
 var qemuEnv = testexec.Environment{
-	Device: testexec.DeviceSpec{
-		Type: "QEMU",
-	},
+	Dimensions: qemuPlatform,
 }
 
 var nucEnv = testexec.Environment{
-	Device: testexec.DeviceSpec{
-		Type: "NUC",
-	},
+	Dimensions: nucPlatform,
 }
 
 var specFoo1 = testexec.TestSpec{
@@ -229,13 +233,13 @@
 		},
 		Envs: []testexec.Environment{
 			testexec.Environment{
-				Device: testexec.DeviceSpec{
-					Type: "NON-EXISTENT-DEVICE",
+				Dimensions: testexec.DimensionSet{
+					DeviceType: "NON-EXISTENT-DEVICE",
 				},
 			},
 		},
 	}
-	platforms := []testexec.Environment{qemuEnv, nucEnv}
+	platforms := []testexec.DimensionSet{qemuPlatform, nucPlatform}
 
 	t.Run("valid specs are validated", func(t *testing.T) {
 		validSpecLists := [][]testexec.TestSpec{
diff --git a/testsharder b/testsharder
new file mode 100755
index 0000000..0addd55
--- /dev/null
+++ b/testsharder
Binary files differ