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