in src/com/facebook/buck/cli/TestRunning.java [128:471]
public static int runTests(
CommandRunnerParams params,
BuildRuleResolver ruleResolver,
Iterable<TestRule> tests,
ExecutionContext executionContext,
TestRunningOptions options,
ListeningExecutorService service,
BuildEngine buildEngine,
BuildContext buildContext,
SourcePathRuleFinder ruleFinder)
throws IOException, InterruptedException {
ImmutableSet<JavaLibrary> rulesUnderTestForCoverage;
// If needed, we first run instrumentation on the class files.
if (options.isCodeCoverageEnabled()) {
rulesUnderTestForCoverage = getRulesUnderTest(tests);
if (!rulesUnderTestForCoverage.isEmpty()) {
try {
// We'll use the filesystem of the first rule under test. This will fail if there are any
// tests from a different repo, but it'll help us bootstrap ourselves to being able to
// support multiple repos
// TODO(t8220837): Support tests in multiple repos
JavaLibrary library = rulesUnderTestForCoverage.iterator().next();
for (Step step :
MakeCleanDirectoryStep.of(
BuildCellRelativePath.fromCellRelativePath(
buildContext.getBuildCellRootPath(),
library.getProjectFilesystem(),
JacocoConstants.getJacocoOutputDir(library.getProjectFilesystem())))) {
StepRunner.runStep(executionContext, step, Optional.empty());
}
} catch (StepFailedException e) {
params
.getBuckEventBus()
.post(ConsoleEvent.severe(Throwables.getRootCause(e).getLocalizedMessage()));
return 1;
}
}
} else {
rulesUnderTestForCoverage = ImmutableSet.of();
}
ImmutableSet<String> testTargets =
FluentIterable.from(tests)
.transform(BuildRule::getBuildTarget)
.transform(Object::toString)
.toSet();
int totalNumberOfTests = Iterables.size(tests);
params
.getBuckEventBus()
.post(
TestRunEvent.started(
options.isRunAllTests(),
options.getTestSelectorList(),
options.shouldExplainTestSelectorList(),
testTargets));
// Start running all of the tests. The result of each java_test() rule is represented as a
// ListenableFuture.
List<ListenableFuture<TestResults>> results = new ArrayList<>();
AtomicInteger lastReportedTestSequenceNumber = new AtomicInteger();
List<TestRun> separateTestRuns = new ArrayList<>();
List<TestRun> parallelTestRuns = new ArrayList<>();
for (TestRule test : tests) {
// Determine whether the test needs to be executed.
Callable<TestResults> resultsInterpreter =
getCachingCallable(
test.interpretTestResults(
executionContext,
buildContext.getSourcePathResolver(),
/*isUsingTestSelectors*/ !options.getTestSelectorList().isEmpty()));
Map<String, UUID> testUUIDMap = new HashMap<>();
AtomicReference<TestStatusMessageEvent.Started> currentTestStatusMessageEvent =
new AtomicReference<>();
TestRule.TestReportingCallback testReportingCallback =
new TestRule.TestReportingCallback() {
@Override
public void testsDidBegin() {
LOG.debug("Tests for rule %s began", test.getBuildTarget());
}
@Override
public void statusDidBegin(TestStatusMessage didBeginMessage) {
LOG.debug("Test status did begin: %s", didBeginMessage);
TestStatusMessageEvent.Started startedEvent =
TestStatusMessageEvent.started(didBeginMessage);
TestStatusMessageEvent.Started previousEvent =
currentTestStatusMessageEvent.getAndSet(startedEvent);
Preconditions.checkState(
previousEvent == null,
"Received begin status before end status (%s)",
previousEvent);
params.getBuckEventBus().post(startedEvent);
String message = didBeginMessage.getMessage();
if (message.toLowerCase().contains("debugger")) {
executionContext
.getStdErr()
.println(executionContext.getAnsi().asWarningText(message));
}
}
@Override
public void statusDidEnd(TestStatusMessage didEndMessage) {
LOG.debug("Test status did end: %s", didEndMessage);
TestStatusMessageEvent.Started previousEvent =
currentTestStatusMessageEvent.getAndSet(null);
Preconditions.checkState(
previousEvent != null,
"Received end status before begin status (%s)",
previousEvent);
params
.getBuckEventBus()
.post(TestStatusMessageEvent.finished(previousEvent, didEndMessage));
}
@Override
public void testDidBegin(String testCaseName, String testName) {
LOG.debug(
"Test rule %s test case %s test name %s began",
test.getBuildTarget(), testCaseName, testName);
UUID testUUID = UUID.randomUUID();
// UUID is immutable and thread-safe as of Java 7, so it's
// safe to stash in a map and use later:
//
// http://bugs.java.com/view_bug.do?bug_id=6611830
testUUIDMap.put(testCaseName + ":" + testName, testUUID);
params
.getBuckEventBus()
.post(TestSummaryEvent.started(testUUID, testCaseName, testName));
}
@Override
public void testDidEnd(TestResultSummary testResultSummary) {
LOG.debug("Test rule %s test did end: %s", test.getBuildTarget(), testResultSummary);
UUID testUUID =
testUUIDMap.get(
testResultSummary.getTestCaseName() + ":" + testResultSummary.getTestName());
Objects.requireNonNull(testUUID);
params.getBuckEventBus().post(TestSummaryEvent.finished(testUUID, testResultSummary));
}
@Override
public void testsDidEnd(List<TestCaseSummary> testCaseSummaries) {
LOG.debug("Test rule %s tests did end: %s", test.getBuildTarget(), testCaseSummaries);
}
};
List<Step> steps;
params.getBuckEventBus().post(IndividualTestEvent.started(testTargets));
ImmutableList.Builder<Step> stepsBuilder = ImmutableList.builder();
Preconditions.checkState(buildEngine.isRuleBuilt(test.getBuildTarget()));
List<Step> testSteps =
test.runTests(executionContext, options, buildContext, testReportingCallback);
if (!testSteps.isEmpty()) {
stepsBuilder.addAll(testSteps);
}
steps = stepsBuilder.build();
TestRun testRun = ImmutableTestRun.of(test, steps, resultsInterpreter, testReportingCallback);
// Always run the commands, even if the list of commands as empty. There may be zero
// commands because the rule is cached, but its results must still be processed.
if (test.runTestSeparately()) {
LOG.debug("Running test %s in serial", test);
separateTestRuns.add(testRun);
} else {
LOG.debug("Running test %s in parallel", test);
parallelTestRuns.add(testRun);
}
}
for (TestRun testRun : parallelTestRuns) {
ListenableFuture<TestResults> testResults =
runStepsAndYieldResult(
executionContext,
testRun.getSteps(),
testRun.getTestResultsCallable(),
testRun.getTest().getBuildTarget(),
params.getBuckEventBus(),
service);
results.add(
transformTestResults(
params,
testResults,
testRun.getTest(),
testRun.getTestReportingCallback(),
testTargets,
lastReportedTestSequenceNumber,
totalNumberOfTests));
}
ListenableFuture<List<TestResults>> parallelTestStepsFuture = Futures.allAsList(results);
List<TestResults> completedResults = new ArrayList<>();
ListeningExecutorService directExecutorService = MoreExecutors.newDirectExecutorService();
ListenableFuture<Unit> uberFuture =
MoreFutures.addListenableCallback(
parallelTestStepsFuture,
new FutureCallback<List<TestResults>>() {
@Override
public void onSuccess(List<TestResults> parallelTestResults) {
LOG.debug("Parallel tests completed, running separate tests...");
completedResults.addAll(parallelTestResults);
List<ListenableFuture<TestResults>> separateResultsList = new ArrayList<>();
for (TestRun testRun : separateTestRuns) {
separateResultsList.add(
transformTestResults(
params,
runStepsAndYieldResult(
executionContext,
testRun.getSteps(),
testRun.getTestResultsCallable(),
testRun.getTest().getBuildTarget(),
params.getBuckEventBus(),
directExecutorService),
testRun.getTest(),
testRun.getTestReportingCallback(),
testTargets,
lastReportedTestSequenceNumber,
totalNumberOfTests));
}
ListenableFuture<List<TestResults>> serialResults =
Futures.allAsList(separateResultsList);
try {
completedResults.addAll(serialResults.get());
} catch (ExecutionException e) {
LOG.error(e, "Error fetching serial test results");
throw new HumanReadableException(e, "Error fetching serial test results");
} catch (InterruptedException e) {
LOG.error(e, "Interrupted fetching serial test results");
try {
serialResults.cancel(true);
} catch (CancellationException ignored) {
// Rethrow original InterruptedException instead.
}
Threads.interruptCurrentThread();
throw new HumanReadableException(e, "Test cancelled");
}
LOG.debug("Done running serial tests.");
}
@Override
public void onFailure(Throwable e) {
LOG.error(e, "Parallel tests failed, not running serial tests");
throw new HumanReadableException(e, "Parallel tests failed");
}
},
directExecutorService);
try {
// Block until all the tests have finished running.
uberFuture.get();
} catch (ExecutionException e) {
e.printStackTrace(params.getConsole().getStdErr());
return 1;
} catch (InterruptedException e) {
try {
uberFuture.cancel(true);
} catch (CancellationException ignored) {
// Rethrow original InterruptedException instead.
}
Threads.interruptCurrentThread();
throw e;
}
params.getBuckEventBus().post(TestRunEvent.finished(testTargets, completedResults));
// Write out the results as XML, if requested.
Optional<String> path = options.getPathToXmlTestOutput();
if (path.isPresent()) {
try (Writer writer = Files.newWriter(new File(path.get()), Charsets.UTF_8)) {
writeXmlOutput(completedResults, writer);
}
}
// Generate the code coverage report.
if (options.isCodeCoverageEnabled() && !rulesUnderTestForCoverage.isEmpty()) {
try {
JavaBuckConfig javaBuckConfig = params.getBuckConfig().getView(JavaBuckConfig.class);
DefaultJavaPackageFinder defaultJavaPackageFinder =
javaBuckConfig.createDefaultJavaPackageFinder();
JavaOptions javaOptions = javaBuckConfig.getDefaultJavaOptions();
ToolProvider javaRuntimeProvider = javaOptions.getJavaRuntimeProvider();
Preconditions.checkState(
Iterables.isEmpty(
// TODO(nga): ignores default_target_platform and platform detector
javaRuntimeProvider.getParseTimeDeps(
params
.getTargetConfiguration()
.orElse(UnconfiguredTargetConfiguration.INSTANCE))),
"Using a rule-defined java runtime does not currently support generating code coverage.");
StepRunner.runStep(
executionContext,
getReportCommand(
rulesUnderTestForCoverage,
defaultJavaPackageFinder,
// TODO(nga): ignores default_target_platform and platform detector
javaRuntimeProvider.resolve(
ruleResolver,
params
.getTargetConfiguration()
.orElse(UnconfiguredTargetConfiguration.INSTANCE)),
params.getCells().getRootCell().getFilesystem(),
ruleFinder,
JacocoConstants.getJacocoOutputDir(params.getCells().getRootCell().getFilesystem()),
options.getCoverageReportFormats(),
options.getCoverageReportTitle(),
javaBuckConfig
.getDefaultJavacOptions(
params
.getTargetConfiguration()
.orElse(UnconfiguredTargetConfiguration.INSTANCE))
.getSpoolMode()
== JavacOptions.SpoolMode.INTERMEDIATE_TO_DISK,
options.getCoverageIncludes(),
options.getCoverageExcludes()),
Optional.empty());
} catch (StepFailedException e) {
params
.getBuckEventBus()
.post(ConsoleEvent.severe(Throwables.getRootCause(e).getLocalizedMessage()));
return 1;
}
}
boolean failures =
Iterables.any(
completedResults,
results1 -> {
LOG.debug("Checking result %s for failure", results1);
return !results1.isSuccess();
});
// TODO(buck_team): convert to ExitCode
return failures ? 32 : 0;
}