platform_build_soong/genrule/genrule_test.go
Vinh Tran 370e08c1af Add more Soong unit test coverage for gensrcs
When gensrcs's srcs prop is set with filegroup from external package and especially when gensrcs generates cpp headers, it's not trivial to know what the output paths and their correponding include paths look like.

In mixed build, there are a few bugs in gensrcs's include paths for cpp headers (b/248555356 and b/248554581) that we'll eventually need to fix. These tests serve as an documentation of the existing behavior when gensrcs generate cpp headers.

Test: go test
Bug: 248555356
Change-Id: I10168dd4229be8f110a31955d214ef792c8050de
2022-09-25 20:21:17 -04:00

1140 lines
28 KiB
Go

// 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"
"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
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",
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",
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) {
bp := "genrule {\n"
bp += "name: \"gen\",\n"
bp += test.prop
bp += "}\n"
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.FixtureModifyContext(func(ctx *android.TestContext) {
ctx.SetAllowMissingDependencies(test.allowMissingDependencies)
}),
).
ExtendWithErrorHandler(android.FixtureExpectsAllErrorsToMatchAPattern(expectedErrors)).
RunTestWithBp(t, testGenruleBp()+bp)
if expectedErrors != nil {
return
}
gen := result.Module("gen", "").(*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, 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
}{
{
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",
},
},
}
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)
if expectedErrors != nil {
return
}
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 TestGensrcsBuildBrokenDepfile(t *testing.T) {
tests := []struct {
name string
prop string
BuildBrokenDepfile *bool
err string
}{
{
name: `error when BuildBrokenDepfile is set to false`,
prop: `
depfile: true,
cmd: "cat $(in) > $(out) && cat $(depfile)",
`,
BuildBrokenDepfile: proptools.BoolPtr(false),
err: "depfile: Deprecated to ensure the module type is convertible to Bazel",
},
{
name: `error when BuildBrokenDepfile is not set`,
prop: `
depfile: true,
cmd: "cat $(in) > $(out) && cat $(depfile)",
`,
err: "depfile: Deprecated to ensure the module type is convertible to Bazel.",
},
{
name: `no error when BuildBrokenDepfile is explicitly set to true`,
prop: `
depfile: true,
cmd: "cat $(in) > $(out) && cat $(depfile)",
`,
BuildBrokenDepfile: proptools.BoolPtr(true),
},
{
name: `no error if depfile is not set`,
prop: `
cmd: "cat $(in) > $(out)",
`,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
bp := fmt.Sprintf(`
gensrcs {
name: "foo",
srcs: ["data.txt"],
%s
}`, test.prop)
var expectedErrors []string
if test.err != "" {
expectedErrors = append(expectedErrors, test.err)
}
android.GroupFixturePreparers(
prepareForGenRuleTest,
android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
if test.BuildBrokenDepfile != nil {
variables.BuildBrokenDepfile = test.BuildBrokenDepfile
}
}),
).
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 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 TestGenruleWithBazel(t *testing.T) {
bp := `
genrule {
name: "foo",
out: ["one.txt", "two.txt"],
bazel_module: { label: "//foo/bar:bar" },
}
`
result := android.GroupFixturePreparers(
prepareForGenRuleTest, android.FixtureModifyConfig(func(config android.Config) {
config.BazelContext = android.MockBazelContext{
OutputBaseDir: "outputbase",
LabelToOutputFiles: map[string][]string{
"//foo/bar:bar": []string{"bazelone.txt", "bazeltwo.txt"}}}
})).RunTestWithBp(t, testGenruleBp()+bp)
gen := result.Module("foo", "").(*Module)
expectedOutputFiles := []string{"outputbase/execroot/__main__/bazelone.txt",
"outputbase/execroot/__main__/bazeltwo.txt"}
android.AssertDeepEquals(t, "output files", expectedOutputFiles, gen.outputFiles.Strings())
android.AssertDeepEquals(t, "output deps", expectedOutputFiles, gen.outputDeps.Strings())
}
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
}