/* * Copyright 2015-present Facebook, Inc. * * 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.facebook.buck.cli; import com.facebook.buck.event.BuckEventBus; import com.facebook.buck.event.ConsoleEvent; import com.facebook.buck.io.ProjectFilesystem; import com.facebook.buck.jvm.java.DefaultJavaLibrary; import com.facebook.buck.jvm.java.DefaultJavaPackageFinder; import com.facebook.buck.jvm.java.GenerateCodeCoverageReportStep; import com.facebook.buck.jvm.java.JacocoConstants; import com.facebook.buck.jvm.java.JavaBuckConfig; import com.facebook.buck.jvm.java.JavaLibrary; import com.facebook.buck.jvm.java.JavaLibraryWithTests; import com.facebook.buck.jvm.java.JavaRuntimeLauncher; import com.facebook.buck.jvm.java.JavaTest; import com.facebook.buck.jvm.java.JavacOptions; import com.facebook.buck.log.Logger; import com.facebook.buck.model.BuildTarget; import com.facebook.buck.model.Either; import com.facebook.buck.rules.BuildEngine; import com.facebook.buck.rules.BuildRule; import com.facebook.buck.rules.IndividualTestEvent; import com.facebook.buck.rules.SourcePath; import com.facebook.buck.rules.SourcePathResolver; import com.facebook.buck.rules.SourcePathRuleFinder; import com.facebook.buck.rules.TestRule; import com.facebook.buck.rules.TestRunEvent; import com.facebook.buck.rules.TestStatusMessageEvent; import com.facebook.buck.rules.TestSummaryEvent; import com.facebook.buck.step.ExecutionContext; import com.facebook.buck.step.Step; import com.facebook.buck.step.StepFailedException; import com.facebook.buck.step.StepRunner; import com.facebook.buck.step.fs.MakeCleanDirectoryStep; import com.facebook.buck.test.CoverageReportFormat; import com.facebook.buck.test.TestCaseSummary; import com.facebook.buck.test.TestResultSummary; import com.facebook.buck.test.TestResults; import com.facebook.buck.test.TestRuleEvent; import com.facebook.buck.test.TestRunningOptions; import com.facebook.buck.test.TestStatusMessage; import com.facebook.buck.test.result.type.ResultType; import com.facebook.buck.util.HumanReadableException; import com.facebook.buck.util.MoreCollectors; import com.facebook.buck.util.concurrent.MoreFutures; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Charsets; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.base.Throwables; import com.google.common.collect.FluentIterable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; import com.google.common.io.Files; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import java.io.File; import java.io.IOException; import java.io.Writer; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nullable; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.w3c.dom.Document; import org.w3c.dom.Element; /** Utility class for running tests from {@link TestRule}s which have been built. */ public class TestRunning { public static final int TEST_FAILURES_EXIT_CODE = 42; private static final Logger LOG = Logger.get(TestRunning.class); // Utility class; do not instantiate. private TestRunning() {} @SuppressWarnings("PMD.EmptyCatchBlock") public static int runTests( final CommandRunnerParams params, Iterable<TestRule> tests, ExecutionContext executionContext, final TestRunningOptions options, ListeningExecutorService service, BuildEngine buildEngine, final StepRunner stepRunner, SourcePathResolver sourcePathResolver, SourcePathRuleFinder ruleFinder) throws IOException, ExecutionException, 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( library.getProjectFilesystem(), JacocoConstants.getJacocoOutputDir(library.getProjectFilesystem()))) { stepRunner.runStepForBuildTarget(executionContext, step, Optional.empty()); } } catch (StepFailedException e) { params .getBuckEventBus() .post(ConsoleEvent.severe(Throwables.getRootCause(e).getLocalizedMessage())); return 1; } } } else { rulesUnderTestForCoverage = ImmutableSet.of(); } final ImmutableSet<String> testTargets = FluentIterable.from(tests) .transform(BuildRule::getBuildTarget) .transform(Object::toString) .toSet(); final 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<>(); final AtomicInteger lastReportedTestSequenceNumber = new AtomicInteger(); final List<TestRun> separateTestRuns = new ArrayList<>(); List<TestRun> parallelTestRuns = new ArrayList<>(); for (final TestRule test : tests) { // Determine whether the test needs to be executed. final Callable<TestResults> resultsInterpreter = getCachingCallable( test.interpretTestResults( executionContext, /*isUsingTestSelectors*/ !options.getTestSelectorList().isEmpty())); final Map<String, UUID> testUUIDMap = new HashMap<>(); final 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()); Preconditions.checkNotNull(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, sourcePathResolver, testReportingCallback); if (!testSteps.isEmpty()) { stepsBuilder.addAll(testSteps); } steps = stepsBuilder.build(); TestRun testRun = TestRun.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( stepRunner, 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); final List<TestResults> completedResults = new ArrayList<>(); final ListeningExecutorService directExecutorService = MoreExecutors.newDirectExecutorService(); ListenableFuture<Void> 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( stepRunner, 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. } Thread.currentThread().interrupt(); 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. } Thread.currentThread().interrupt(); 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(); stepRunner.runStepForBuildTarget( executionContext, getReportCommand( rulesUnderTestForCoverage, defaultJavaPackageFinder, javaBuckConfig.getDefaultJavaOptions().getJavaRuntimeLauncher(), params.getCell().getFilesystem(), sourcePathResolver, ruleFinder, JacocoConstants.getJacocoOutputDir(params.getCell().getFilesystem()), options.getCoverageReportFormat(), options.getCoverageReportTitle(), javaBuckConfig.getDefaultJavacOptions().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(); }); return failures ? TEST_FAILURES_EXIT_CODE : 0; } private static ListenableFuture<TestResults> transformTestResults( final CommandRunnerParams params, ListenableFuture<TestResults> originalTestResults, final TestRule testRule, final TestRule.TestReportingCallback testReportingCallback, final ImmutableSet<String> testTargets, final AtomicInteger lastReportedTestSequenceNumber, final int totalNumberOfTests) { final SettableFuture<TestResults> transformedTestResults = SettableFuture.create(); FutureCallback<TestResults> callback = new FutureCallback<TestResults>() { private TestResults postTestResults(TestResults testResults) { if (!testRule.supportsStreamingTests()) { // For test rules which don't support streaming tests, we'll // stream test summary events after interpreting the // results. LOG.debug("Simulating streaming test events for rule %s", testRule); testReportingCallback.testsDidBegin(); for (TestCaseSummary testCaseSummary : testResults.getTestCases()) { for (TestResultSummary testResultSummary : testCaseSummary.getTestResults()) { testReportingCallback.testDidBegin( testResultSummary.getTestCaseName(), testResultSummary.getTestName()); testReportingCallback.testDidEnd(testResultSummary); } } testReportingCallback.testsDidEnd(testResults.getTestCases()); LOG.debug("Done simulating streaming test events for rule %s", testRule); } TestResults transformedTestResults = TestResults.builder() .from(testResults) .setSequenceNumber(lastReportedTestSequenceNumber.incrementAndGet()) .setTotalNumberOfTests(totalNumberOfTests) .build(); params .getBuckEventBus() .post(IndividualTestEvent.finished(testTargets, transformedTestResults)); return transformedTestResults; } @Override public void onSuccess(TestResults testResults) { LOG.debug("Transforming successful test results %s", testResults); postTestResults(testResults); transformedTestResults.set(testResults); } @Override public void onFailure(Throwable throwable) { LOG.warn(throwable, "Test command step failed, marking %s as failed", testRule); // If the test command steps themselves fail, report this as special test result. TestResults testResults = TestResults.of( testRule.getBuildTarget(), ImmutableList.of( new TestCaseSummary( testRule.getBuildTarget().toString(), ImmutableList.of( new TestResultSummary( testRule.getBuildTarget().toString(), "main", ResultType.FAILURE, 0L, throwable.getMessage(), Throwables.getStackTraceAsString(throwable), "", "")))), testRule.getContacts(), testRule .getLabels() .stream() .map(Object::toString) .collect(MoreCollectors.toImmutableSet())); TestResults newTestResults = postTestResults(testResults); transformedTestResults.set(newTestResults); } }; Futures.addCallback(originalTestResults, callback); return transformedTestResults; } private static Callable<TestResults> getCachingCallable(final Callable<TestResults> callable) { return new Callable<TestResults>() { @Nullable private Either<TestResults, Exception> result = null; @Override public synchronized TestResults call() throws Exception { if (result == null) { try { result = Either.ofLeft(callable.call()); } catch (Exception t) { result = Either.ofRight(t); } } if (result.isRight()) { throw result.getRight(); } return result.getLeft(); } }; } /** Generates the set of Java library rules under test. */ private static ImmutableSet<JavaLibrary> getRulesUnderTest(Iterable<TestRule> tests) { ImmutableSet.Builder<JavaLibrary> rulesUnderTest = ImmutableSet.builder(); // Gathering all rules whose source will be under test. for (TestRule test : tests) { if (test instanceof JavaTest) { // Look at the transitive dependencies for `tests` attribute that refers to this test. JavaTest javaTest = (JavaTest) test; ImmutableSet<JavaLibrary> transitiveDeps = javaTest.getCompiledTestsLibrary().getTransitiveClasspathDeps(); for (JavaLibrary dep : transitiveDeps) { if (dep instanceof JavaLibraryWithTests) { ImmutableSortedSet<BuildTarget> depTests = ((JavaLibraryWithTests) dep).getTests(); if (depTests.contains(test.getBuildTarget())) { rulesUnderTest.add(dep); } } } } } return rulesUnderTest.build(); } /** * Writes the test results in XML format to the supplied writer. * * <p>This method does NOT close the writer object. * * @param allResults The test results. * @param writer The writer in which the XML data will be written to. */ public static void writeXmlOutput(List<TestResults> allResults, Writer writer) throws IOException { try { // Build the XML output. DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder docBuilder = dbf.newDocumentBuilder(); Document doc = docBuilder.newDocument(); // Create the <tests> tag. All test data will be within this tag. Element testsEl = doc.createElement("tests"); doc.appendChild(testsEl); for (TestResults results : allResults) { for (TestCaseSummary testCase : results.getTestCases()) { // Create the <test name="..." status="..." time="..."> tag. // This records a single test case result in the test suite. Element testEl = doc.createElement("test"); testEl.setAttribute("name", testCase.getTestCaseName()); testEl.setAttribute("status", testCase.isSuccess() ? "PASS" : "FAIL"); testEl.setAttribute("time", Long.toString(testCase.getTotalTime())); testsEl.appendChild(testEl); // Loop through the test case and add XML data (name, message, and // stacktrace) for each individual test, if present. addExtraXmlInfo(testCase, testEl); } } // Write XML to the writer. TransformerFactory tf = TransformerFactory.newInstance(); Transformer transformer = tf.newTransformer(); transformer.transform(new DOMSource(doc), new StreamResult(writer)); } catch (TransformerException | ParserConfigurationException ex) { throw new IOException("Unable to build the XML document!"); } } /** * A helper method that adds extra XML. * * <p>This includes a test name, time (in ms), message, and stack trace, when present. Example: * * <pre> * <testresult name="failed_test" time="200"> * <message>Reason for test failure</message> * <stacktrace>Stacktrace here</stacktrace> * </testresult> * </pre> * * @param testCase The test case summary containing one or more tests. * @param testEl The XML element object for the <test> tag, in which extra information tags will * be added. */ @VisibleForTesting static void addExtraXmlInfo(TestCaseSummary testCase, Element testEl) { Document doc = testEl.getOwnerDocument(); // Loop through the test case and extract test data. for (TestResultSummary testResult : testCase.getTestResults()) { // Extract the test name and time. String name = Strings.nullToEmpty(testResult.getTestName()); String time = Long.toString(testResult.getTime()); // Create the tag: <testresult name="..." time="..."> Element testResultEl = doc.createElement("testresult"); testResultEl.setAttribute("name", name); testResultEl.setAttribute("time", time); testEl.appendChild(testResultEl); // Create the tag: <message>(Error message here)</message> Element messageEl = doc.createElement("message"); String message = Strings.nullToEmpty(testResult.getMessage()); messageEl.appendChild(doc.createTextNode(message)); testResultEl.appendChild(messageEl); // Create the tag: <stacktrace>(Stacktrace here)</stacktrace> Element stacktraceEl = doc.createElement("stacktrace"); String stacktrace = Strings.nullToEmpty(testResult.getStacktrace()); stacktraceEl.appendChild(doc.createTextNode(stacktrace)); testResultEl.appendChild(stacktraceEl); } } /** * Returns the ShellCommand object that is supposed to generate a code coverage report from data * obtained during the test run. This method will also generate a set of source paths to the class * files tested during the test run. */ private static Step getReportCommand( ImmutableSet<JavaLibrary> rulesUnderTest, DefaultJavaPackageFinder defaultJavaPackageFinder, JavaRuntimeLauncher javaRuntimeLauncher, ProjectFilesystem filesystem, SourcePathResolver sourcePathResolver, SourcePathRuleFinder ruleFinder, Path outputDirectory, CoverageReportFormat format, String title, boolean useIntermediateClassesDir, Optional<String> coverageIncludes, Optional<String> coverageExcludes) { ImmutableSet.Builder<String> srcDirectories = ImmutableSet.builder(); ImmutableSet.Builder<Path> pathsToJars = ImmutableSet.builder(); // Add all source directories of java libraries that we are testing to -sourcepath. for (JavaLibrary rule : rulesUnderTest) { ImmutableSet<String> sourceFolderPath = getPathToSourceFolders(rule, sourcePathResolver, ruleFinder, defaultJavaPackageFinder); if (!sourceFolderPath.isEmpty()) { srcDirectories.addAll(sourceFolderPath); } Path classesItem = null; if (useIntermediateClassesDir) { classesItem = DefaultJavaLibrary.getClassesDir(rule.getBuildTarget(), filesystem); } else { SourcePath path = rule.getSourcePathToOutput(); if (path != null) { classesItem = sourcePathResolver.getRelativePath(path); } } if (classesItem == null) { continue; } pathsToJars.add(classesItem); } return new GenerateCodeCoverageReportStep( javaRuntimeLauncher, filesystem, srcDirectories.build(), pathsToJars.build(), outputDirectory, format, title, coverageIncludes, coverageExcludes); } /** Returns a set of source folders of the java files of a library. */ @VisibleForTesting static ImmutableSet<String> getPathToSourceFolders( JavaLibrary rule, SourcePathResolver sourcePathResolver, SourcePathRuleFinder ruleFinder, DefaultJavaPackageFinder defaultJavaPackageFinder) { ImmutableSet<SourcePath> javaSrcs = rule.getJavaSrcs(); // A Java library rule with just resource files has an empty javaSrcs. if (javaSrcs.isEmpty()) { return ImmutableSet.of(); } // Iterate through all source paths to make sure we are generating a complete set of source // folders for the source paths. Set<String> srcFolders = Sets.newHashSet(); loopThroughSourcePath: for (SourcePath javaSrcPath : javaSrcs) { if (ruleFinder.getRule(javaSrcPath).isPresent()) { continue; } Path javaSrcRelativePath = sourcePathResolver.getRelativePath(javaSrcPath); // If the source path is already under a known source folder, then we can skip this // source path. for (String srcFolder : srcFolders) { if (javaSrcRelativePath.startsWith(srcFolder)) { continue loopThroughSourcePath; } } // If the source path is under one of the source roots, then we can just add the source // root. ImmutableSortedSet<String> pathsFromRoot = defaultJavaPackageFinder.getPathsFromRoot(); for (String root : pathsFromRoot) { if (javaSrcRelativePath.startsWith(root)) { srcFolders.add(root); continue loopThroughSourcePath; } } // Traverse the file system from the parent directory of the java file until we hit the // parent of the src root directory. ImmutableSet<String> pathElements = defaultJavaPackageFinder.getPathElements(); Path directory = sourcePathResolver.getAbsolutePath(javaSrcPath).getParent(); if (pathElements.isEmpty()) { continue; } while (directory != null && directory.getFileName() != null && !pathElements.contains(directory.getFileName().toString())) { directory = directory.getParent(); } if (directory == null || directory.getFileName() == null) { continue; } String directoryPath = directory.toString(); if (!directoryPath.endsWith("/")) { directoryPath += "/"; } srcFolders.add(directoryPath); } return ImmutableSet.copyOf(srcFolders); } private static ListenableFuture<TestResults> runStepsAndYieldResult( StepRunner stepRunner, ExecutionContext context, final List<Step> steps, final Callable<TestResults> interpretResults, final BuildTarget buildTarget, BuckEventBus eventBus, ListeningExecutorService listeningExecutorService) { Preconditions.checkState(!listeningExecutorService.isShutdown()); Callable<TestResults> callable = () -> { LOG.debug("Test steps will run for %s", buildTarget); eventBus.post(TestRuleEvent.started(buildTarget)); for (Step step : steps) { stepRunner.runStepForBuildTarget(context, step, Optional.of(buildTarget)); } LOG.debug("Test steps did run for %s", buildTarget); eventBus.post(TestRuleEvent.finished(buildTarget)); return interpretResults.call(); }; return listeningExecutorService.submit(callable); } }