657 lines
14 KiB
Go
657 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)
|