platform_build_soong/genrule/genrule_test.go
Colin Cross 98be1bb00f Move filesystem into Config
The filesystem object was available through ModuleContext.Fs(), but
gives too much access to the filesystem without enforicing correct
dependencies.  In order to support sandboxing the soong_build
process move the filesystem into the Config.  The next change will
make it private.

Bug: 146437378
Test: all Soong tests
Change-Id: I5d3ae9108f120fd335b21efd612aefa078378813
2019-12-18 08:19:10 -08:00

656 lines
14 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 (
"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("genrule", GenRuleFactory)
ctx.RegisterModuleType("gensrcs", GenSrcsFactory)
ctx.RegisterModuleType("genrule_defaults", defaultsFactory)
ctx.RegisterModuleType("tool", toolFactory)
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 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)