blob: 0470a7ef044d40603b3f63adbc8a0c3437459460 [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.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
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.analysis.ConfiguredTarget;
import com.google.devtools.build.lib.buildtool.util.BuildIntegrationTestCase;
import com.google.devtools.build.lib.util.io.OutErr;
import com.google.devtools.build.lib.util.io.RecordingOutErr;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests that concern the transitive closure of data dependencies. Regression testing for bug
* 1022571.
*/
@RunWith(JUnit4.class)
public abstract class TransitiveDataDependencyTest extends BuildIntegrationTestCase {
/**
* Hook for subclasses to define which executor we use. (The two concrete
* subclasses, {Sequential,Parallel}TransitiveDataDependencyTest are at the bottom of this
* source file.)
*/
protected abstract int numJobs();
@Before
public final void addJobNumberOption() throws Exception {
addOptions("--jobs", "" + numJobs());
}
private void assertSameConfiguredTarget(String label) throws Exception {
assertThat(getOnlyElement(getResult().getSuccessfulTargets()))
.isSameInstanceAs(getConfiguredTarget(label));
}
@Test
public void testTransitiveDataDepIsBuilt() throws Exception {
write(
"data/BUILD",
"""
cc_library(
name = "needsdata",
data = [":data_bin"],
)
cc_binary(
name = "data_bin",
srcs = ["data_bin.c"],
)
""");
write("data/data_bin.c", "int main() { return 0; }");
buildTarget("//data:needsdata");
ConfiguredTarget dataLibTarget = getConfiguredTarget("//data:data_bin");
assertThat(getFilesToBuild(dataLibTarget).toList()).isNotEmpty();
for (Artifact dataOut : getFilesToBuild(dataLibTarget).toList()) {
assertWithMessage("Missing output: " + dataOut.getPath())
.that(dataOut.getPath().exists())
.isTrue();
}
assertSameConfiguredTarget("//data:needsdata");
}
@Test
public void testMissingInputFile() throws Exception {
write("data/BUILD",
"cc_library(name = 'needsdata', data = [':data_file'])");
RecordingOutErr recOutErr = new RecordingOutErr();
OutErr origOutErr = this.outErr;
this.outErr = recOutErr;
// Remove this flag after fixing:
// "Remove source artifacts from top-level artifacts in SkyframeExecutor#buildArtifacts"
// We are adding information about //data:needdata to error message only if
// ActionExecutionFunction has requested that missing artifact. But there is small chance
// that ArtifactFunction of that missing artifact has thrown exception before that request.
// In case of keep_going we can be sure that ActionExecutionFunction has made request.
addOptions("--keep_going");
try {
buildTarget("//data:needsdata");
fail();
} catch (BuildFailedException e) {
assertThat(recOutErr.errAsLatin1())
.containsMatch("//data:needsdata: missing input file '//data:data_file'");
} finally {
this.outErr = origOutErr;
}
assertThat(getResult().getSuccess()).isFalse();
assertThat(getResult().getSuccessfulTargets()).isEmpty();
}
@Test
public void testMissingExportsFiles() throws Exception {
write("data/BUILD", "exports_files(['nosuchfile'])");
RecordingOutErr recOutErr = new RecordingOutErr();
OutErr origOutErr = this.outErr;
this.outErr = recOutErr;
try {
buildTarget("//data:nosuchfile");
fail();
} catch (BuildFailedException e) {
assertThat(recOutErr.errAsLatin1()).containsMatch("missing input file '//data:nosuchfile'");
} finally {
this.outErr = origOutErr;
}
assertThat(getResult().getSuccess()).isFalse();
assertThat(getResult().getSuccessfulTargets()).isEmpty();
}
@Test
public void testMissingInputFilesKeepGoing() throws Exception {
write(
"data/BUILD",
"""
# Comment line
cc_library(
name = "needsdata1",
data = [":data_file1"],
)
cc_library(
name = "needsdata2",
data = [":data_file2"],
)
""");
write("data/data_file2", "data_file2 exists");
RecordingOutErr recOutErr = new RecordingOutErr();
OutErr origOutErr = this.outErr;
this.outErr = recOutErr;
addOptions("--keep_going");
try {
buildTarget("//data:needsdata1", "//data:needsdata2");
fail();
} catch (BuildFailedException expected) {
assertThat(recOutErr.errAsLatin1())
.containsMatch(
"data/BUILD:2:1: //data:needsdata1: missing input file '//data:data_file1'");
} finally {
this.outErr = origOutErr;
}
assertThat(getResult().getSuccess()).isFalse();
assertSameConfiguredTarget("//data:needsdata2");
}
// Concrete implementations of this abstract test:
/** Tests with 1 job. */
@RunWith(JUnit4.class)
public static class SequentialTransitiveDataDependencyTest extends TransitiveDataDependencyTest {
@Override
protected int numJobs() { return 0; }
}
/** Tests with 100 jobs. */
@RunWith(JUnit4.class)
public static class ParallelTransitiveDataDependencyTest extends TransitiveDataDependencyTest {
@Override
protected int numJobs() { return 100; }
}
}