@Override
  public void perform(Run build, FilePath workspace, Launcher launcher, TaskListener listener)
      throws InterruptedException, IOException {
    listener.getLogger().println(Messages.JUnitResultArchiver_Recording());

    final String testResults = build.getEnvironment(listener).expand(this.testResults);

    TestResult result = parse(testResults, build, workspace, launcher, listener);

    synchronized (build) {
      // TODO can the build argument be omitted now, or is it used prior to the call to addAction?
      TestResultAction action = build.getAction(TestResultAction.class);
      boolean appending;
      if (action == null) {
        appending = false;
        action = new TestResultAction(build, result, listener);
      } else {
        appending = true;
        result.freeze(action);
        action.mergeResult(result, listener);
      }
      action.setHealthScaleFactor(getHealthScaleFactor()); // overwrites previous value if appending
      if (result.isEmpty()) {
        if (build.getResult() == Result.FAILURE) {
          // most likely a build failed before it gets to the test phase.
          // don't report confusing error message.
          return;
        }
        if (this.allowEmptyResults) {
          // User allow empty results
          listener.getLogger().println(Messages.JUnitResultArchiver_ResultIsEmpty());
          return;
        }
        // most likely a configuration error in the job - e.g. false pattern to match the JUnit
        // result files
        throw new AbortException(Messages.JUnitResultArchiver_ResultIsEmpty());
      }

      // TODO: Move into JUnitParser [BUG 3123310]
      List<Data> data = action.getData();
      if (testDataPublishers != null) {
        for (TestDataPublisher tdp : testDataPublishers) {
          Data d = tdp.contributeTestData(build, workspace, launcher, listener, result);
          if (d != null) {
            data.add(d);
          }
        }
      }

      if (appending) {
        build.save();
      } else {
        build.addAction(action);
      }

      if (action.getResult().getFailCount() > 0) build.setResult(Result.UNSTABLE);
    }
  }
  private void assertTestResults(FreeStyleBuild build) {
    TestResultAction testResultAction = build.getAction(TestResultAction.class);
    assertNotNull("no TestResultAction", testResultAction);

    TestResult result = testResultAction.getResult();
    assertNotNull("no TestResult", result);

    assertEquals("should have 1 failing test", 1, testResultAction.getFailCount());
    assertEquals("should have 1 failing test", 1, result.getFailCount());

    assertEquals("should have 132 total tests", 132, testResultAction.getTotalCount());
    assertEquals("should have 132 total tests", 132, result.getTotalCount());
  }
  /**
   * Verifies that we find the correct results for the following constellation.
   *
   * <ol>
   *   <li>Build with result
   *   <li>Build with no result
   *   <li>Baseline
   * </ol>
   *
   * @throws Exception the exception
   */
  @Test
  @SuppressWarnings("rawtypes")
  public void testHasPreviousResult() throws Exception {
    AbstractBuild withResult = mock(AbstractBuild.class);
    AbstractBuild noResult = mock(AbstractBuild.class);
    AbstractBuild baseline = mock(AbstractBuild.class);
    when(baseline.getPreviousBuild()).thenReturn(noResult);
    when(noResult.getPreviousBuild()).thenReturn(withResult);

    TestResultAction action = mock(TestResultAction.class);
    when(withResult.getAction(TestResultAction.class)).thenReturn(action);
    BuildResult result = mock(BuildResult.class);
    when(action.getResult()).thenReturn(result);
    AnnotationContainer container = mock(AnnotationContainer.class);
    when(result.getContainer()).thenReturn(container);
    BuildHistory history = createHistory(baseline);

    assertTrue("Build has no previous result", history.hasPreviousResult());
    assertSame("Build has wrong previous result", result, history.getPreviousResult());
    assertSame("Build has wrong reference result", container, history.getReferenceAnnotations());
  }
  /**
   * Verifies that we find the correct results for the following constellation.
   *
   * <ol>
   *   <li>Build with result, build result = SUCCESS
   *   <li>Build with no result
   *   <li>Build with result, build result = FAILURE
   *   <li>Build with no result
   *   <li>Baseline
   * </ol>
   *
   * @throws Exception the exception
   */
  @SuppressWarnings("rawtypes")
  @Test
  public void testHasReferenceResult() throws Exception {
    AbstractBuild withSuccessResult = mock(AbstractBuild.class);
    AbstractBuild noResult2 = mock(AbstractBuild.class);
    AbstractBuild withFailureResult = mock(AbstractBuild.class);
    AbstractBuild noResult1 = mock(AbstractBuild.class);
    AbstractBuild baseline = mock(AbstractBuild.class);
    when(baseline.getPreviousBuild()).thenReturn(noResult1);
    when(noResult1.getPreviousBuild()).thenReturn(withFailureResult);
    when(withFailureResult.getPreviousBuild()).thenReturn(noResult2);
    when(noResult2.getPreviousBuild()).thenReturn(withSuccessResult);

    TestResultAction failureAction = mock(TestResultAction.class);
    when(withFailureResult.getAction(TestResultAction.class)).thenReturn(failureAction);
    when(failureAction.isSuccessful()).thenReturn(false);
    BuildResult failureResult = mock(BuildResult.class);
    when(failureAction.getResult()).thenReturn(failureResult);

    TestResultAction successAction = mock(TestResultAction.class);
    when(withSuccessResult.getAction(TestResultAction.class)).thenReturn(successAction);
    when(successAction.isSuccessful()).thenReturn(true);
    BuildResult successResult = mock(BuildResult.class);
    AnnotationContainer container = mock(AnnotationContainer.class);
    when(successResult.getContainer()).thenReturn(container);
    when(successAction.getResult()).thenReturn(successResult);

    BuildHistory history = createHistory(baseline);

    assertTrue("Build has no previous result", history.hasPreviousResult());
    assertSame("Build has wrong previous result", failureResult, history.getPreviousResult());
    assertSame("Build has wrong reference result", container, history.getReferenceAnnotations());
  }