public static int runTests()

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;
  }