| // Copyright 2019 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 android | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"io" | 
 | 	"reflect" | 
 | 	"runtime" | 
 | 	"strings" | 
 | 	"testing" | 
 |  | 
 | 	"github.com/google/blueprint/proptools" | 
 | ) | 
 |  | 
 | type customModule struct { | 
 | 	ModuleBase | 
 |  | 
 | 	properties struct { | 
 | 		Default_dist_files *string | 
 | 		Dist_output_file   *bool | 
 | 	} | 
 |  | 
 | 	data       AndroidMkData | 
 | 	distFiles  TaggedDistFiles | 
 | 	outputFile OptionalPath | 
 |  | 
 | 	// The paths that will be used as the default dist paths if no tag is | 
 | 	// specified. | 
 | 	defaultDistPaths Paths | 
 | } | 
 |  | 
 | const ( | 
 | 	defaultDistFiles_None    = "none" | 
 | 	defaultDistFiles_Default = "default" | 
 | 	defaultDistFiles_Tagged  = "tagged" | 
 | ) | 
 |  | 
 | func (m *customModule) GenerateAndroidBuildActions(ctx ModuleContext) { | 
 |  | 
 | 	m.base().licenseMetadataFile = PathForOutput(ctx, "meta_lic") | 
 |  | 
 | 	// If the dist_output_file: true then create an output file that is stored in | 
 | 	// the OutputFile property of the AndroidMkEntry. | 
 | 	if proptools.BoolDefault(m.properties.Dist_output_file, true) { | 
 | 		path := PathForTesting("dist-output-file.out") | 
 | 		m.outputFile = OptionalPathForPath(path) | 
 |  | 
 | 		// Previous code would prioritize the DistFiles property over the OutputFile | 
 | 		// property in AndroidMkEntry when determining the default dist paths. | 
 | 		// Setting this first allows it to be overridden based on the | 
 | 		// default_dist_files setting replicating that previous behavior. | 
 | 		m.defaultDistPaths = Paths{path} | 
 | 	} | 
 |  | 
 | 	// Based on the setting of the default_dist_files property possibly create a | 
 | 	// TaggedDistFiles structure that will be stored in the DistFiles property of | 
 | 	// the AndroidMkEntry. | 
 | 	defaultDistFiles := proptools.StringDefault(m.properties.Default_dist_files, defaultDistFiles_Tagged) | 
 | 	switch defaultDistFiles { | 
 | 	case defaultDistFiles_None: | 
 | 		// Do nothing | 
 |  | 
 | 	case defaultDistFiles_Default: | 
 | 		path := PathForTesting("default-dist.out") | 
 | 		m.defaultDistPaths = Paths{path} | 
 | 		m.distFiles = MakeDefaultDistFiles(path) | 
 |  | 
 | 	case defaultDistFiles_Tagged: | 
 | 		// Module types that set AndroidMkEntry.DistFiles to the result of calling | 
 | 		// GenerateTaggedDistFiles(ctx) relied on no tag being treated as "" which | 
 | 		// meant that the default dist paths would be whatever was returned by | 
 | 		// OutputFiles(""). In order to preserve that behavior when treating no tag | 
 | 		// as being equal to DefaultDistTag this ensures that | 
 | 		// OutputFiles(DefaultDistTag) will return the same as OutputFiles(""). | 
 | 		m.defaultDistPaths = PathsForTesting("one.out") | 
 |  | 
 | 		// This must be called after setting defaultDistPaths/outputFile as | 
 | 		// GenerateTaggedDistFiles calls into OutputFiles(tag) which may use those | 
 | 		// fields. | 
 | 		m.distFiles = m.GenerateTaggedDistFiles(ctx) | 
 | 	} | 
 | } | 
 |  | 
 | func (m *customModule) AndroidMk() AndroidMkData { | 
 | 	return AndroidMkData{ | 
 | 		Custom: func(w io.Writer, name, prefix, moduleDir string, data AndroidMkData) { | 
 | 			m.data = data | 
 | 		}, | 
 | 	} | 
 | } | 
 |  | 
 | func (m *customModule) OutputFiles(tag string) (Paths, error) { | 
 | 	switch tag { | 
 | 	case DefaultDistTag: | 
 | 		if m.defaultDistPaths != nil { | 
 | 			return m.defaultDistPaths, nil | 
 | 		} else { | 
 | 			return nil, fmt.Errorf("default dist tag is not available") | 
 | 		} | 
 | 	case "": | 
 | 		return PathsForTesting("one.out"), nil | 
 | 	case ".multiple": | 
 | 		return PathsForTesting("two.out", "three/four.out"), nil | 
 | 	case ".another-tag": | 
 | 		return PathsForTesting("another.out"), nil | 
 | 	default: | 
 | 		return nil, fmt.Errorf("unsupported module reference tag %q", tag) | 
 | 	} | 
 | } | 
 |  | 
 | func (m *customModule) AndroidMkEntries() []AndroidMkEntries { | 
 | 	return []AndroidMkEntries{ | 
 | 		{ | 
 | 			Class:      "CUSTOM_MODULE", | 
 | 			DistFiles:  m.distFiles, | 
 | 			OutputFile: m.outputFile, | 
 | 		}, | 
 | 	} | 
 | } | 
 |  | 
 | func customModuleFactory() Module { | 
 | 	module := &customModule{} | 
 |  | 
 | 	module.AddProperties(&module.properties) | 
 |  | 
 | 	InitAndroidModule(module) | 
 | 	return module | 
 | } | 
 |  | 
 | // buildContextAndCustomModuleFoo creates a config object, processes the supplied | 
 | // bp module and then returns the config and the custom module called "foo". | 
 | func buildContextAndCustomModuleFoo(t *testing.T, bp string) (*TestContext, *customModule) { | 
 | 	t.Helper() | 
 | 	result := GroupFixturePreparers( | 
 | 		// Enable androidmk Singleton | 
 | 		PrepareForTestWithAndroidMk, | 
 | 		FixtureRegisterWithContext(func(ctx RegistrationContext) { | 
 | 			ctx.RegisterModuleType("custom", customModuleFactory) | 
 | 		}), | 
 | 		FixtureModifyProductVariables(func(variables FixtureProductVariables) { | 
 | 			variables.DeviceProduct = proptools.StringPtr("bar") | 
 | 		}), | 
 | 		FixtureWithRootAndroidBp(bp), | 
 | 	).RunTest(t) | 
 |  | 
 | 	module := result.ModuleForTests("foo", "").Module().(*customModule) | 
 | 	return result.TestContext, module | 
 | } | 
 |  | 
 | func TestAndroidMkSingleton_PassesUpdatedAndroidMkDataToCustomCallback(t *testing.T) { | 
 | 	if runtime.GOOS == "darwin" { | 
 | 		// Device modules are not exported on Mac, so this test doesn't work. | 
 | 		t.SkipNow() | 
 | 	} | 
 |  | 
 | 	bp := ` | 
 | 	custom { | 
 | 		name: "foo", | 
 | 		required: ["bar"], | 
 | 		host_required: ["baz"], | 
 | 		target_required: ["qux"], | 
 | 	} | 
 | 	` | 
 |  | 
 | 	_, m := buildContextAndCustomModuleFoo(t, bp) | 
 |  | 
 | 	assertEqual := func(expected interface{}, actual interface{}) { | 
 | 		if !reflect.DeepEqual(expected, actual) { | 
 | 			t.Errorf("%q expected, but got %q", expected, actual) | 
 | 		} | 
 | 	} | 
 | 	assertEqual([]string{"bar"}, m.data.Required) | 
 | 	assertEqual([]string{"baz"}, m.data.Host_required) | 
 | 	assertEqual([]string{"qux"}, m.data.Target_required) | 
 | } | 
 |  | 
 | func TestGenerateDistContributionsForMake(t *testing.T) { | 
 | 	dc := &distContributions{ | 
 | 		copiesForGoals: []*copiesForGoals{ | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("one.out", "one.out"), | 
 | 					distCopyForTest("two.out", "other.out"), | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	dc.licenseMetadataFile = PathForTesting("meta_lic") | 
 | 	makeOutput := generateDistContributionsForMake(dc) | 
 |  | 
 | 	assertStringEquals(t, `.PHONY: my_goal | 
 | $(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic)) | 
 | $(call dist-for-goals,my_goal,one.out:one.out) | 
 | $(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic)) | 
 | $(call dist-for-goals,my_goal,two.out:other.out) | 
 | `, strings.Join(makeOutput, "")) | 
 | } | 
 |  | 
 | func TestGetDistForGoals(t *testing.T) { | 
 | 	bp := ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				dist: { | 
 | 					targets: ["my_goal", "my_other_goal"], | 
 | 					tag: ".multiple", | 
 | 				}, | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_second_goal"], | 
 | 						tag: ".multiple", | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_third_goal"], | 
 | 						dir: "test/dir", | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_fourth_goal"], | 
 | 						suffix: ".suffix", | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_fifth_goal"], | 
 | 						dest: "new-name", | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_sixth_goal"], | 
 | 						dest: "new-name", | 
 | 						dir: "some/dir", | 
 | 						suffix: ".suffix", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | 			` | 
 |  | 
 | 	expectedAndroidMkLines := []string{ | 
 | 		".PHONY: my_second_goal\n", | 
 | 		"$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))\n", | 
 | 		"$(call dist-for-goals,my_second_goal,two.out:two.out)\n", | 
 | 		"$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))\n", | 
 | 		"$(call dist-for-goals,my_second_goal,three/four.out:four.out)\n", | 
 | 		".PHONY: my_third_goal\n", | 
 | 		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))\n", | 
 | 		"$(call dist-for-goals,my_third_goal,one.out:test/dir/one.out)\n", | 
 | 		".PHONY: my_fourth_goal\n", | 
 | 		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))\n", | 
 | 		"$(call dist-for-goals,my_fourth_goal,one.out:one.suffix.out)\n", | 
 | 		".PHONY: my_fifth_goal\n", | 
 | 		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))\n", | 
 | 		"$(call dist-for-goals,my_fifth_goal,one.out:new-name)\n", | 
 | 		".PHONY: my_sixth_goal\n", | 
 | 		"$(if $(strip $(ALL_TARGETS.one.out.META_LIC)),,$(eval ALL_TARGETS.one.out.META_LIC := meta_lic))\n", | 
 | 		"$(call dist-for-goals,my_sixth_goal,one.out:some/dir/new-name.suffix)\n", | 
 | 		".PHONY: my_goal my_other_goal\n", | 
 | 		"$(if $(strip $(ALL_TARGETS.two.out.META_LIC)),,$(eval ALL_TARGETS.two.out.META_LIC := meta_lic))\n", | 
 | 		"$(call dist-for-goals,my_goal my_other_goal,two.out:two.out)\n", | 
 | 		"$(if $(strip $(ALL_TARGETS.three/four.out.META_LIC)),,$(eval ALL_TARGETS.three/four.out.META_LIC := meta_lic))\n", | 
 | 		"$(call dist-for-goals,my_goal my_other_goal,three/four.out:four.out)\n", | 
 | 	} | 
 |  | 
 | 	ctx, module := buildContextAndCustomModuleFoo(t, bp) | 
 | 	entries := AndroidMkEntriesForTest(t, ctx, module) | 
 | 	if len(entries) != 1 { | 
 | 		t.Errorf("Expected a single AndroidMk entry, got %d", len(entries)) | 
 | 	} | 
 | 	androidMkLines := entries[0].GetDistForGoals(module) | 
 |  | 
 | 	if len(androidMkLines) != len(expectedAndroidMkLines) { | 
 | 		t.Errorf( | 
 | 			"Expected %d AndroidMk lines, got %d:\n%v", | 
 | 			len(expectedAndroidMkLines), | 
 | 			len(androidMkLines), | 
 | 			androidMkLines, | 
 | 		) | 
 | 	} | 
 | 	for idx, line := range androidMkLines { | 
 | 		expectedLine := strings.ReplaceAll(expectedAndroidMkLines[idx], "meta_lic", module.base().licenseMetadataFile.String()) | 
 | 		if line != expectedLine { | 
 | 			t.Errorf( | 
 | 				"Expected AndroidMk line to be '%s', got '%s'", | 
 | 				expectedLine, | 
 | 				line, | 
 | 			) | 
 | 		} | 
 | 	} | 
 | } | 
 |  | 
 | func distCopyForTest(from, to string) distCopy { | 
 | 	return distCopy{PathForTesting(from), to} | 
 | } | 
 |  | 
 | func TestGetDistContributions(t *testing.T) { | 
 | 	compareContributions := func(d1 *distContributions, d2 *distContributions) error { | 
 | 		if d1 == nil || d2 == nil { | 
 | 			if d1 != d2 { | 
 | 				return fmt.Errorf("pointer mismatch, expected both to be nil but they were %p and %p", d1, d2) | 
 | 			} else { | 
 | 				return nil | 
 | 			} | 
 | 		} | 
 | 		if expected, actual := len(d1.copiesForGoals), len(d2.copiesForGoals); expected != actual { | 
 | 			return fmt.Errorf("length mismatch, expected %d found %d", expected, actual) | 
 | 		} | 
 |  | 
 | 		for i, copies1 := range d1.copiesForGoals { | 
 | 			copies2 := d2.copiesForGoals[i] | 
 | 			if expected, actual := copies1.goals, copies2.goals; expected != actual { | 
 | 				return fmt.Errorf("goals mismatch at position %d: expected %q found %q", i, expected, actual) | 
 | 			} | 
 |  | 
 | 			if expected, actual := len(copies1.copies), len(copies2.copies); expected != actual { | 
 | 				return fmt.Errorf("length mismatch in copy instructions at position %d, expected %d found %d", i, expected, actual) | 
 | 			} | 
 |  | 
 | 			for j, c1 := range copies1.copies { | 
 | 				c2 := copies2.copies[j] | 
 | 				if expected, actual := NormalizePathForTesting(c1.from), NormalizePathForTesting(c2.from); expected != actual { | 
 | 					return fmt.Errorf("paths mismatch at position %d.%d: expected %q found %q", i, j, expected, actual) | 
 | 				} | 
 |  | 
 | 				if expected, actual := c1.dest, c2.dest; expected != actual { | 
 | 					return fmt.Errorf("dest mismatch at position %d.%d: expected %q found %q", i, j, expected, actual) | 
 | 				} | 
 | 			} | 
 | 		} | 
 |  | 
 | 		return nil | 
 | 	} | 
 |  | 
 | 	formatContributions := func(d *distContributions) string { | 
 | 		buf := &strings.Builder{} | 
 | 		if d == nil { | 
 | 			fmt.Fprint(buf, "nil") | 
 | 		} else { | 
 | 			for _, copiesForGoals := range d.copiesForGoals { | 
 | 				fmt.Fprintf(buf, "    Goals: %q {\n", copiesForGoals.goals) | 
 | 				for _, c := range copiesForGoals.copies { | 
 | 					fmt.Fprintf(buf, "        %s -> %s\n", NormalizePathForTesting(c.from), c.dest) | 
 | 				} | 
 | 				fmt.Fprint(buf, "    }\n") | 
 | 			} | 
 | 		} | 
 | 		return buf.String() | 
 | 	} | 
 |  | 
 | 	testHelper := func(t *testing.T, name, bp string, expectedContributions *distContributions) { | 
 | 		t.Helper() | 
 | 		t.Run(name, func(t *testing.T) { | 
 | 			t.Helper() | 
 |  | 
 | 			ctx, module := buildContextAndCustomModuleFoo(t, bp) | 
 | 			entries := AndroidMkEntriesForTest(t, ctx, module) | 
 | 			if len(entries) != 1 { | 
 | 				t.Errorf("Expected a single AndroidMk entry, got %d", len(entries)) | 
 | 			} | 
 | 			distContributions := entries[0].getDistContributions(module) | 
 |  | 
 | 			if err := compareContributions(expectedContributions, distContributions); err != nil { | 
 | 				t.Errorf("%s\nExpected Contributions\n%sActualContributions\n%s", | 
 | 					err, | 
 | 					formatContributions(expectedContributions), | 
 | 					formatContributions(distContributions)) | 
 | 			} | 
 | 		}) | 
 | 	} | 
 |  | 
 | 	testHelper(t, "dist-without-tag", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				dist: { | 
 | 					targets: ["my_goal"] | 
 | 				} | 
 | 			} | 
 | `, | 
 | 		&distContributions{ | 
 | 			copiesForGoals: []*copiesForGoals{ | 
 | 				{ | 
 | 					goals: "my_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "one.out"), | 
 | 					}, | 
 | 				}, | 
 | 			}, | 
 | 		}) | 
 |  | 
 | 	testHelper(t, "dist-with-tag", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				dist: { | 
 | 					targets: ["my_goal"], | 
 | 					tag: ".another-tag", | 
 | 				} | 
 | 			} | 
 | `, | 
 | 		&distContributions{ | 
 | 			copiesForGoals: []*copiesForGoals{ | 
 | 				{ | 
 | 					goals: "my_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("another.out", "another.out"), | 
 | 					}, | 
 | 				}, | 
 | 			}, | 
 | 		}) | 
 |  | 
 | 	testHelper(t, "append-artifact-with-product", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				dist: { | 
 | 					targets: ["my_goal"], | 
 | 					append_artifact_with_product: true, | 
 | 				} | 
 | 			} | 
 | `, &distContributions{ | 
 | 		copiesForGoals: []*copiesForGoals{ | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("one.out", "one_bar.out"), | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	}) | 
 |  | 
 | 	testHelper(t, "dists-with-tag", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 						tag: ".another-tag", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, | 
 | 		&distContributions{ | 
 | 			copiesForGoals: []*copiesForGoals{ | 
 | 				{ | 
 | 					goals: "my_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("another.out", "another.out"), | 
 | 					}, | 
 | 				}, | 
 | 			}, | 
 | 		}) | 
 |  | 
 | 	testHelper(t, "multiple-dists-with-and-without-tag", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_second_goal", "my_third_goal"], | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, | 
 | 		&distContributions{ | 
 | 			copiesForGoals: []*copiesForGoals{ | 
 | 				{ | 
 | 					goals: "my_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "one.out"), | 
 | 					}, | 
 | 				}, | 
 | 				{ | 
 | 					goals: "my_second_goal my_third_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "one.out"), | 
 | 					}, | 
 | 				}, | 
 | 			}, | 
 | 		}) | 
 |  | 
 | 	testHelper(t, "dist-plus-dists-without-tags", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				dist: { | 
 | 					targets: ["my_goal"], | 
 | 				}, | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_second_goal", "my_third_goal"], | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, | 
 | 		&distContributions{ | 
 | 			copiesForGoals: []*copiesForGoals{ | 
 | 				{ | 
 | 					goals: "my_second_goal my_third_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "one.out"), | 
 | 					}, | 
 | 				}, | 
 | 				{ | 
 | 					goals: "my_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "one.out"), | 
 | 					}, | 
 | 				}, | 
 | 			}, | 
 | 		}) | 
 |  | 
 | 	testHelper(t, "dist-plus-dists-with-tags", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				dist: { | 
 | 					targets: ["my_goal", "my_other_goal"], | 
 | 					tag: ".multiple", | 
 | 				}, | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_second_goal"], | 
 | 						tag: ".multiple", | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_third_goal"], | 
 | 						dir: "test/dir", | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_fourth_goal"], | 
 | 						suffix: ".suffix", | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_fifth_goal"], | 
 | 						dest: "new-name", | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_sixth_goal"], | 
 | 						dest: "new-name", | 
 | 						dir: "some/dir", | 
 | 						suffix: ".suffix", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, | 
 | 		&distContributions{ | 
 | 			copiesForGoals: []*copiesForGoals{ | 
 | 				{ | 
 | 					goals: "my_second_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("two.out", "two.out"), | 
 | 						distCopyForTest("three/four.out", "four.out"), | 
 | 					}, | 
 | 				}, | 
 | 				{ | 
 | 					goals: "my_third_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "test/dir/one.out"), | 
 | 					}, | 
 | 				}, | 
 | 				{ | 
 | 					goals: "my_fourth_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "one.suffix.out"), | 
 | 					}, | 
 | 				}, | 
 | 				{ | 
 | 					goals: "my_fifth_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "new-name"), | 
 | 					}, | 
 | 				}, | 
 | 				{ | 
 | 					goals: "my_sixth_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("one.out", "some/dir/new-name.suffix"), | 
 | 					}, | 
 | 				}, | 
 | 				{ | 
 | 					goals: "my_goal my_other_goal", | 
 | 					copies: []distCopy{ | 
 | 						distCopyForTest("two.out", "two.out"), | 
 | 						distCopyForTest("three/four.out", "four.out"), | 
 | 					}, | 
 | 				}, | 
 | 			}, | 
 | 		}) | 
 |  | 
 | 	// The above test the default values of default_dist_files and use_output_file. | 
 |  | 
 | 	// The following tests explicitly test the different combinations of those settings. | 
 | 	testHelper(t, "tagged-dist-files-no-output", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				default_dist_files: "tagged", | 
 | 				dist_output_file: false, | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 						tag: ".multiple", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, &distContributions{ | 
 | 		copiesForGoals: []*copiesForGoals{ | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("one.out", "one.out"), | 
 | 				}, | 
 | 			}, | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("two.out", "two.out"), | 
 | 					distCopyForTest("three/four.out", "four.out"), | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	}) | 
 |  | 
 | 	testHelper(t, "default-dist-files-no-output", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				default_dist_files: "default", | 
 | 				dist_output_file: false, | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 						tag: ".multiple", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, &distContributions{ | 
 | 		copiesForGoals: []*copiesForGoals{ | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("default-dist.out", "default-dist.out"), | 
 | 				}, | 
 | 			}, | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("two.out", "two.out"), | 
 | 					distCopyForTest("three/four.out", "four.out"), | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	}) | 
 |  | 
 | 	testHelper(t, "no-dist-files-no-output", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				default_dist_files: "none", | 
 | 				dist_output_file: false, | 
 | 				dists: [ | 
 | 					// The following is silently ignored because there is not default file | 
 | 					// in either the dist files or the output file. | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 						tag: ".multiple", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, &distContributions{ | 
 | 		copiesForGoals: []*copiesForGoals{ | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("two.out", "two.out"), | 
 | 					distCopyForTest("three/four.out", "four.out"), | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	}) | 
 |  | 
 | 	testHelper(t, "tagged-dist-files-default-output", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				default_dist_files: "tagged", | 
 | 				dist_output_file: true, | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 						tag: ".multiple", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, &distContributions{ | 
 | 		copiesForGoals: []*copiesForGoals{ | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("one.out", "one.out"), | 
 | 				}, | 
 | 			}, | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("two.out", "two.out"), | 
 | 					distCopyForTest("three/four.out", "four.out"), | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	}) | 
 |  | 
 | 	testHelper(t, "default-dist-files-default-output", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				default_dist_files: "default", | 
 | 				dist_output_file: true, | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 						tag: ".multiple", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, &distContributions{ | 
 | 		copiesForGoals: []*copiesForGoals{ | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("default-dist.out", "default-dist.out"), | 
 | 				}, | 
 | 			}, | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("two.out", "two.out"), | 
 | 					distCopyForTest("three/four.out", "four.out"), | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	}) | 
 |  | 
 | 	testHelper(t, "no-dist-files-default-output", ` | 
 | 			custom { | 
 | 				name: "foo", | 
 | 				default_dist_files: "none", | 
 | 				dist_output_file: true, | 
 | 				dists: [ | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 					}, | 
 | 					{ | 
 | 						targets: ["my_goal"], | 
 | 						tag: ".multiple", | 
 | 					}, | 
 | 				], | 
 | 			} | 
 | `, &distContributions{ | 
 | 		copiesForGoals: []*copiesForGoals{ | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("dist-output-file.out", "dist-output-file.out"), | 
 | 				}, | 
 | 			}, | 
 | 			{ | 
 | 				goals: "my_goal", | 
 | 				copies: []distCopy{ | 
 | 					distCopyForTest("two.out", "two.out"), | 
 | 					distCopyForTest("three/four.out", "four.out"), | 
 | 				}, | 
 | 			}, | 
 | 		}, | 
 | 	}) | 
 | } |