package hudson.plugins.nunit; import hudson.AbortException; import hudson.Extension; import hudson.Launcher; import hudson.Util; import hudson.FilePath.FileCallable; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.BuildListener; import hudson.model.Descriptor; import hudson.model.Result; import hudson.remoting.VirtualChannel; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Publisher; import hudson.tasks.Recorder; import hudson.tasks.junit.TestResult; import hudson.tasks.junit.TestResultAction; import hudson.tasks.test.TestResultProjectAction; import java.io.File; import java.io.IOException; import java.io.Serializable; import javax.xml.transform.TransformerException; import net.sf.json.JSONObject; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.types.FileSet; import org.kohsuke.stapler.StaplerRequest; /** * Class that records NUnit test reports into Hudson. * * @author Erik Ramfelt */ public class NUnitPublisher extends Recorder implements Serializable { private static final long serialVersionUID = 1L; private static transient final String PLUGIN_NUNIT = "/plugin/nunit/"; @Extension public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); private String testResultsPattern; private boolean debug = false; private boolean keepJUnitReports = false; private boolean skipJUnitArchiver = false; public NUnitPublisher(String testResultsPattern, boolean debug, boolean keepJUnitReports, boolean skipJUnitArchiver) { this.testResultsPattern = testResultsPattern; this.debug = debug; if (this.debug) { this.keepJUnitReports = keepJUnitReports; this.skipJUnitArchiver = skipJUnitArchiver; } } public String getTestResultsPattern() { return testResultsPattern; } public boolean getDebug() { return debug; } public boolean getKeepJunitReports() { return keepJUnitReports; } public boolean getSkipJunitArchiver() { return skipJUnitArchiver; } @Override public Action getProjectAction(AbstractProject<?,?> project) { TestResultProjectAction action = project.getAction(TestResultProjectAction.class); if (action == null) { return new TestResultProjectAction(project); } else { return null; } } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.BUILD; } @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { if (debug) { listener.getLogger().println("NUnit publisher running in debug mode."); } boolean result = true; try { listener.getLogger().println("Recording NUnit tests results"); NUnitArchiver transformer = new NUnitArchiver(listener, testResultsPattern, new NUnitReportTransformer()); result = build.getWorkspace().act(transformer); if (result) { if (skipJUnitArchiver) { listener.getLogger().println("Skipping feeding JUnit reports to JUnitArchiver"); } else { // Run the JUnit test archiver result = recordTestResult(NUnitArchiver.JUNIT_REPORTS_PATH + "/TEST-*.xml", build, listener); } if (keepJUnitReports) { listener.getLogger().println("Skipping deletion of temporary JUnit reports."); } else { build.getWorkspace().child(NUnitArchiver.JUNIT_REPORTS_PATH).deleteRecursive(); } } } catch (TransformerException te) { throw new AbortException("Could not read the XSL XML file. Please report this issue to the plugin author"); } return result; } /** * Record the test results into the current build. * @param junitFilePattern * @param build * @param listener * @return * @throws InterruptedException * @throws IOException */ private boolean recordTestResult(String junitFilePattern, AbstractBuild<?, ?> build, BuildListener listener) throws InterruptedException, IOException { TestResultAction existingAction = build.getAction(TestResultAction.class); TestResultAction action; try { final long buildTime = build.getTimestamp().getTimeInMillis(); TestResult existingTestResults = null; if (existingAction != null) { existingTestResults = existingAction.getResult(); } TestResult result = getTestResult(junitFilePattern, build, existingTestResults, buildTime); if (existingAction == null) { action = new TestResultAction(build, result, listener); } else { action = existingAction; action.setResult(result, listener); } if(result.getPassCount()==0 && result.getFailCount()==0){ throw new AbortException("None of the test reports contained any result"); } } catch (AbortException e) { if(build.getResult()==Result.FAILURE) // most likely a build failed before it gets to the test phase. // don't report confusing error message. return true; listener.getLogger().println(e.getMessage()); build.setResult(Result.FAILURE); return true; } if (existingAction == null) { build.getActions().add(action); } if(action.getResult().getFailCount()>0) build.setResult(Result.UNSTABLE); return true; } /** * Collect the test results from the files * @param junitFilePattern * @param build * @param existingTestResults existing test results to add results to * @param buildTime * @return a test result * @throws IOException * @throws InterruptedException */ private TestResult getTestResult(final String junitFilePattern, AbstractBuild<?, ?> build, final TestResult existingTestResults, final long buildTime) throws IOException, InterruptedException { TestResult result = build.getWorkspace().act(new FileCallable<TestResult>() { public TestResult invoke(File ws, VirtualChannel channel) throws IOException { FileSet fs = Util.createFileSet(ws,junitFilePattern); DirectoryScanner ds = fs.getDirectoryScanner(); String[] files = ds.getIncludedFiles(); if(files.length==0) { // no test result. Most likely a configuration error or fatal problem throw new AbortException("No test report files were found. Configuration error?"); } if (existingTestResults == null) { return new TestResult(buildTime, ds); } else { existingTestResults.parse(buildTime, ds); return existingTestResults; } } }); return result; } @Override public BuildStepDescriptor<Publisher> getDescriptor() { return DESCRIPTOR; } public static class DescriptorImpl extends BuildStepDescriptor<Publisher> { protected DescriptorImpl() { super(NUnitPublisher.class); } @Override public String getDisplayName() { return "Publish NUnit test result report"; } @Override public String getHelpFile() { return PLUGIN_NUNIT + "help.html"; } @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } @Override public Publisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { return new NUnitPublisher(req.getParameter("nunit_reports.pattern"), (req.getParameter("nunit_reports.debug") != null), (req.getParameter("nunit_reports.keepjunitreports") != null), (req.getParameter("nunit_reports.skipjunitarchiver") != null)); } } }