From 2a0769264397c07693c2948d6df98fc983d8982b Mon Sep 17 00:00:00 2001 From: Colin Cross Date: Thu, 4 Oct 2018 23:28:25 -0700 Subject: [PATCH] Add tests for genrule command expansion Add tests for expanding variables in a genrule cmd property. Test: genrule_test.go Change-Id: I8288b8616d518bb5f24a892c4e59f68d95055d0a --- Android.bp | 3 + android/config.go | 2 + genrule/genrule.go | 6 +- genrule/genrule_test.go | 374 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 genrule/genrule_test.go diff --git a/Android.bp b/Android.bp index ac50166e1..ccab727a2 100644 --- a/Android.bp +++ b/Android.bp @@ -192,6 +192,9 @@ bootstrap_go_package { srcs: [ "genrule/genrule.go", ], + testSrcs: [ + "genrule/genrule_test.go", + ], pluginFor: ["soong_build"], } diff --git a/android/config.go b/android/config.go index db5d0046a..4b10552ab 100644 --- a/android/config.go +++ b/android/config.go @@ -241,6 +241,8 @@ func TestArchConfig(buildDir string, env map[string]string) Config { }, } + config.BuildOsVariant = config.Targets[Host][0].String() + return testConfig } diff --git a/genrule/genrule.go b/genrule/genrule.go index e3823c5c1..f19e2aa01 100644 --- a/genrule/genrule.go +++ b/genrule/genrule.go @@ -102,8 +102,9 @@ type Module struct { taskGenerator taskFunc - deps android.Paths - rule blueprint.Rule + deps android.Paths + rule blueprint.Rule + rawCommand string exportedIncludeDirs android.Paths @@ -287,6 +288,7 @@ func (g *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) { genDir := android.PathForModuleGen(ctx) // Escape the command for the shell rawCommand = "'" + strings.Replace(rawCommand, "'", `'\''`, -1) + "'" + g.rawCommand = rawCommand sandboxCommand := fmt.Sprintf("$sboxCmd --sandbox-path %s --output-root %s -c %s %s $allouts", sandboxPath, genDir, rawCommand, depfilePlaceholder) diff --git a/genrule/genrule_test.go b/genrule/genrule_test.go new file mode 100644 index 000000000..0d690d428 --- /dev/null +++ b/genrule/genrule_test.go @@ -0,0 +1,374 @@ +// 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" + "strings" + "testing" + + "android/soong/android" +) + +var buildDir string + +func setUp() { + var err error + buildDir, err = ioutil.TempDir("", "soong_java_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, bp string, + fs map[string][]byte) *android.TestContext { + + ctx := android.NewTestArchContext() + ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(android.FileGroupFactory)) + ctx.RegisterModuleType("genrule", android.ModuleFactoryAdaptor(GenRuleFactory)) + ctx.RegisterModuleType("tool", android.ModuleFactoryAdaptor(toolFactory)) + ctx.Register() + + 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{ + "Android.bp": []byte(bp), + "tool": nil, + "tool_file1": nil, + "tool_file2": nil, + "in1": nil, + "in2": nil, + } + + for k, v := range fs { + mockFS[k] = v + } + + ctx.MockFileSystem(mockFS) + + return ctx +} + +func TestGenruleCmd(t *testing.T) { + testcases := []struct { + name string + prop string + + 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 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: "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 tool_file1) > $(out)", + `, + expect: "tool_file1 > __SBOX_OUT_FILES__", + }, + { + name: "tool files", + prop: ` + tool_files: [":tool_files"], + out: ["out"], + cmd: "$(location tool_file1) $(location tool_file2) > $(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: "outs", + prop: ` + out: ["out", "out2"], + cmd: "echo foo > $(out)", + `, + expect: "echo foo > __SBOX_OUT_FILES__", + }, + { + 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 location", + prop: ` + out: ["out"], + cmd: "echo foo > $(location missing)", + `, + err: `unknown location label "missing"`, + }, + { + 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", + }, + } + + for _, test := range testcases { + t.Run(test.name, func(t *testing.T) { + config := android.TestArchConfig(buildDir, nil) + bp := "genrule {\n" + bp += "name: \"gen\",\n" + bp += test.prop + bp += "}\n" + + ctx := testContext(config, bp, nil) + + _, 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 gen.rawCommand != "'"+test.expect+"'" { + t.Errorf("want %q, got %q", test.expect, gen.rawCommand) + } + }) + } + +} + +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) DepsMutator(ctx android.BottomUpMutatorContext) {} + +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 _ HostToolProvider = (*testTool)(nil)