| // Copyright 2020 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; | |
| import static com.google.common.truth.Truth.assertThat; | |
| import static com.google.devtools.build.lib.testutil.MoreAsserts.assertNoEvents; | |
| import static org.junit.Assert.assertThrows; | |
| import static org.junit.Assert.fail; | |
| import com.google.common.collect.Iterables; | |
| import com.google.devtools.build.lib.actions.BuildFailedException; | |
| import com.google.devtools.build.lib.analysis.ViewCreationFailedException; | |
| import com.google.devtools.build.lib.buildtool.util.GoogleBuildIntegrationTestCase; | |
| import org.junit.Test; | |
| import org.junit.runner.RunWith; | |
| import org.junit.runners.JUnit4; | |
| /** Tests for actions that generate artifacts with one a prefix of the other. */ | |
| @RunWith(JUnit4.class) | |
| public class OutputArtifactConflictTest extends GoogleBuildIntegrationTestCase { | |
| private void runArtifactPrefix(boolean keepGoing, boolean modifyBuildFile) throws Exception { | |
| if (modifyBuildFile) { | |
| write("x/BUILD", "cc_library(name = 'y', srcs = ['y.cc'])"); | |
| } else { | |
| write("x/BUILD", "cc_binary(name = 'y', srcs = ['y.cc'], malloc = '//base:system_malloc')"); | |
| } | |
| write("x/y/BUILD", "cc_library(name = 'y')"); | |
| write("x/y.cc", "int main() { return 0; }"); | |
| if (modifyBuildFile) { | |
| buildTarget("//x/y", "//x:y"); | |
| write("x/BUILD", "cc_binary(name = 'y', srcs = ['y.cc'], malloc = '//base:system_malloc')"); | |
| } else { | |
| buildTarget("//x/y"); | |
| } | |
| assertNoEvents(events.errors()); | |
| if (keepGoing) { | |
| runtimeWrapper.addOptions("--keep_going"); | |
| } | |
| try { | |
| // Skyframe full should throw an error here even if we just build //x:y. However, because our | |
| // testing infrastructure sets up lots of symlinks, Skyframe invalidates the //x/y action, and | |
| // so would not find a conflict here without re-evaluating //x/y. Note that in a real client, | |
| // requesting the //x/y target would not be necessary to throw an exception. | |
| buildTarget("//x:y", "//x/y"); | |
| fail(); | |
| } catch (BuildFailedException | ViewCreationFailedException e) { | |
| // Expected. | |
| } | |
| events.assertContainsError("output path 'blaze-out/"); | |
| // Skip over config key string ... | |
| events.assertContainsError( | |
| "/bin/x/y' (belonging to //x:y) is a prefix of output path 'blaze-out"); | |
| if (keepGoing) { | |
| assertThat(Iterables.size(events.errors())).isGreaterThan(1); | |
| } else { | |
| assertThat(events.errors()).hasSize(1); | |
| } | |
| } | |
| @Test | |
| public void testArtifactPrefix_KeepGoing() throws Exception { | |
| runArtifactPrefix(true, false); | |
| } | |
| @Test | |
| public void testArtifactPrefix_NoKeepGoing() throws Exception { | |
| runArtifactPrefix(false, false); | |
| } | |
| @Test | |
| public void testArtifactPrefix_KeepGoing_ModifyBuildFile() throws Exception { | |
| runArtifactPrefix(true, true); | |
| } | |
| @Test | |
| public void testArtifactPrefix_NoKeepGoing_ModifyBuildFile() throws Exception { | |
| runArtifactPrefix(false, true); | |
| } | |
| @Test | |
| public void testInvalidatedConflict() throws Exception { | |
| write("x/BUILD", "cc_binary(name = 'y', srcs = ['y.cc'], malloc = '//base:system_malloc')"); | |
| write("x/y/BUILD", "cc_library(name = 'y')"); | |
| write("x/y.cc", "int main() { return 0; }"); | |
| try { | |
| buildTarget("//x:y", "//x/y"); | |
| fail(); | |
| } catch (BuildFailedException | ViewCreationFailedException e) { | |
| // Expected. | |
| } | |
| write("x/BUILD", "# no conflict"); | |
| events.clear(); | |
| buildTarget("//x/y"); | |
| events.assertNoWarningsOrErrors(); | |
| } | |
| @Test | |
| public void testNewTargetConflict() throws Exception { | |
| write("x/BUILD", "cc_binary(name = 'y', srcs = ['y.cc'], malloc = '//base:system_malloc')"); | |
| write("x/y/BUILD", "cc_library(name = 'y')"); | |
| write("x/y.cc", "int main() { return 0; }"); | |
| buildTarget("//x/y"); | |
| events.assertNoWarningsOrErrors(); | |
| try { | |
| buildTarget("//x:y", "//x/y"); | |
| fail(); | |
| } catch (BuildFailedException | ViewCreationFailedException e) { | |
| // Expected. | |
| } | |
| } | |
| @Test | |
| public void testTwoOverlappingBuildsHasNoConflict() throws Exception { | |
| write("x/BUILD", "cc_binary(name = 'y', srcs = ['y.cc'], malloc = '//base:system_malloc')"); | |
| write("x/y/BUILD", "cc_library(name = 'y')"); | |
| write("x/y.cc", "int main() { return 0; }"); | |
| buildTarget("//x/y"); | |
| events.assertNoWarningsOrErrors(); | |
| buildTarget("//x:y"); | |
| events.assertNoWarningsOrErrors(); | |
| // Verify that together they fail, even though no new targets have been analyzed | |
| try { | |
| buildTarget("//x:y", "//x/y"); | |
| fail(); | |
| } catch (BuildFailedException | ViewCreationFailedException e) { | |
| // Expected. | |
| } | |
| events.clear(); | |
| // Verify that they still don't fail individually, so no state remains | |
| buildTarget("//x/y"); | |
| events.assertNoWarningsOrErrors(); | |
| buildTarget("//x:y"); | |
| events.assertNoWarningsOrErrors(); | |
| } | |
| @Test | |
| public void testFailingTargetsDoNotCauseActionConflicts() throws Exception { | |
| write( | |
| "x/bad_rule.bzl", | |
| "def _impl(ctx):", | |
| " return list().this_method_does_not_exist()", | |
| "bad_rule = rule(_impl, attrs = {'deps': attr.label_list()})"); | |
| write( | |
| "x/BUILD", | |
| "load('//x:bad_rule.bzl', 'bad_rule')", | |
| "cc_binary(name = 'y', srcs = ['y.cc'], malloc = '//base:system_malloc')", | |
| "bad_rule(name = 'bad', deps = [':y'])"); | |
| write("x/y/BUILD", "cc_library(name = 'y')"); | |
| write("x/y.cc", "int main() { return 0; }"); | |
| runtimeWrapper.addOptions("--keep_going"); | |
| try { | |
| buildTarget("//x:y", "//x/y"); | |
| fail(); | |
| } catch (ViewCreationFailedException e) { | |
| fail("Unexpected artifact prefix conflict: " + e); | |
| } catch (BuildFailedException e) { | |
| // Expected. | |
| } | |
| } | |
| @Test | |
| public void testMultipleConflictErrors() throws Exception { | |
| write( | |
| "conflict/BUILD", | |
| "cc_library(name='x', srcs=['foo.cc'])", | |
| "cc_binary(name='_objs/x/foo.pic.o', srcs=['bar.cc'], " | |
| + "malloc = '//base:system_malloc')"); | |
| write("x/BUILD", "cc_binary(name = 'y', srcs = ['y.cc'], malloc = '//base:system_malloc')"); | |
| write("x/y.cc", "int main() { return 0; }"); | |
| write("conflict/foo.cc", "int main() { return 0; }"); | |
| write("conflict/bar.cc", "int main() { return 0; }"); | |
| write("x/y/BUILD", "cc_library(name = 'y')"); | |
| runtimeWrapper.addOptions("--keep_going"); | |
| assertThrows( | |
| BuildFailedException.class, | |
| () -> buildTarget("//x/y", "//x:y", "//conflict:x", "//conflict:_objs/x/foo.pic.o")); | |
| events.assertContainsError( | |
| "file 'conflict/_objs/x/foo.pic.o' is generated by these conflicting actions:"); | |
| events.assertContainsError( | |
| "/bin/x/y' (belonging to //x:y) is a prefix of output path 'blaze-out"); | |
| } | |
| } |