| // Copyright 2018 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 genrule | 
 |  | 
 | import ( | 
 | 	"io/ioutil" | 
 | 	"os" | 
 | 	"reflect" | 
 | 	"strings" | 
 | 	"testing" | 
 |  | 
 | 	"android/soong/android" | 
 |  | 
 | 	"github.com/google/blueprint/proptools" | 
 | ) | 
 |  | 
 | var buildDir string | 
 |  | 
 | func setUp() { | 
 | 	var err error | 
 | 	buildDir, err = ioutil.TempDir("", "genrule_test") | 
 | 	if err != nil { | 
 | 		panic(err) | 
 | 	} | 
 | } | 
 |  | 
 | func tearDown() { | 
 | 	os.RemoveAll(buildDir) | 
 | } | 
 |  | 
 | func TestMain(m *testing.M) { | 
 | 	run := func() int { | 
 | 		setUp() | 
 | 		defer tearDown() | 
 |  | 
 | 		return m.Run() | 
 | 	} | 
 |  | 
 | 	os.Exit(run()) | 
 | } | 
 |  | 
 | func testContext(config android.Config) *android.TestContext { | 
 |  | 
 | 	ctx := android.NewTestArchContext() | 
 | 	ctx.RegisterModuleType("filegroup", android.FileGroupFactory) | 
 | 	ctx.RegisterModuleType("tool", toolFactory) | 
 |  | 
 | 	registerGenruleBuildComponents(ctx) | 
 |  | 
 | 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators) | 
 | 	ctx.Register(config) | 
 |  | 
 | 	return ctx | 
 | } | 
 |  | 
 | func testConfig(bp string, fs map[string][]byte) android.Config { | 
 | 	bp += ` | 
 | 		tool { | 
 | 			name: "tool", | 
 | 		} | 
 |  | 
 | 		filegroup { | 
 | 			name: "tool_files", | 
 | 			srcs: [ | 
 | 				"tool_file1", | 
 | 				"tool_file2", | 
 | 			], | 
 | 		} | 
 |  | 
 | 		filegroup { | 
 | 			name: "1tool_file", | 
 | 			srcs: [ | 
 | 				"tool_file1", | 
 | 			], | 
 | 		} | 
 |  | 
 | 		filegroup { | 
 | 			name: "ins", | 
 | 			srcs: [ | 
 | 				"in1", | 
 | 				"in2", | 
 | 			], | 
 | 		} | 
 |  | 
 | 		filegroup { | 
 | 			name: "1in", | 
 | 			srcs: [ | 
 | 				"in1", | 
 | 			], | 
 | 		} | 
 |  | 
 | 		filegroup { | 
 | 			name: "empty", | 
 | 		} | 
 | 	` | 
 |  | 
 | 	mockFS := map[string][]byte{ | 
 | 		"tool":       nil, | 
 | 		"tool_file1": nil, | 
 | 		"tool_file2": nil, | 
 | 		"in1":        nil, | 
 | 		"in2":        nil, | 
 | 		"in1.txt":    nil, | 
 | 		"in2.txt":    nil, | 
 | 		"in3.txt":    nil, | 
 | 	} | 
 |  | 
 | 	for k, v := range fs { | 
 | 		mockFS[k] = v | 
 | 	} | 
 |  | 
 | 	return android.TestArchConfig(buildDir, nil, bp, mockFS) | 
 | } | 
 |  | 
 | func TestGenruleCmd(t *testing.T) { | 
 | 	testcases := []struct { | 
 | 		name string | 
 | 		prop string | 
 |  | 
 | 		allowMissingDependencies bool | 
 |  | 
 | 		err    string | 
 | 		expect string | 
 | 	}{ | 
 | 		{ | 
 | 			name: "empty location tool", | 
 | 			prop: ` | 
 | 				tools: ["tool"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location) > $(out)", | 
 | 			`, | 
 | 			expect: "out/tool > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "empty location tool2", | 
 | 			prop: ` | 
 | 				tools: [":tool"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location) > $(out)", | 
 | 			`, | 
 | 			expect: "out/tool > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "empty location tool file", | 
 | 			prop: ` | 
 | 				tool_files: ["tool_file1"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location) > $(out)", | 
 | 			`, | 
 | 			expect: "tool_file1 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "empty location tool file fg", | 
 | 			prop: ` | 
 | 				tool_files: [":1tool_file"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location) > $(out)", | 
 | 			`, | 
 | 			expect: "tool_file1 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "empty location tool and tool file", | 
 | 			prop: ` | 
 | 				tools: ["tool"], | 
 | 				tool_files: ["tool_file1"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location) > $(out)", | 
 | 			`, | 
 | 			expect: "out/tool > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "tool", | 
 | 			prop: ` | 
 | 				tools: ["tool"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location tool) > $(out)", | 
 | 			`, | 
 | 			expect: "out/tool > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "tool2", | 
 | 			prop: ` | 
 | 				tools: [":tool"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location :tool) > $(out)", | 
 | 			`, | 
 | 			expect: "out/tool > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "tool file", | 
 | 			prop: ` | 
 | 				tool_files: ["tool_file1"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location tool_file1) > $(out)", | 
 | 			`, | 
 | 			expect: "tool_file1 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "tool file fg", | 
 | 			prop: ` | 
 | 				tool_files: [":1tool_file"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location :1tool_file) > $(out)", | 
 | 			`, | 
 | 			expect: "tool_file1 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "tool files", | 
 | 			prop: ` | 
 | 				tool_files: [":tool_files"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(locations :tool_files) > $(out)", | 
 | 			`, | 
 | 			expect: "tool_file1 tool_file2 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "in1", | 
 | 			prop: ` | 
 | 				srcs: ["in1"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(in) > $(out)", | 
 | 			`, | 
 | 			expect: "cat ${in} > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "in1 fg", | 
 | 			prop: ` | 
 | 				srcs: [":1in"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(in) > $(out)", | 
 | 			`, | 
 | 			expect: "cat ${in} > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "ins", | 
 | 			prop: ` | 
 | 				srcs: ["in1", "in2"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(in) > $(out)", | 
 | 			`, | 
 | 			expect: "cat ${in} > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "ins fg", | 
 | 			prop: ` | 
 | 				srcs: [":ins"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(in) > $(out)", | 
 | 			`, | 
 | 			expect: "cat ${in} > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "location in1", | 
 | 			prop: ` | 
 | 				srcs: ["in1"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(location in1) > $(out)", | 
 | 			`, | 
 | 			expect: "cat in1 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "location in1 fg", | 
 | 			prop: ` | 
 | 				srcs: [":1in"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(location :1in) > $(out)", | 
 | 			`, | 
 | 			expect: "cat in1 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "location ins", | 
 | 			prop: ` | 
 | 				srcs: ["in1", "in2"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(location in1) > $(out)", | 
 | 			`, | 
 | 			expect: "cat in1 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "location ins fg", | 
 | 			prop: ` | 
 | 				srcs: [":ins"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(locations :ins) > $(out)", | 
 | 			`, | 
 | 			expect: "cat in1 in2 > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "outs", | 
 | 			prop: ` | 
 | 				out: ["out", "out2"], | 
 | 				cmd: "echo foo > $(out)", | 
 | 			`, | 
 | 			expect: "echo foo > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "location out", | 
 | 			prop: ` | 
 | 				out: ["out", "out2"], | 
 | 				cmd: "echo foo > $(location out2)", | 
 | 			`, | 
 | 			expect: "echo foo > __SBOX_OUT_DIR__/out2", | 
 | 		}, | 
 | 		{ | 
 | 			name: "depfile", | 
 | 			prop: ` | 
 | 				out: ["out"], | 
 | 				depfile: true, | 
 | 				cmd: "echo foo > $(out) && touch $(depfile)", | 
 | 			`, | 
 | 			expect: "echo foo > __SBOX_OUT_FILES__ && touch __SBOX_DEPFILE__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "gendir", | 
 | 			prop: ` | 
 | 				out: ["out"], | 
 | 				cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)", | 
 | 			`, | 
 | 			expect: "echo foo > __SBOX_OUT_DIR__/foo && cp __SBOX_OUT_DIR__/foo __SBOX_OUT_FILES__", | 
 | 		}, | 
 |  | 
 | 		{ | 
 | 			name: "error empty location", | 
 | 			prop: ` | 
 | 				out: ["out"], | 
 | 				cmd: "$(location) > $(out)", | 
 | 			`, | 
 | 			err: "at least one `tools` or `tool_files` is required if $(location) is used", | 
 | 		}, | 
 | 		{ | 
 | 			name: "error empty location no files", | 
 | 			prop: ` | 
 | 				tool_files: [":empty"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location) > $(out)", | 
 | 			`, | 
 | 			err: `default label ":empty" has no files`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "error empty location multiple files", | 
 | 			prop: ` | 
 | 				tool_files: [":tool_files"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location) > $(out)", | 
 | 			`, | 
 | 			err: `default label ":tool_files" has multiple files`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "error location", | 
 | 			prop: ` | 
 | 				out: ["out"], | 
 | 				cmd: "echo foo > $(location missing)", | 
 | 			`, | 
 | 			err: `unknown location label "missing"`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "error locations", | 
 | 			prop: ` | 
 | 					out: ["out"], | 
 | 					cmd: "echo foo > $(locations missing)", | 
 | 			`, | 
 | 			err: `unknown locations label "missing"`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "error location no files", | 
 | 			prop: ` | 
 | 					out: ["out"], | 
 | 					srcs: [":empty"], | 
 | 					cmd: "echo $(location :empty) > $(out)", | 
 | 			`, | 
 | 			err: `label ":empty" has no files`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "error locations no files", | 
 | 			prop: ` | 
 | 					out: ["out"], | 
 | 					srcs: [":empty"], | 
 | 					cmd: "echo $(locations :empty) > $(out)", | 
 | 			`, | 
 | 			err: `label ":empty" has no files`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "error location multiple files", | 
 | 			prop: ` | 
 | 					out: ["out"], | 
 | 					srcs: [":ins"], | 
 | 					cmd: "echo $(location :ins) > $(out)", | 
 | 			`, | 
 | 			err: `label ":ins" has multiple files`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "error variable", | 
 | 			prop: ` | 
 | 					out: ["out"], | 
 | 					srcs: ["in1"], | 
 | 					cmd: "echo $(foo) > $(out)", | 
 | 			`, | 
 | 			err: `unknown variable '$(foo)'`, | 
 | 		}, | 
 | 		{ | 
 | 			name: "error depfile", | 
 | 			prop: ` | 
 | 				out: ["out"], | 
 | 				cmd: "echo foo > $(out) && touch $(depfile)", | 
 | 			`, | 
 | 			err: "$(depfile) used without depfile property", | 
 | 		}, | 
 | 		{ | 
 | 			name: "error no depfile", | 
 | 			prop: ` | 
 | 				out: ["out"], | 
 | 				depfile: true, | 
 | 				cmd: "echo foo > $(out)", | 
 | 			`, | 
 | 			err: "specified depfile=true but did not include a reference to '${depfile}' in cmd", | 
 | 		}, | 
 | 		{ | 
 | 			name: "error no out", | 
 | 			prop: ` | 
 | 				cmd: "echo foo > $(out)", | 
 | 			`, | 
 | 			err: "must have at least one output file", | 
 | 		}, | 
 | 		{ | 
 | 			name: "srcs allow missing dependencies", | 
 | 			prop: ` | 
 | 				srcs: [":missing"], | 
 | 				out: ["out"], | 
 | 				cmd: "cat $(location :missing) > $(out)", | 
 | 			`, | 
 |  | 
 | 			allowMissingDependencies: true, | 
 |  | 
 | 			expect: "cat ***missing srcs :missing*** > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 		{ | 
 | 			name: "tool allow missing dependencies", | 
 | 			prop: ` | 
 | 				tools: [":missing"], | 
 | 				out: ["out"], | 
 | 				cmd: "$(location :missing) > $(out)", | 
 | 			`, | 
 |  | 
 | 			allowMissingDependencies: true, | 
 |  | 
 | 			expect: "***missing tool :missing*** > __SBOX_OUT_FILES__", | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	for _, test := range testcases { | 
 | 		t.Run(test.name, func(t *testing.T) { | 
 | 			bp := "genrule {\n" | 
 | 			bp += "name: \"gen\",\n" | 
 | 			bp += test.prop | 
 | 			bp += "}\n" | 
 |  | 
 | 			config := testConfig(bp, nil) | 
 | 			config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(test.allowMissingDependencies) | 
 |  | 
 | 			ctx := testContext(config) | 
 | 			ctx.SetAllowMissingDependencies(test.allowMissingDependencies) | 
 |  | 
 | 			_, errs := ctx.ParseFileList(".", []string{"Android.bp"}) | 
 | 			if errs == nil { | 
 | 				_, errs = ctx.PrepareBuildActions(config) | 
 | 			} | 
 | 			if errs == nil && test.err != "" { | 
 | 				t.Fatalf("want error %q, got no error", test.err) | 
 | 			} else if errs != nil && test.err == "" { | 
 | 				android.FailIfErrored(t, errs) | 
 | 			} else if test.err != "" { | 
 | 				if len(errs) != 1 { | 
 | 					t.Errorf("want 1 error, got %d errors:", len(errs)) | 
 | 					for _, err := range errs { | 
 | 						t.Errorf("   %s", err.Error()) | 
 | 					} | 
 | 					t.FailNow() | 
 | 				} | 
 | 				if !strings.Contains(errs[0].Error(), test.err) { | 
 | 					t.Fatalf("want %q, got %q", test.err, errs[0].Error()) | 
 | 				} | 
 | 				return | 
 | 			} | 
 |  | 
 | 			gen := ctx.ModuleForTests("gen", "").Module().(*Module) | 
 | 			if g, w := gen.rawCommands[0], "'"+test.expect+"'"; w != g { | 
 | 				t.Errorf("want %q, got %q", w, g) | 
 | 			} | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | func TestGenruleHashInputs(t *testing.T) { | 
 |  | 
 | 	// The basic idea here is to verify that the sbox command (which is | 
 | 	// in the Command field of the generate rule) contains a hash of the | 
 | 	// inputs, but only if $(in) is not referenced in the genrule cmd | 
 | 	// property. | 
 |  | 
 | 	// By including a hash of the inputs, we cause the rule to re-run if | 
 | 	// the list of inputs changes (because the sbox command changes). | 
 |  | 
 | 	// However, if the genrule cmd property already contains $(in), then | 
 | 	// the dependency is already expressed, so we don't need to include the | 
 | 	// hash in that case. | 
 |  | 
 | 	bp := ` | 
 | 			genrule { | 
 | 				name: "hash0", | 
 | 				srcs: ["in1.txt", "in2.txt"], | 
 | 				out: ["out"], | 
 | 				cmd: "echo foo > $(out)", | 
 | 			} | 
 | 			genrule { | 
 | 				name: "hash1", | 
 | 				srcs: ["*.txt"], | 
 | 				out: ["out"], | 
 | 				cmd: "echo bar > $(out)", | 
 | 			} | 
 | 			genrule { | 
 | 				name: "hash2", | 
 | 				srcs: ["*.txt"], | 
 | 				out: ["out"], | 
 | 				cmd: "echo $(in) > $(out)", | 
 | 			} | 
 | 		` | 
 | 	testcases := []struct { | 
 | 		name         string | 
 | 		expectedHash string | 
 | 	}{ | 
 | 		{ | 
 | 			name: "hash0", | 
 | 			// sha256 value obtained from: echo -n 'in1.txtin2.txt' | sha256sum | 
 | 			expectedHash: "031097e11e0a8c822c960eb9742474f46336360a515744000d086d94335a9cb9", | 
 | 		}, | 
 | 		{ | 
 | 			name: "hash1", | 
 | 			// sha256 value obtained from: echo -n 'in1.txtin2.txtin3.txt' | sha256sum | 
 | 			expectedHash: "de5d22a4a7ab50d250cc59fcdf7a7e0775790d270bfca3a7a9e1f18a70dd996c", | 
 | 		}, | 
 | 		{ | 
 | 			name: "hash2", | 
 | 			// $(in) is present, option should not appear | 
 | 			expectedHash: "", | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	config := testConfig(bp, nil) | 
 | 	ctx := testContext(config) | 
 | 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"}) | 
 | 	if errs == nil { | 
 | 		_, errs = ctx.PrepareBuildActions(config) | 
 | 	} | 
 | 	if errs != nil { | 
 | 		t.Fatal(errs) | 
 | 	} | 
 |  | 
 | 	for _, test := range testcases { | 
 | 		t.Run(test.name, func(t *testing.T) { | 
 | 			gen := ctx.ModuleForTests(test.name, "") | 
 | 			command := gen.Rule("generator").RuleParams.Command | 
 |  | 
 | 			if len(test.expectedHash) > 0 { | 
 | 				// We add spaces before and after to make sure that | 
 | 				// this option doesn't abutt another sbox option. | 
 | 				expectedInputHashOption := " --input-hash " + test.expectedHash + " " | 
 |  | 
 | 				if !strings.Contains(command, expectedInputHashOption) { | 
 | 					t.Errorf("Expected command \"%s\" to contain \"%s\"", command, expectedInputHashOption) | 
 | 				} | 
 | 			} else { | 
 | 				if strings.Contains(command, "--input-hash") { | 
 | 					t.Errorf("Unexpected \"--input-hash\" found in command: \"%s\"", command) | 
 | 				} | 
 | 			} | 
 | 		}) | 
 | 	} | 
 | } | 
 |  | 
 | func TestGenSrcs(t *testing.T) { | 
 | 	testcases := []struct { | 
 | 		name string | 
 | 		prop string | 
 |  | 
 | 		allowMissingDependencies bool | 
 |  | 
 | 		err   string | 
 | 		cmds  []string | 
 | 		deps  []string | 
 | 		files []string | 
 | 	}{ | 
 | 		{ | 
 | 			name: "gensrcs", | 
 | 			prop: ` | 
 | 				tools: ["tool"], | 
 | 				srcs: ["in1.txt", "in2.txt"], | 
 | 				cmd: "$(location) $(in) > $(out)", | 
 | 			`, | 
 | 			cmds: []string{ | 
 | 				"'bash -c '\\''out/tool in1.txt > __SBOX_OUT_DIR__/in1.h'\\'' && bash -c '\\''out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'\\'''", | 
 | 			}, | 
 | 			deps:  []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"}, | 
 | 			files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h"}, | 
 | 		}, | 
 | 		{ | 
 | 			name: "shards", | 
 | 			prop: ` | 
 | 				tools: ["tool"], | 
 | 				srcs: ["in1.txt", "in2.txt", "in3.txt"], | 
 | 				cmd: "$(location) $(in) > $(out)", | 
 | 				shard_size: 2, | 
 | 			`, | 
 | 			cmds: []string{ | 
 | 				"'bash -c '\\''out/tool in1.txt > __SBOX_OUT_DIR__/in1.h'\\'' && bash -c '\\''out/tool in2.txt > __SBOX_OUT_DIR__/in2.h'\\'''", | 
 | 				"'bash -c '\\''out/tool in3.txt > __SBOX_OUT_DIR__/in3.h'\\'''", | 
 | 			}, | 
 | 			deps:  []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"}, | 
 | 			files: []string{buildDir + "/.intermediates/gen/gen/gensrcs/in1.h", buildDir + "/.intermediates/gen/gen/gensrcs/in2.h", buildDir + "/.intermediates/gen/gen/gensrcs/in3.h"}, | 
 | 		}, | 
 | 	} | 
 |  | 
 | 	for _, test := range testcases { | 
 | 		t.Run(test.name, func(t *testing.T) { | 
 | 			bp := "gensrcs {\n" | 
 | 			bp += `name: "gen",` + "\n" | 
 | 			bp += `output_extension: "h",` + "\n" | 
 | 			bp += test.prop | 
 | 			bp += "}\n" | 
 |  | 
 | 			config := testConfig(bp, nil) | 
 | 			ctx := testContext(config) | 
 |  | 
 | 			_, errs := ctx.ParseFileList(".", []string{"Android.bp"}) | 
 | 			if errs == nil { | 
 | 				_, errs = ctx.PrepareBuildActions(config) | 
 | 			} | 
 | 			if errs == nil && test.err != "" { | 
 | 				t.Fatalf("want error %q, got no error", test.err) | 
 | 			} else if errs != nil && test.err == "" { | 
 | 				android.FailIfErrored(t, errs) | 
 | 			} else if test.err != "" { | 
 | 				if len(errs) != 1 { | 
 | 					t.Errorf("want 1 error, got %d errors:", len(errs)) | 
 | 					for _, err := range errs { | 
 | 						t.Errorf("   %s", err.Error()) | 
 | 					} | 
 | 					t.FailNow() | 
 | 				} | 
 | 				if !strings.Contains(errs[0].Error(), test.err) { | 
 | 					t.Fatalf("want %q, got %q", test.err, errs[0].Error()) | 
 | 				} | 
 | 				return | 
 | 			} | 
 |  | 
 | 			gen := ctx.ModuleForTests("gen", "").Module().(*Module) | 
 | 			if g, w := gen.rawCommands, test.cmds; !reflect.DeepEqual(w, g) { | 
 | 				t.Errorf("want %q, got %q", w, g) | 
 | 			} | 
 |  | 
 | 			if g, w := gen.outputDeps.Strings(), test.deps; !reflect.DeepEqual(w, g) { | 
 | 				t.Errorf("want deps %q, got %q", w, g) | 
 | 			} | 
 |  | 
 | 			if g, w := gen.outputFiles.Strings(), test.files; !reflect.DeepEqual(w, g) { | 
 | 				t.Errorf("want files %q, got %q", w, g) | 
 | 			} | 
 | 		}) | 
 | 	} | 
 |  | 
 | } | 
 |  | 
 | func TestGenruleDefaults(t *testing.T) { | 
 | 	bp := ` | 
 | 				genrule_defaults { | 
 | 					name: "gen_defaults1", | 
 | 					cmd: "cp $(in) $(out)", | 
 | 				} | 
 |  | 
 | 				genrule_defaults { | 
 | 					name: "gen_defaults2", | 
 | 					srcs: ["in1"], | 
 | 				} | 
 |  | 
 | 				genrule { | 
 | 					name: "gen", | 
 | 					out: ["out"], | 
 | 					defaults: ["gen_defaults1", "gen_defaults2"], | 
 | 				} | 
 | 			` | 
 | 	config := testConfig(bp, nil) | 
 | 	ctx := testContext(config) | 
 | 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"}) | 
 | 	if errs == nil { | 
 | 		_, errs = ctx.PrepareBuildActions(config) | 
 | 	} | 
 | 	if errs != nil { | 
 | 		t.Fatal(errs) | 
 | 	} | 
 | 	gen := ctx.ModuleForTests("gen", "").Module().(*Module) | 
 |  | 
 | 	expectedCmd := "'cp ${in} __SBOX_OUT_FILES__'" | 
 | 	if gen.rawCommands[0] != expectedCmd { | 
 | 		t.Errorf("Expected cmd: %q, actual: %q", expectedCmd, gen.rawCommands[0]) | 
 | 	} | 
 |  | 
 | 	expectedSrcs := []string{"in1"} | 
 | 	if !reflect.DeepEqual(expectedSrcs, gen.properties.Srcs) { | 
 | 		t.Errorf("Expected srcs: %q, actual: %q", expectedSrcs, gen.properties.Srcs) | 
 | 	} | 
 | } | 
 |  | 
 | type testTool struct { | 
 | 	android.ModuleBase | 
 | 	outputFile android.Path | 
 | } | 
 |  | 
 | func toolFactory() android.Module { | 
 | 	module := &testTool{} | 
 | 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) | 
 | 	return module | 
 | } | 
 |  | 
 | func (t *testTool) GenerateAndroidBuildActions(ctx android.ModuleContext) { | 
 | 	t.outputFile = android.PathForTesting("out", ctx.ModuleName()) | 
 | } | 
 |  | 
 | func (t *testTool) HostToolPath() android.OptionalPath { | 
 | 	return android.OptionalPathForPath(t.outputFile) | 
 | } | 
 |  | 
 | var _ android.HostToolProvider = (*testTool)(nil) |