package hudson.plugins.testng; import java.io.IOException; import java.io.PrintStream; import java.io.Serializable; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import hudson.FilePath; import hudson.model.Action; import hudson.model.Api; import hudson.model.Run; import hudson.plugins.testng.parser.ResultsParser; import hudson.plugins.testng.results.MethodResult; import hudson.plugins.testng.results.TestNGResult; import hudson.tasks.junit.CaseResult; import hudson.tasks.test.AbstractTestResultAction; import java.util.Collection; import java.util.Collections; import jenkins.tasks.SimpleBuildStep; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; /** * TestNG build action that exposes the results per build * * @author nullin * @since v1.0 */ public class TestNGTestResultBuildAction extends AbstractTestResultAction implements Serializable, SimpleBuildStep.LastBuildAction { private static final Logger LOGGER = Logger.getLogger(TestNGTestResultBuildAction.class.getName()); /** * Unique identifier for this class. */ private static final long serialVersionUID = 31415926L; /** * try and be good citizen. We don't want to hold this in memory. * We also don't want to save this to build XML as we already save testng Reports */ private transient Reference<TestNGResult> testngResultRef; /* * Cache test counts to speed up loading of graphs */ protected Integer passCount; // null if uncomputed protected int failCount; protected int skipCount; private final boolean escapeTestDescp; private final boolean escapeExceptionMsg; private final boolean showFailedBuilds; public TestNGTestResultBuildAction(TestNGResult testngResults, boolean escapeTestDescp, boolean escapeExceptionMsg, boolean showFailedBuilds) { if (testngResults != null) { this.testngResultRef = new WeakReference<TestNGResult>(testngResults); //initialize the cached values when TestNGBuildAction is instantiated count(testngResults); } this.escapeTestDescp = escapeTestDescp; this.escapeExceptionMsg = escapeExceptionMsg; this.showFailedBuilds = showFailedBuilds; } private void count(TestNGResult testngResults) { this.passCount = testngResults.getPassCount(); this.failCount = testngResults.getFailCount(); this.skipCount = testngResults.getSkipCount(); } private void countAndSave(TestNGResult testngResults) { int savedPassCount = passCount != null ? passCount : -1; int savedFailCount = failCount; int savedSkipCount = skipCount; count(testngResults); if (passCount != savedPassCount || failCount != savedFailCount || skipCount != savedSkipCount) { LOGGER.log(Level.FINE, "saving {0}", owner); try { owner.save(); } catch (IOException x) { LOGGER.log(Level.WARNING, "failed to save " + owner, x); } } } private void countAsNeeded() { if (passCount == null) { countAndSave(getResult()); } } @Override public TestNGResult getResult() { return getResult(super.run); } public TestNGResult getResult(Run build) { TestNGResult tr = testngResultRef != null ? testngResultRef.get() : null; if (tr == null) { tr = loadResults(build, null); countAndSave(tr); testngResultRef = new WeakReference<TestNGResult>(tr); } return tr; } static TestNGResult loadResults(Run<?, ?> owner, PrintStream logger) { LOGGER.log(Level.FINE, "loading results for {0}", owner); FilePath testngDir = Publisher.getTestNGReport(owner); FilePath[] paths = null; try { paths = testngDir.list("testng-results*.xml"); } catch (Exception e) { //do nothing } if (paths == null) { TestNGResult tr = new TestNGResult(); tr.setRun(owner); return tr; } ResultsParser parser = new ResultsParser(logger); TestNGResult result = parser.parse(paths); result.setRun(owner); return result; } /** * {@inheritDoc} */ @Override public String getIconFileName() { return PluginImpl.ICON_FILE_NAME; } @Override public int getFailCount() { countAsNeeded(); return failCount; } @Override public int getSkipCount() { countAsNeeded(); return skipCount; } @Override public int getTotalCount() { countAsNeeded(); return failCount + passCount + skipCount; } /** * {@inheritDoc} */ @Override public String getDisplayName() { return PluginImpl.DISPLAY_NAME; } /** * {@inheritDoc} */ @Override public String getUrlName() { return PluginImpl.URL; } public Object getDynamic(String token, StaplerRequest req, StaplerResponse rsp) { return getResult().getDynamic(token, req, rsp); } @Override public Api getApi() { return new Api(getResult()); } public List<CaseResult> getFailedTests() { class HackyCaseResult extends CaseResult { private MethodResult methodResult; public HackyCaseResult(MethodResult methodResult) { super(null, methodResult.getDisplayName(), methodResult.getErrorStackTrace()); this.methodResult = methodResult; } public Status getStatus() { //We don't calculate age of results currently //so, can't state if the failure is a regression or not return Status.FAILED; } public String getClassName() { return methodResult.getClassName(); } public String getDisplayName() { return methodResult.getDisplayName(); } public String getErrorDetails() { return methodResult.getErrorDetails(); } } List< CaseResult > results = new ArrayList<CaseResult>(getFailCount()); for (MethodResult methodResult : getResult().getFailedTests()) { results.add(new HackyCaseResult(methodResult)); } return results; } @Override public Collection<? extends Action> getProjectActions() { return Collections.singleton(new TestNGProjectAction(run.getParent(), escapeTestDescp, escapeExceptionMsg, showFailedBuilds)); } }