| // 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 ( |
| "fmt" |
| "os" |
| "regexp" |
| "strconv" |
| "testing" |
| |
| "android/soong/android" |
| |
| "github.com/google/blueprint/proptools" |
| ) |
| |
| func TestMain(m *testing.M) { |
| os.Exit(m.Run()) |
| } |
| |
| var prepareForGenRuleTest = android.GroupFixturePreparers( |
| android.PrepareForTestWithArchMutator, |
| android.PrepareForTestWithDefaults, |
| android.PrepareForTestWithFilegroup, |
| PrepareForTestWithGenRuleBuildComponents, |
| android.FixtureRegisterWithContext(func(ctx android.RegistrationContext) { |
| android.RegisterPrebuiltMutators(ctx) |
| ctx.RegisterModuleType("tool", toolFactory) |
| ctx.RegisterModuleType("prebuilt_tool", prebuiltToolFactory) |
| ctx.RegisterModuleType("output", outputProducerFactory) |
| ctx.RegisterModuleType("use_source", useSourceFactory) |
| }), |
| android.FixtureMergeMockFs(android.MockFS{ |
| "tool": nil, |
| "tool_file1": nil, |
| "tool_file2": nil, |
| "in1": nil, |
| "in2": nil, |
| "in1.txt": nil, |
| "in2.txt": nil, |
| "in3.txt": nil, |
| }), |
| ) |
| |
| func testGenruleBp() string { |
| return ` |
| 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", |
| } |
| ` |
| } |
| |
| func TestGenruleCmd(t *testing.T) { |
| testcases := []struct { |
| name string |
| moduleName string |
| prop string |
| |
| allowMissingDependencies bool |
| |
| err string |
| expect string |
| }{ |
| { |
| name: "empty location tool", |
| prop: ` |
| tools: ["tool"], |
| out: ["out"], |
| cmd: "$(location) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "empty location tool2", |
| prop: ` |
| tools: [":tool"], |
| out: ["out"], |
| cmd: "$(location) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "empty location tool file", |
| prop: ` |
| tool_files: ["tool_file1"], |
| out: ["out"], |
| cmd: "$(location) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "empty location tool file fg", |
| prop: ` |
| tool_files: [":1tool_file"], |
| out: ["out"], |
| cmd: "$(location) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "empty location tool and tool file", |
| prop: ` |
| tools: ["tool"], |
| tool_files: ["tool_file1"], |
| out: ["out"], |
| cmd: "$(location) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "tool", |
| prop: ` |
| tools: ["tool"], |
| out: ["out"], |
| cmd: "$(location tool) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "tool2", |
| prop: ` |
| tools: [":tool"], |
| out: ["out"], |
| cmd: "$(location :tool) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/out/bin/tool > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "tool file", |
| prop: ` |
| tool_files: ["tool_file1"], |
| out: ["out"], |
| cmd: "$(location tool_file1) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "tool file fg", |
| prop: ` |
| tool_files: [":1tool_file"], |
| out: ["out"], |
| cmd: "$(location :1tool_file) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "tool files", |
| prop: ` |
| tool_files: [":tool_files"], |
| out: ["out"], |
| cmd: "$(locations :tool_files) > $(out)", |
| `, |
| expect: "__SBOX_SANDBOX_DIR__/tools/src/tool_file1 __SBOX_SANDBOX_DIR__/tools/src/tool_file2 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "in1", |
| prop: ` |
| srcs: ["in1"], |
| out: ["out"], |
| cmd: "cat $(in) > $(out)", |
| `, |
| expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "in1 fg", |
| prop: ` |
| srcs: [":1in"], |
| out: ["out"], |
| cmd: "cat $(in) > $(out)", |
| `, |
| expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "ins", |
| prop: ` |
| srcs: ["in1", "in2"], |
| out: ["out"], |
| cmd: "cat $(in) > $(out)", |
| `, |
| expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "ins fg", |
| prop: ` |
| srcs: [":ins"], |
| out: ["out"], |
| cmd: "cat $(in) > $(out)", |
| `, |
| expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "location in1", |
| prop: ` |
| srcs: ["in1"], |
| out: ["out"], |
| cmd: "cat $(location in1) > $(out)", |
| `, |
| expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "location in1 fg", |
| prop: ` |
| srcs: [":1in"], |
| out: ["out"], |
| cmd: "cat $(location :1in) > $(out)", |
| `, |
| expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "location ins", |
| prop: ` |
| srcs: ["in1", "in2"], |
| out: ["out"], |
| cmd: "cat $(location in1) > $(out)", |
| `, |
| expect: "cat in1 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "location ins fg", |
| prop: ` |
| srcs: [":ins"], |
| out: ["out"], |
| cmd: "cat $(locations :ins) > $(out)", |
| `, |
| expect: "cat in1 in2 > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "outs", |
| prop: ` |
| out: ["out", "out2"], |
| cmd: "echo foo > $(out)", |
| `, |
| expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out __SBOX_SANDBOX_DIR__/out/out2", |
| }, |
| { |
| name: "location out", |
| prop: ` |
| out: ["out", "out2"], |
| cmd: "echo foo > $(location out2)", |
| `, |
| expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out2", |
| }, |
| { |
| name: "depfile", |
| moduleName: "depfile_allowed_for_test", |
| prop: ` |
| out: ["out"], |
| depfile: true, |
| cmd: "echo foo > $(out) && touch $(depfile)", |
| `, |
| expect: "echo foo > __SBOX_SANDBOX_DIR__/out/out && touch __SBOX_DEPFILE__", |
| }, |
| { |
| name: "gendir", |
| prop: ` |
| out: ["out"], |
| cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)", |
| `, |
| expect: "echo foo > __SBOX_SANDBOX_DIR__/out/foo && cp __SBOX_SANDBOX_DIR__/out/foo __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "$", |
| prop: ` |
| out: ["out"], |
| cmd: "echo $$ > $(out)", |
| `, |
| expect: "echo $ > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| |
| { |
| 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" is not in srcs, out, tools or tool_files.`, |
| }, |
| { |
| name: "error locations", |
| prop: ` |
| out: ["out"], |
| cmd: "echo foo > $(locations missing)", |
| `, |
| err: `unknown locations label "missing" is not in srcs, out, tools or tool_files`, |
| }, |
| { |
| 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", |
| moduleName: "depfile_allowed_for_test", |
| 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_SANDBOX_DIR__/out/out", |
| }, |
| { |
| name: "tool allow missing dependencies", |
| prop: ` |
| tools: [":missing"], |
| out: ["out"], |
| cmd: "$(location :missing) > $(out)", |
| `, |
| |
| allowMissingDependencies: true, |
| |
| expect: "'***missing tool :missing***' > __SBOX_SANDBOX_DIR__/out/out", |
| }, |
| } |
| |
| for _, test := range testcases { |
| t.Run(test.name, func(t *testing.T) { |
| moduleName := "gen" |
| if test.moduleName != "" { |
| moduleName = test.moduleName |
| } |
| bp := fmt.Sprintf(` |
| genrule { |
| name: "%s", |
| %s |
| }`, moduleName, test.prop) |
| var expectedErrors []string |
| if test.err != "" { |
| expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err)) |
| } |
| |
| result := android.GroupFixturePreparers( |
| prepareForGenRuleTest, |
| android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { |
| variables.Allow_missing_dependencies = proptools.BoolPtr(test.allowMissingDependencies) |
| }), |
| android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { |
| variables.GenruleSandboxing = proptools.BoolPtr(true) |
| }), |
| android.FixtureModifyContext(func(ctx *android.TestContext) { |
| ctx.SetAllowMissingDependencies(test.allowMissingDependencies) |
| }), |
| ). |
| ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)). |
| RunTestWithBp(t, testGenruleBp()+bp) |
| |
| if expectedErrors != nil { |
| return |
| } |
| |
| gen := result.Module(moduleName, "").(*Module) |
| android.AssertStringEquals(t, "raw commands", test.expect, gen.rawCommands[0]) |
| }) |
| } |
| } |
| |
| 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 -en 'in1.txt\nin2.txt' | sha256sum |
| expectedHash: "18da75b9b1cc74b09e365b4ca2e321b5d618f438cc632b387ad9dc2ab4b20e9d", |
| }, |
| { |
| name: "hash1", |
| // sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum |
| expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45", |
| }, |
| { |
| name: "hash2", |
| // sha256 value obtained from: echo -en 'in1.txt\nin2.txt\nin3.txt' | sha256sum |
| expectedHash: "a38d432a4b19df93140e1f1fe26c97ff0387dae01fe506412b47208f0595fb45", |
| }, |
| } |
| |
| result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp) |
| |
| for _, test := range testcases { |
| t.Run(test.name, func(t *testing.T) { |
| gen := result.ModuleForTests(test.name, "") |
| manifest := android.RuleBuilderSboxProtoForTests(t, result.TestContext, gen.Output("genrule.sbox.textproto")) |
| hash := manifest.Commands[0].GetInputHash() |
| |
| android.AssertStringEquals(t, "hash", test.expectedHash, hash) |
| }) |
| } |
| } |
| |
| func TestGenSrcs(t *testing.T) { |
| testcases := []struct { |
| name string |
| prop string |
| |
| allowMissingDependencies bool |
| |
| err string |
| cmds []string |
| deps []string |
| files []string |
| shards int |
| inputs []string |
| }{ |
| { |
| name: "gensrcs", |
| prop: ` |
| tools: ["tool"], |
| srcs: ["in1.txt", "in2.txt"], |
| cmd: "$(location) $(in) > $(out)", |
| `, |
| cmds: []string{ |
| "bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'", |
| }, |
| deps: []string{ |
| "out/soong/.intermediates/gen/gen/gensrcs/in1.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in2.h", |
| }, |
| files: []string{ |
| "out/soong/.intermediates/gen/gen/gensrcs/in1.h", |
| "out/soong/.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 '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt > __SBOX_SANDBOX_DIR__/out/in2.h'", |
| "bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in3.txt > __SBOX_SANDBOX_DIR__/out/in3.h'", |
| }, |
| deps: []string{ |
| "out/soong/.intermediates/gen/gen/gensrcs/in1.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in2.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in3.h", |
| }, |
| files: []string{ |
| "out/soong/.intermediates/gen/gen/gensrcs/in1.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in2.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in3.h", |
| }, |
| }, |
| { |
| name: "data", |
| prop: ` |
| tools: ["tool"], |
| srcs: ["in1.txt", "in2.txt", "in3.txt"], |
| cmd: "$(location) $(in) --extra_input=$(location baz.txt) > $(out)", |
| data: ["baz.txt"], |
| shard_size: 2, |
| `, |
| cmds: []string{ |
| "bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in1.txt --extra_input=baz.txt > __SBOX_SANDBOX_DIR__/out/in1.h' && bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in2.txt --extra_input=baz.txt > __SBOX_SANDBOX_DIR__/out/in2.h'", |
| "bash -c '__SBOX_SANDBOX_DIR__/tools/out/bin/tool in3.txt --extra_input=baz.txt > __SBOX_SANDBOX_DIR__/out/in3.h'", |
| }, |
| deps: []string{ |
| "out/soong/.intermediates/gen/gen/gensrcs/in1.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in2.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in3.h", |
| }, |
| files: []string{ |
| "out/soong/.intermediates/gen/gen/gensrcs/in1.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in2.h", |
| "out/soong/.intermediates/gen/gen/gensrcs/in3.h", |
| }, |
| shards: 2, |
| inputs: []string{ |
| "baz.txt", |
| }, |
| }, |
| } |
| |
| checkInputs := func(t *testing.T, rule android.TestingBuildParams, inputs []string) { |
| t.Helper() |
| if len(inputs) == 0 { |
| return |
| } |
| inputBaseNames := map[string]bool{} |
| for _, f := range rule.Implicits { |
| inputBaseNames[f.Base()] = true |
| } |
| for _, f := range inputs { |
| if _, ok := inputBaseNames[f]; !ok { |
| t.Errorf("Expected to find input file %q for %q, but did not", f, rule.Description) |
| } |
| } |
| } |
| |
| 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" |
| |
| var expectedErrors []string |
| if test.err != "" { |
| expectedErrors = append(expectedErrors, regexp.QuoteMeta(test.err)) |
| } |
| |
| result := prepareForGenRuleTest. |
| ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)). |
| RunTestWithBp(t, testGenruleBp()+bp) |
| |
| mod := result.ModuleForTests("gen", "") |
| if expectedErrors != nil { |
| return |
| } |
| |
| if test.shards > 0 { |
| for i := 0; i < test.shards; i++ { |
| r := mod.Rule("generator" + strconv.Itoa(i)) |
| checkInputs(t, r, test.inputs) |
| } |
| } else { |
| r := mod.Rule("generator") |
| checkInputs(t, r, test.inputs) |
| } |
| |
| gen := result.Module("gen", "").(*Module) |
| android.AssertDeepEquals(t, "cmd", test.cmds, gen.rawCommands) |
| |
| android.AssertPathsRelativeToTopEquals(t, "deps", test.deps, gen.outputDeps) |
| |
| android.AssertPathsRelativeToTopEquals(t, "files", test.files, gen.outputFiles) |
| }) |
| } |
| } |
| |
| func TestGenruleAllowlistingDepfile(t *testing.T) { |
| tests := []struct { |
| name string |
| prop string |
| err string |
| moduleName string |
| }{ |
| { |
| name: `error when module is not allowlisted`, |
| prop: ` |
| depfile: true, |
| cmd: "cat $(in) > $(out) && cat $(depfile)", |
| `, |
| err: "depfile: Deprecated because with genrule sandboxing, dependencies must be known before the action is run in order to add them to the sandbox", |
| }, |
| { |
| name: `no error when module is allowlisted`, |
| prop: ` |
| depfile: true, |
| cmd: "cat $(in) > $(out) && cat $(depfile)", |
| `, |
| moduleName: `depfile_allowed_for_test`, |
| }, |
| } |
| for _, test := range tests { |
| t.Run(test.name, func(t *testing.T) { |
| moduleName := "foo" |
| if test.moduleName != "" { |
| moduleName = test.moduleName |
| } |
| bp := fmt.Sprintf(` |
| gensrcs { |
| name: "%s", |
| srcs: ["data.txt"], |
| %s |
| }`, moduleName, test.prop) |
| |
| var expectedErrors []string |
| if test.err != "" { |
| expectedErrors = append(expectedErrors, test.err) |
| } |
| android.GroupFixturePreparers( |
| prepareForGenRuleTest, |
| android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) { |
| variables.GenruleSandboxing = proptools.BoolPtr(true) |
| }), |
| ). |
| ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)). |
| RunTestWithBp(t, bp) |
| }) |
| |
| } |
| } |
| |
| 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"], |
| } |
| ` |
| |
| result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp) |
| |
| gen := result.Module("gen", "").(*Module) |
| |
| expectedCmd := "cp in1 __SBOX_SANDBOX_DIR__/out/out" |
| android.AssertStringEquals(t, "cmd", expectedCmd, gen.rawCommands[0]) |
| |
| expectedSrcs := []string{"in1"} |
| android.AssertDeepEquals(t, "srcs", expectedSrcs, gen.properties.Srcs) |
| } |
| |
| func TestGenruleAllowMissingDependencies(t *testing.T) { |
| bp := ` |
| output { |
| name: "disabled", |
| enabled: false, |
| } |
| |
| genrule { |
| name: "gen", |
| srcs: [ |
| ":disabled", |
| ], |
| out: ["out"], |
| cmd: "cat $(in) > $(out)", |
| } |
| ` |
| result := android.GroupFixturePreparers( |
| prepareForGenRuleTest, |
| android.FixtureModifyConfigAndContext( |
| func(config android.Config, ctx *android.TestContext) { |
| config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(true) |
| ctx.SetAllowMissingDependencies(true) |
| })).RunTestWithBp(t, bp) |
| |
| gen := result.ModuleForTests("gen", "").Output("out") |
| if gen.Rule != android.ErrorRule { |
| t.Errorf("Expected missing dependency error rule for gen, got %q", gen.Rule.String()) |
| } |
| } |
| |
| func TestGenruleOutputFiles(t *testing.T) { |
| bp := ` |
| genrule { |
| name: "gen", |
| out: ["foo", "sub/bar"], |
| cmd: "echo foo > $(location foo) && echo bar > $(location sub/bar)", |
| } |
| use_source { |
| name: "gen_foo", |
| srcs: [":gen{foo}"], |
| } |
| use_source { |
| name: "gen_bar", |
| srcs: [":gen{sub/bar}"], |
| } |
| use_source { |
| name: "gen_all", |
| srcs: [":gen"], |
| } |
| ` |
| |
| result := prepareForGenRuleTest.RunTestWithBp(t, testGenruleBp()+bp) |
| android.AssertPathsRelativeToTopEquals(t, |
| "genrule.tag with output", |
| []string{"out/soong/.intermediates/gen/gen/foo"}, |
| result.ModuleForTests("gen_foo", "").Module().(*useSource).srcs) |
| android.AssertPathsRelativeToTopEquals(t, |
| "genrule.tag with output in subdir", |
| []string{"out/soong/.intermediates/gen/gen/sub/bar"}, |
| result.ModuleForTests("gen_bar", "").Module().(*useSource).srcs) |
| android.AssertPathsRelativeToTopEquals(t, |
| "genrule.tag with all", |
| []string{"out/soong/.intermediates/gen/gen/foo", "out/soong/.intermediates/gen/gen/sub/bar"}, |
| result.ModuleForTests("gen_all", "").Module().(*useSource).srcs) |
| } |
| |
| func TestGenruleInterface(t *testing.T) { |
| result := android.GroupFixturePreparers( |
| prepareForGenRuleTest, |
| android.FixtureMergeMockFs(android.MockFS{ |
| "package-dir/Android.bp": []byte(` |
| genrule { |
| name: "module-name", |
| cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)", |
| srcs: [ |
| "src/foo.proto", |
| ], |
| out: ["proto.h", "bar/proto.h"], |
| export_include_dirs: [".", "bar"], |
| } |
| `), |
| }), |
| ).RunTest(t) |
| |
| exportedIncludeDirs := []string{ |
| "out/soong/.intermediates/package-dir/module-name/gen/package-dir", |
| "out/soong/.intermediates/package-dir/module-name/gen", |
| "out/soong/.intermediates/package-dir/module-name/gen/package-dir/bar", |
| "out/soong/.intermediates/package-dir/module-name/gen/bar", |
| } |
| gen := result.Module("module-name", "").(*Module) |
| |
| android.AssertPathsRelativeToTopEquals( |
| t, |
| "include path", |
| exportedIncludeDirs, |
| gen.GeneratedHeaderDirs(), |
| ) |
| android.AssertPathsRelativeToTopEquals( |
| t, |
| "files", |
| []string{ |
| "out/soong/.intermediates/package-dir/module-name/gen/proto.h", |
| "out/soong/.intermediates/package-dir/module-name/gen/bar/proto.h", |
| }, |
| gen.GeneratedSourceFiles(), |
| ) |
| } |
| |
| func TestGenSrcsWithNonRootAndroidBpOutputFiles(t *testing.T) { |
| result := android.GroupFixturePreparers( |
| prepareForGenRuleTest, |
| android.FixtureMergeMockFs(android.MockFS{ |
| "external-protos/path/Android.bp": []byte(` |
| filegroup { |
| name: "external-protos", |
| srcs: ["baz/baz.proto", "bar.proto"], |
| } |
| `), |
| "package-dir/Android.bp": []byte(` |
| gensrcs { |
| name: "module-name", |
| cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)", |
| srcs: [ |
| "src/foo.proto", |
| ":external-protos", |
| ], |
| output_extension: "proto.h", |
| } |
| `), |
| }), |
| ).RunTest(t) |
| |
| exportedIncludeDir := "out/soong/.intermediates/package-dir/module-name/gen/gensrcs" |
| gen := result.Module("module-name", "").(*Module) |
| |
| android.AssertPathsRelativeToTopEquals( |
| t, |
| "include path", |
| []string{exportedIncludeDir}, |
| gen.exportedIncludeDirs, |
| ) |
| android.AssertPathsRelativeToTopEquals( |
| t, |
| "files", |
| []string{ |
| exportedIncludeDir + "/package-dir/src/foo.proto.h", |
| exportedIncludeDir + "/external-protos/path/baz/baz.proto.h", |
| exportedIncludeDir + "/external-protos/path/bar.proto.h", |
| }, |
| gen.outputFiles, |
| ) |
| } |
| |
| func TestGenSrcsWithSrcsFromExternalPackage(t *testing.T) { |
| bp := ` |
| gensrcs { |
| name: "module-name", |
| cmd: "mkdir -p $(genDir) && cat $(in) >> $(genDir)/$(out)", |
| srcs: [ |
| ":external-protos", |
| ], |
| output_extension: "proto.h", |
| } |
| ` |
| result := android.GroupFixturePreparers( |
| prepareForGenRuleTest, |
| android.FixtureMergeMockFs(android.MockFS{ |
| "external-protos/path/Android.bp": []byte(` |
| filegroup { |
| name: "external-protos", |
| srcs: ["foo/foo.proto", "bar.proto"], |
| } |
| `), |
| }), |
| ).RunTestWithBp(t, bp) |
| |
| exportedIncludeDir := "out/soong/.intermediates/module-name/gen/gensrcs" |
| gen := result.Module("module-name", "").(*Module) |
| |
| android.AssertPathsRelativeToTopEquals( |
| t, |
| "include path", |
| []string{exportedIncludeDir}, |
| gen.exportedIncludeDirs, |
| ) |
| android.AssertPathsRelativeToTopEquals( |
| t, |
| "files", |
| []string{ |
| exportedIncludeDir + "/external-protos/path/foo/foo.proto.h", |
| exportedIncludeDir + "/external-protos/path/bar.proto.h", |
| }, |
| gen.outputFiles, |
| ) |
| } |
| |
| func TestPrebuiltTool(t *testing.T) { |
| testcases := []struct { |
| name string |
| bp string |
| expectedToolName string |
| }{ |
| { |
| name: "source only", |
| bp: ` |
| tool { name: "tool" } |
| `, |
| expectedToolName: "bin/tool", |
| }, |
| { |
| name: "prebuilt only", |
| bp: ` |
| prebuilt_tool { name: "tool" } |
| `, |
| expectedToolName: "prebuilt_bin/tool", |
| }, |
| { |
| name: "source preferred", |
| bp: ` |
| tool { name: "tool" } |
| prebuilt_tool { name: "tool" } |
| `, |
| expectedToolName: "bin/tool", |
| }, |
| { |
| name: "prebuilt preferred", |
| bp: ` |
| tool { name: "tool" } |
| prebuilt_tool { name: "tool", prefer: true } |
| `, |
| expectedToolName: "prebuilt_bin/prebuilt_tool", |
| }, |
| { |
| name: "source disabled", |
| bp: ` |
| tool { name: "tool", enabled: false } |
| prebuilt_tool { name: "tool" } |
| `, |
| expectedToolName: "prebuilt_bin/prebuilt_tool", |
| }, |
| } |
| |
| for _, test := range testcases { |
| t.Run(test.name, func(t *testing.T) { |
| result := prepareForGenRuleTest.RunTestWithBp(t, test.bp+` |
| genrule { |
| name: "gen", |
| tools: ["tool"], |
| out: ["foo"], |
| cmd: "$(location tool)", |
| } |
| `) |
| gen := result.Module("gen", "").(*Module) |
| expectedCmd := "__SBOX_SANDBOX_DIR__/tools/out/" + test.expectedToolName |
| android.AssertStringEquals(t, "command", expectedCmd, gen.rawCommands[0]) |
| }) |
| } |
| } |
| |
| func TestGenruleWithGlobPaths(t *testing.T) { |
| testcases := []struct { |
| name string |
| bp string |
| additionalFiles android.MockFS |
| expectedCmd string |
| }{ |
| { |
| name: "single file in directory with $ sign", |
| bp: ` |
| genrule { |
| name: "gen", |
| srcs: ["inn*.txt"], |
| out: ["out.txt"], |
| cmd: "cp $(in) $(out)", |
| } |
| `, |
| additionalFiles: android.MockFS{"inn$1.txt": nil}, |
| expectedCmd: "cp 'inn$1.txt' __SBOX_SANDBOX_DIR__/out/out.txt", |
| }, |
| { |
| name: "multiple file in directory with $ sign", |
| bp: ` |
| genrule { |
| name: "gen", |
| srcs: ["inn*.txt"], |
| out: ["."], |
| cmd: "cp $(in) $(out)", |
| } |
| `, |
| additionalFiles: android.MockFS{"inn$1.txt": nil, "inn$2.txt": nil}, |
| expectedCmd: "cp 'inn$1.txt' 'inn$2.txt' __SBOX_SANDBOX_DIR__/out", |
| }, |
| { |
| name: "file in directory with other shell unsafe character", |
| bp: ` |
| genrule { |
| name: "gen", |
| srcs: ["inn*.txt"], |
| out: ["out.txt"], |
| cmd: "cp $(in) $(out)", |
| } |
| `, |
| additionalFiles: android.MockFS{"inn@1.txt": nil}, |
| expectedCmd: "cp 'inn@1.txt' __SBOX_SANDBOX_DIR__/out/out.txt", |
| }, |
| { |
| name: "glob location param with filepath containing $", |
| bp: ` |
| genrule { |
| name: "gen", |
| srcs: ["**/inn*"], |
| out: ["."], |
| cmd: "cp $(in) $(location **/inn*)", |
| } |
| `, |
| additionalFiles: android.MockFS{"a/inn$1.txt": nil}, |
| expectedCmd: "cp 'a/inn$1.txt' 'a/inn$1.txt'", |
| }, |
| { |
| name: "glob locations param with filepath containing $", |
| bp: ` |
| genrule { |
| name: "gen", |
| tool_files: ["**/inn*"], |
| out: ["out.txt"], |
| cmd: "cp $(locations **/inn*) $(out)", |
| } |
| `, |
| additionalFiles: android.MockFS{"a/inn$1.txt": nil}, |
| expectedCmd: "cp '__SBOX_SANDBOX_DIR__/tools/src/a/inn$1.txt' __SBOX_SANDBOX_DIR__/out/out.txt", |
| }, |
| } |
| |
| for _, test := range testcases { |
| t.Run(test.name, func(t *testing.T) { |
| result := android.GroupFixturePreparers( |
| prepareForGenRuleTest, |
| android.FixtureMergeMockFs(test.additionalFiles), |
| ).RunTestWithBp(t, test.bp) |
| gen := result.Module("gen", "").(*Module) |
| android.AssertStringEquals(t, "command", test.expectedCmd, gen.rawCommands[0]) |
| }) |
| } |
| } |
| |
| 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 = ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName())) |
| } |
| |
| func (t *testTool) HostToolPath() android.OptionalPath { |
| return android.OptionalPathForPath(t.outputFile) |
| } |
| |
| type prebuiltTestTool struct { |
| android.ModuleBase |
| prebuilt android.Prebuilt |
| testTool |
| } |
| |
| func (p *prebuiltTestTool) Name() string { |
| return p.prebuilt.Name(p.ModuleBase.Name()) |
| } |
| |
| func (p *prebuiltTestTool) Prebuilt() *android.Prebuilt { |
| return &p.prebuilt |
| } |
| |
| func (t *prebuiltTestTool) GenerateAndroidBuildActions(ctx android.ModuleContext) { |
| t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "prebuilt_bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName())) |
| } |
| |
| func prebuiltToolFactory() android.Module { |
| module := &prebuiltTestTool{} |
| android.InitPrebuiltModuleWithoutSrcs(module) |
| android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) |
| return module |
| } |
| |
| var _ android.HostToolProvider = (*testTool)(nil) |
| var _ android.HostToolProvider = (*prebuiltTestTool)(nil) |
| |
| type testOutputProducer struct { |
| android.ModuleBase |
| outputFile android.Path |
| } |
| |
| func outputProducerFactory() android.Module { |
| module := &testOutputProducer{} |
| android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst) |
| return module |
| } |
| |
| func (t *testOutputProducer) GenerateAndroidBuildActions(ctx android.ModuleContext) { |
| t.outputFile = ctx.InstallFile(android.PathForModuleInstall(ctx, "bin"), ctx.ModuleName(), android.PathForOutput(ctx, ctx.ModuleName())) |
| } |
| |
| func (t *testOutputProducer) OutputFiles(tag string) (android.Paths, error) { |
| return android.Paths{t.outputFile}, nil |
| } |
| |
| var _ android.OutputFileProducer = (*testOutputProducer)(nil) |
| |
| type useSource struct { |
| android.ModuleBase |
| props struct { |
| Srcs []string `android:"path"` |
| } |
| srcs android.Paths |
| } |
| |
| func (s *useSource) GenerateAndroidBuildActions(ctx android.ModuleContext) { |
| s.srcs = android.PathsForModuleSrc(ctx, s.props.Srcs) |
| } |
| |
| func useSourceFactory() android.Module { |
| module := &useSource{} |
| module.AddProperties(&module.props) |
| android.InitAndroidModule(module) |
| return module |
| } |