| // Copyright 2021 The Bazel Authors. 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 com.google.devtools.build.lib.buildtool; | |
| // For debugging, uncomment these and the call to setupLogging() below. | |
| // | |
| // import com.google.devtools.build.lib.blaze.BlazeRuntime; | |
| // import java.util.logging.Level; | |
| import static com.google.common.collect.Iterables.getOnlyElement; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static org.junit.Assert.assertThrows; | |
| import static org.junit.Assert.fail; | |
| import com.google.devtools.build.lib.actions.Artifact; | |
| import com.google.devtools.build.lib.actions.BuildFailedException; | |
| import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase; | |
| import com.google.devtools.build.lib.events.Event; | |
| import java.io.IOException; | |
| import java.util.Iterator; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** A test of the semantics of the keepGoing flag: continue as much as possible after an error. */ | |
| @RunWith(JUnit4.class) | |
| public class KeepGoingTest extends BuildIntegrationTestCase { | |
| @Before | |
| public final void addOptions() { | |
| addOptions("--keep_going"); | |
| } | |
| private static String genrule(String name, String deps, int succeeds) { | |
| String out = name + ".out"; | |
| String cmd = (succeeds != 0) ? "cp $(location in) $(location " + out + ")" : "exit 42"; | |
| return "genrule(name='" | |
| + name | |
| + "', " | |
| + " srcs=['in'," | |
| + deps | |
| + "], " | |
| + " outs=['" | |
| + out | |
| + "'], " | |
| + " cmd='" | |
| + cmd | |
| + "')\n"; | |
| } | |
| private static final int A = 0x01; | |
| private static final int B = 0x02; | |
| private static final int C = 0x04; | |
| private static final int D = 0x08; | |
| private static final int E = 0x10; | |
| private static final String[] labels = { | |
| "//keepgoing:A", "//keepgoing:B", "//keepgoing:C", "//keepgoing:D", "//keepgoing:E" | |
| }; | |
| // "mask" is a bitmask of rules that succeed. | |
| private void writeFiles(int mask) throws IOException { | |
| // A --> B --> C | |
| // | +---> D | |
| // | | |
| // +---> E | |
| write( | |
| "keepgoing/BUILD", | |
| genrule("A", "'B','E'", mask & A) | |
| + genrule("B", "'C','D'", mask & B) | |
| + genrule("C", "", mask & C) | |
| + genrule("D", "", mask & D) | |
| + genrule("E", "", mask & E)); | |
| write("keepgoing/in", "(input)"); | |
| } | |
| // "mask" is a bitmask of rules that succeed. | |
| private void assertBuilt(int mask) throws Exception { | |
| for (int ii = 0; ii < labels.length; ii++) { | |
| assertOneBuilt(labels[ii], (mask & (1 << ii)) != 0); | |
| } | |
| } | |
| private void assertOneBuilt(String label, boolean shouldBeBuilt) throws Exception { | |
| Iterable<Artifact> files = getArtifacts(label); | |
| for (Artifact file : files) { | |
| boolean isActuallyBuilt = file.getPath().exists(); | |
| if (file.getPath().exists() != shouldBeBuilt) { | |
| fail( | |
| file.prettyPrint() | |
| + ": shouldBeBuilt=" | |
| + shouldBeBuilt | |
| + ", isActuallyBuilt=" | |
| + isActuallyBuilt); | |
| } | |
| } | |
| } | |
| private void assertNoMoreEvents(Iterator<Event> events) { | |
| boolean ok = true; | |
| while (events.hasNext()) { | |
| System.err.println(events.next()); | |
| ok = false; | |
| } | |
| assertThat(ok).isTrue(); | |
| } | |
| // Build //keepgoing:A, expecting failure. (The BuildResult instance is | |
| // subsequently available via getRequest() for later assertions.) | |
| private void buildA() throws Exception { | |
| BuildFailedException e = | |
| assertThrows(BuildFailedException.class, () -> buildTarget("//keepgoing:A")); | |
| assertThat(e).hasMessageThat().isNull(); | |
| } | |
| /******************************************************************** | |
| * * | |
| * Actual tests... * | |
| * * | |
| ********************************************************************/ | |
| @Test | |
| public void testKeepGoingAfterCFails() throws Exception { | |
| // C fails due to error (logged). | |
| // B fails due to failed prereqs (logged). | |
| // A fails due to failed prereqs (logged). | |
| // D and E are built. | |
| // Then a BuildFailedException is thrown. | |
| writeFiles(A | B | D | E); | |
| buildA(); | |
| Iterator<Event> errors = events.errors().iterator(); | |
| assertThat(errors.next().getMessage()) | |
| .containsMatch("Executing genrule //keepgoing:C failed: .*Exit 42.*"); | |
| assertNoMoreEvents(errors); | |
| assertBuilt(D | E); | |
| } | |
| @Test | |
| public void testKeepGoingAfterDFails() throws Exception { | |
| // D fails due to error (logged). | |
| // B fails due to failed prereqs (logged). | |
| // A fails due to failed prereqs (logged). | |
| // C and E are built. | |
| // Then a BuildFailedException is thrown. | |
| writeFiles(A | B | C | E); | |
| buildA(); | |
| Iterator<Event> errors = events.errors().iterator(); | |
| assertThat(errors.next().getMessage()) | |
| .containsMatch("Executing genrule //keepgoing:D failed: .*Exit 42.*"); | |
| assertNoMoreEvents(errors); | |
| assertBuilt(C | E); | |
| } | |
| @Test | |
| public void testKeepGoingAfterCAndDFail() throws Exception { | |
| // C and D fail due to error (logged). | |
| // B fails due to failed prereqs (logged). | |
| // A fails due to failed prereqs (logged). | |
| // E is built. | |
| // Then a BuildFailedException is thrown. | |
| writeFiles(A | B | E); | |
| buildA(); | |
| // C, D events are unordered: | |
| Iterator<Event> errors = events.errors().iterator(); | |
| assertThat(errors.next().getMessage()) | |
| .containsMatch("Executing genrule //keepgoing:(C|D) failed: .*Exit 42.*"); | |
| assertThat(errors.next().getMessage()) | |
| .containsMatch("Executing genrule //keepgoing:(C|D) failed: .*Exit 42.*"); | |
| assertNoMoreEvents(errors); | |
| assertBuilt(E); | |
| } | |
| @Test | |
| public void testKeepGoingAfterEFails() throws Exception { | |
| // E fails due to error (logged). | |
| // A fails due to failed prereqs (logged). | |
| // B,C,D are built. | |
| // Then a BuildFailedException is thrown. | |
| writeFiles(A | B | C | D); | |
| buildA(); | |
| Iterator<Event> errors = events.errors().iterator(); | |
| assertThat(errors.next().getMessage()) | |
| .containsMatch("Executing genrule //keepgoing:E failed: .*Exit 42.*"); | |
| assertNoMoreEvents(errors); | |
| assertBuilt(B | C | D); | |
| } | |
| // Regression test for b/8826301, incremental builder does not correctly set root actions. | |
| // Check that keep going works on second build. Note that this test failed non-deterministically | |
| // in b/8826301, because it depended on HashSet iteration order. | |
| @Test | |
| public void testKeepGoingOnSecondBuild() throws Exception { | |
| StringBuilder buildFile = new StringBuilder(); | |
| buildFile | |
| .append("genrule(name='topgen', tools=[':badgen'], outs=['top.out'], ") | |
| .append("cmd='touch $@')\n") | |
| .append("genrule(name='badgen', executable=1, srcs=['badsrc.sh'], ") | |
| .append("outs=['bad.out'], cmd='bash $< > $@', tools = ["); | |
| // Make graph large so incremental dependency checker does graph culling, if enabled. | |
| for (int i = 0; i < 60; i++) { | |
| buildFile.append("':gen" + i + "', "); | |
| } | |
| buildFile.append("])\n"); | |
| for (int i = 0; i < 60; i++) { | |
| buildFile | |
| .append("genrule(name='gen") | |
| .append(i) | |
| .append("', " + "outs=['gen") | |
| .append(i) | |
| .append(".out'], executable=1, cmd = 'echo \"#!/bin/true\" > $@')\n"); | |
| } | |
| write("keepgoing/BUILD", buildFile.toString()); | |
| write("keepgoing/badsrc.sh", "exit 0"); | |
| buildTarget("//keepgoing:topgen"); | |
| write("keepgoing/badsrc.sh", "exit 42"); | |
| events.clear(); | |
| BuildFailedException e = | |
| assertThrows(BuildFailedException.class, () -> buildTarget("//keepgoing:topgen")); | |
| assertThat(e).hasMessageThat().isNull(); | |
| Iterator<Event> errors = events.errors().iterator(); | |
| assertThat(errors.next().getMessage()) | |
| .containsMatch("Executing genrule //keepgoing:badgen.* failed: .*Exit 42.*"); | |
| assertNoMoreEvents(errors); | |
| } | |
| @Test | |
| public void testConfigurationErrorsAreToleratedWithKeepGoing() throws Exception { | |
| runtimeWrapper.addOptions("--experimental_builtins_injection_override=+cc_library"); | |
| write("a/BUILD", "cc_library(name='a', srcs=['missing.foo'])"); | |
| write("b/BUILD", "cc_library(name='b')"); | |
| /** | |
| * Regression coverage for bug 1191396: "blaze build -k exits zero if execution succeeds, even | |
| * if there were analysis errors". | |
| */ | |
| assertBuildFailedExceptionFromBuilding( | |
| "command succeeded, but not all targets were analyzed", "//a", "//b"); | |
| events.assertContainsError( | |
| "in srcs attribute of cc_library rule @//a:a: source file '@//a:missing.foo' is misplaced" | |
| + " here"); | |
| events.assertContainsInfo("Analysis succeeded for only 1 of 2 top-level targets"); | |
| assertSameConfiguredTarget("//b:b"); | |
| } | |
| @Test | |
| public void testKeepGoingAfterLoadingPhaseErrors() throws Exception { | |
| write("a/BUILD", "cc_library(name='a')"); | |
| write("b/BUILD", "cc_library(name='b', deps = ['//missing:lib'])"); | |
| assertBuildFailedExceptionFromBuilding( | |
| "command succeeded, but not all targets were analyzed", "//a", "//b"); // | |
| events.assertContainsError("no such package 'missing': BUILD file not found in any of the"); | |
| assertSameConfiguredTarget("//a:a"); | |
| events.assertContainsInfo(" succeeded for only 1 of "); | |
| } | |
| @Test | |
| public void testKeepGoingAfterTargetParsingErrors() throws Exception { | |
| write("a/BUILD", "cc_library(name='a', xyz)"); | |
| write("b/BUILD", "cc_library(name='b', xyz)"); | |
| write("b/b1/BUILD", "cc_library(name='b1')"); | |
| write("b/b2/BUILD", "cc_library(name='b2', xyz)"); | |
| assertBuildFailedExceptionFromBuilding( | |
| "command succeeded, but there were errors parsing the target pattern", "b/...", "//a"); | |
| events.assertContainsWarning("Target pattern parsing failed."); | |
| assertSameConfiguredTarget("//b/b1"); | |
| } | |
| @Test | |
| public void testKeepGoingAfterSchedulingDependencyMiddlemanFailure() throws Exception { | |
| write("foo/foo.cc", "int main() { return 0; }"); | |
| write( | |
| "foo/BUILD", | |
| "cc_binary(name='foo', srcs=['foo.cc', 'gen.h'], malloc='system_malloc')", | |
| "cc_library(name='system_malloc', linkstatic=1)", | |
| "genrule(name='gen', srcs=[], outs=['gen.h'], cmd = 'exit 1')"); | |
| assertThrows(BuildFailedException.class, () -> buildTarget("//foo:foo")); | |
| } | |
| private void assertSameConfiguredTarget(String label) throws Exception { | |
| assertThat(getOnlyElement(getResult().getSuccessfulTargets())) | |
| .isSameInstanceAs(getConfiguredTarget(label)); | |
| } | |
| @Test | |
| public void testKeepGoingAfterAnalysisFailure() throws Exception { | |
| write( | |
| "analysiserror/failer.bzl", | |
| "def _failer_impl(ctx):", | |
| " fail('BOOM!')", | |
| "", | |
| "failer = rule(implementation = _failer_impl)"); | |
| write( | |
| "analysiserror/BUILD", | |
| "load(':failer.bzl', 'failer')", | |
| "genrule(name='gen', srcs=[], outs=['gen.h'], cmd='exit 1')", | |
| // The next line has an analysis failure: the xmb_lint rule is devoid of xmb files. | |
| "failer(name='foo')", | |
| "cc_library(name='bar')"); | |
| assertBuildFailedExceptionFromBuilding( | |
| "command succeeded, but not all targets were analyzed", | |
| "//analysiserror:foo", | |
| "//analysiserror:bar"); | |
| events.assertContainsError("Error in fail: BOOM!"); | |
| assertSameConfiguredTarget("//analysiserror:bar"); | |
| } | |
| private void assertBuildFailedExceptionFromBuilding(String msg, String... targets) { | |
| BuildFailedException e = assertThrows(BuildFailedException.class, () -> buildTarget(targets)); | |
| assertThat(e).hasMessageThat().isEqualTo(msg); | |
| assertThat(getResult().getSuccess()).isFalse(); | |
| } | |
| } |