blob: c21ec4d32cfa70d9b764f7d8adba76fa37624c08 [file] [log] [blame]
// 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");
}
}