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