// Copyright 2020 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 bazel import ( "fmt" "reflect" "testing" ) func TestAqueryMultiArchGenrule(t *testing.T) { // This input string is retrieved from a real build of bionic-related genrules. const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 6 }, { "id": 3, "pathFragmentId": 8 }, { "id": 4, "pathFragmentId": 12 }, { "id": 5, "pathFragmentId": 19 }, { "id": 6, "pathFragmentId": 20 }, { "id": 7, "pathFragmentId": 21 }], "actions": [{ "targetId": 1, "actionKey": "ab53f6ecbdc2ee8cb8812613b63205464f1f5083f6dca87081a0a398c0f1ecf7", "mnemonic": "Genrule", "configurationId": 1, "arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm.S"], "environmentVariables": [{ "key": "PATH", "value": "/bin:/usr/bin:/usr/local/bin" }], "inputDepSetIds": [1], "outputIds": [4], "primaryOutputId": 4 }, { "targetId": 2, "actionKey": "9f4309ce165dac458498cb92811c18b0b7919782cc37b82a42d2141b8cc90826", "mnemonic": "Genrule", "configurationId": 1, "arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86.S"], "environmentVariables": [{ "key": "PATH", "value": "/bin:/usr/bin:/usr/local/bin" }], "inputDepSetIds": [2], "outputIds": [5], "primaryOutputId": 5 }, { "targetId": 3, "actionKey": "50d6c586103ebeed3a218195540bcc30d329464eae36377eb82f8ce7c36ac342", "mnemonic": "Genrule", "configurationId": 1, "arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py x86_64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-x86_64.S"], "environmentVariables": [{ "key": "PATH", "value": "/bin:/usr/bin:/usr/local/bin" }], "inputDepSetIds": [3], "outputIds": [6], "primaryOutputId": 6 }, { "targetId": 4, "actionKey": "f30cbe442f5216f4223cf16a39112cad4ec56f31f49290d85cff587e48647ffa", "mnemonic": "Genrule", "configurationId": 1, "arguments": ["/bin/bash", "-c", "source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py arm64 ../sourceroot/bionic/libc/SYSCALLS.TXT \u003e bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-arm64.S"], "environmentVariables": [{ "key": "PATH", "value": "/bin:/usr/bin:/usr/local/bin" }], "inputDepSetIds": [4], "outputIds": [7], "primaryOutputId": 7 }], "targets": [{ "id": 1, "label": "@sourceroot//bionic/libc:syscalls-arm", "ruleClassId": 1 }, { "id": 2, "label": "@sourceroot//bionic/libc:syscalls-x86", "ruleClassId": 1 }, { "id": 3, "label": "@sourceroot//bionic/libc:syscalls-x86_64", "ruleClassId": 1 }, { "id": 4, "label": "@sourceroot//bionic/libc:syscalls-arm64", "ruleClassId": 1 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1, 2, 3] }, { "id": 2, "directArtifactIds": [1, 2, 3] }, { "id": 3, "directArtifactIds": [1, 2, 3] }, { "id": 4, "directArtifactIds": [1, 2, 3] }], "configuration": [{ "id": 1, "mnemonic": "k8-fastbuild", "platformName": "k8", "checksum": "485c362832c178e367d972177f68e69e0981e51e67ef1c160944473db53fe046" }], "ruleClasses": [{ "id": 1, "name": "genrule" }], "pathFragments": [{ "id": 5, "label": ".." }, { "id": 4, "label": "sourceroot", "parentId": 5 }, { "id": 3, "label": "bionic", "parentId": 4 }, { "id": 2, "label": "libc", "parentId": 3 }, { "id": 1, "label": "SYSCALLS.TXT", "parentId": 2 }, { "id": 7, "label": "tools", "parentId": 2 }, { "id": 6, "label": "gensyscalls.py", "parentId": 7 }, { "id": 11, "label": "bazel_tools", "parentId": 5 }, { "id": 10, "label": "tools", "parentId": 11 }, { "id": 9, "label": "genrule", "parentId": 10 }, { "id": 8, "label": "genrule-setup.sh", "parentId": 9 }, { "id": 18, "label": "bazel-out" }, { "id": 17, "label": "sourceroot", "parentId": 18 }, { "id": 16, "label": "k8-fastbuild", "parentId": 17 }, { "id": 15, "label": "bin", "parentId": 16 }, { "id": 14, "label": "bionic", "parentId": 15 }, { "id": 13, "label": "libc", "parentId": 14 }, { "id": 12, "label": "syscalls-arm.S", "parentId": 13 }, { "id": 19, "label": "syscalls-x86.S", "parentId": 13 }, { "id": 20, "label": "syscalls-x86_64.S", "parentId": 13 }, { "id": 21, "label": "syscalls-arm64.S", "parentId": 13 }] }` actualbuildStatements, _ := AqueryBuildStatements([]byte(inputString)) expectedBuildStatements := []BuildStatement{} for _, arch := range []string{"arm", "arm64", "x86", "x86_64"} { expectedBuildStatements = append(expectedBuildStatements, BuildStatement{ Command: fmt.Sprintf( "/bin/bash -c 'source ../bazel_tools/tools/genrule/genrule-setup.sh; ../sourceroot/bionic/libc/tools/gensyscalls.py %s ../sourceroot/bionic/libc/SYSCALLS.TXT > bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S'", arch, arch), OutputPaths: []string{ fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/bionic/libc/syscalls-%s.S", arch), }, InputPaths: []string{ "../sourceroot/bionic/libc/SYSCALLS.TXT", "../sourceroot/bionic/libc/tools/gensyscalls.py", "../bazel_tools/tools/genrule/genrule-setup.sh", }, Env: []KeyValuePair{ KeyValuePair{Key: "PATH", Value: "/bin:/usr/bin:/usr/local/bin"}, }, Mnemonic: "Genrule", }) } assertBuildStatements(t, expectedBuildStatements, actualbuildStatements) } func TestInvalidOutputId(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "x", "arguments": ["touch", "foo"], "inputDepSetIds": [1], "outputIds": [3], "primaryOutputId": 3 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1, 2] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "two" }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, "undefined outputId 3") } func TestInvalidInputDepsetId(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "x", "arguments": ["touch", "foo"], "inputDepSetIds": [2], "outputIds": [1], "primaryOutputId": 1 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1, 2] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "two" }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, "undefined input depsetId 2") } func TestInvalidInputArtifactId(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "x", "arguments": ["touch", "foo"], "inputDepSetIds": [1], "outputIds": [1], "primaryOutputId": 1 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1, 3] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "two" }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, "undefined input artifactId 3") } func TestInvalidPathFragmentId(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "x", "arguments": ["touch", "foo"], "inputDepSetIds": [1], "outputIds": [1], "primaryOutputId": 1 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1, 2] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "two", "parentId": 3 }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, "undefined path fragment id 3") } func TestDepfiles(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }, { "id": 3, "pathFragmentId": 3 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "x", "arguments": ["touch", "foo"], "inputDepSetIds": [1], "outputIds": [2, 3], "primaryOutputId": 2 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1, 2, 3] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "two" }, { "id": 3, "label": "two.d" }] }` actual, err := AqueryBuildStatements([]byte(inputString)) if err != nil { t.Errorf("Unexpected error %q", err) } if expected := 1; len(actual) != expected { t.Fatalf("Expected %d build statements, got %d", expected, len(actual)) } bs := actual[0] expectedDepfile := "two.d" if bs.Depfile == nil { t.Errorf("Expected depfile %q, but there was none found", expectedDepfile) } else if *bs.Depfile != expectedDepfile { t.Errorf("Expected depfile %q, but got %q", expectedDepfile, *bs.Depfile) } } func TestMultipleDepfiles(t *testing.T) { const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 2 }, { "id": 3, "pathFragmentId": 3 }, { "id": 4, "pathFragmentId": 4 }], "actions": [{ "targetId": 1, "actionKey": "x", "mnemonic": "x", "arguments": ["touch", "foo"], "inputDepSetIds": [1], "outputIds": [2,3,4], "primaryOutputId": 2 }], "depSetOfFiles": [{ "id": 1, "directArtifactIds": [1, 2, 3, 4] }], "pathFragments": [{ "id": 1, "label": "one" }, { "id": 2, "label": "two" }, { "id": 3, "label": "two.d" }, { "id": 4, "label": "other.d" }] }` _, err := AqueryBuildStatements([]byte(inputString)) assertError(t, err, `found multiple potential depfiles "two.d", "other.d"`) } func TestTransitiveInputDepsets(t *testing.T) { // The input aquery for this test comes from a proof-of-concept starlark rule which registers // a single action with many inputs given via a deep depset. const inputString = ` { "artifacts": [{ "id": 1, "pathFragmentId": 1 }, { "id": 2, "pathFragmentId": 7 }, { "id": 3, "pathFragmentId": 8 }, { "id": 4, "pathFragmentId": 9 }, { "id": 5, "pathFragmentId": 10 }, { "id": 6, "pathFragmentId": 11 }, { "id": 7, "pathFragmentId": 12 }, { "id": 8, "pathFragmentId": 13 }, { "id": 9, "pathFragmentId": 14 }, { "id": 10, "pathFragmentId": 15 }, { "id": 11, "pathFragmentId": 16 }, { "id": 12, "pathFragmentId": 17 }, { "id": 13, "pathFragmentId": 18 }, { "id": 14, "pathFragmentId": 19 }, { "id": 15, "pathFragmentId": 20 }, { "id": 16, "pathFragmentId": 21 }, { "id": 17, "pathFragmentId": 22 }, { "id": 18, "pathFragmentId": 23 }, { "id": 19, "pathFragmentId": 24 }, { "id": 20, "pathFragmentId": 25 }, { "id": 21, "pathFragmentId": 26 }], "actions": [{ "targetId": 1, "actionKey": "3b826d17fadbbbcd8313e456b90ec47c078c438088891dd45b4adbcd8889dc50", "mnemonic": "Action", "configurationId": 1, "arguments": ["/bin/bash", "-c", "touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"], "inputDepSetIds": [1], "outputIds": [21], "primaryOutputId": 21 }], "depSetOfFiles": [{ "id": 3, "directArtifactIds": [1, 2, 3, 4, 5] }, { "id": 4, "directArtifactIds": [6, 7, 8, 9, 10] }, { "id": 2, "transitiveDepSetIds": [3, 4], "directArtifactIds": [11, 12, 13, 14, 15] }, { "id": 5, "directArtifactIds": [16, 17, 18, 19] }, { "id": 1, "transitiveDepSetIds": [2, 5], "directArtifactIds": [20] }], "pathFragments": [{ "id": 6, "label": "bazel-out" }, { "id": 5, "label": "sourceroot", "parentId": 6 }, { "id": 4, "label": "k8-fastbuild", "parentId": 5 }, { "id": 3, "label": "bin", "parentId": 4 }, { "id": 2, "label": "testpkg", "parentId": 3 }, { "id": 1, "label": "test_1", "parentId": 2 }, { "id": 7, "label": "test_2", "parentId": 2 }, { "id": 8, "label": "test_3", "parentId": 2 }, { "id": 9, "label": "test_4", "parentId": 2 }, { "id": 10, "label": "test_5", "parentId": 2 }, { "id": 11, "label": "test_6", "parentId": 2 }, { "id": 12, "label": "test_7", "parentId": 2 }, { "id": 13, "label": "test_8", "parentId": 2 }, { "id": 14, "label": "test_9", "parentId": 2 }, { "id": 15, "label": "test_10", "parentId": 2 }, { "id": 16, "label": "test_11", "parentId": 2 }, { "id": 17, "label": "test_12", "parentId": 2 }, { "id": 18, "label": "test_13", "parentId": 2 }, { "id": 19, "label": "test_14", "parentId": 2 }, { "id": 20, "label": "test_15", "parentId": 2 }, { "id": 21, "label": "test_16", "parentId": 2 }, { "id": 22, "label": "test_17", "parentId": 2 }, { "id": 23, "label": "test_18", "parentId": 2 }, { "id": 24, "label": "test_19", "parentId": 2 }, { "id": 25, "label": "test_root", "parentId": 2 }, { "id": 26, "label": "test_out", "parentId": 2 }] }` actualbuildStatements, _ := AqueryBuildStatements([]byte(inputString)) // Inputs for the action are test_{i} from 1 to 20, and test_root. These inputs // are given via a deep depset, but the depset is flattened when returned as a // BuildStatement slice. inputPaths := []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_root"} for i := 1; i < 20; i++ { inputPaths = append(inputPaths, fmt.Sprintf("bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_%d", i)) } expectedBuildStatements := []BuildStatement{ BuildStatement{ Command: "/bin/bash -c touch bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out", OutputPaths: []string{"bazel-out/sourceroot/k8-fastbuild/bin/testpkg/test_out"}, InputPaths: inputPaths, Mnemonic: "Action", }, } assertBuildStatements(t, expectedBuildStatements, actualbuildStatements) } func assertError(t *testing.T, err error, expected string) { if err == nil { t.Errorf("expected error '%s', but got no error", expected) } else if err.Error() != expected { t.Errorf("expected error '%s', but got: %s", expected, err.Error()) } } // Asserts that the given actual build statements match the given expected build statements. // Build statement equivalence is determined using buildStatementEquals. func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) { if len(expected) != len(actual) { t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v", len(expected), len(actual), expected, actual) return } ACTUAL_LOOP: for _, actualStatement := range actual { for _, expectedStatement := range expected { if buildStatementEquals(actualStatement, expectedStatement) { continue ACTUAL_LOOP } } t.Errorf("unexpected build statement %v.\n expected: %v", actualStatement, expected) return } } func buildStatementEquals(first BuildStatement, second BuildStatement) bool { if first.Mnemonic != second.Mnemonic { return false } if first.Command != second.Command { return false } // Ordering is significant for environment variables. if !reflect.DeepEqual(first.Env, second.Env) { return false } // Ordering is irrelevant for input and output paths, so compare sets. if !reflect.DeepEqual(stringSet(first.InputPaths), stringSet(second.InputPaths)) { return false } if !reflect.DeepEqual(stringSet(first.OutputPaths), stringSet(second.OutputPaths)) { return false } return true } func stringSet(stringSlice []string) map[string]struct{} { stringMap := make(map[string]struct{}) for _, s := range stringSlice { stringMap[s] = struct{}{} } return stringMap }