package hudson.plugins.jwsdp_sqe;
import hudson.Extension;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.remoting.VirtualChannel;
import hudson.model.BuildListener;
import hudson.model.Result;
import hudson.model.Action;
import hudson.model.Build;
import hudson.model.Project;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Publisher;
import hudson.tasks.Recorder;
import hudson.tasks.test.TestResultProjectAction;
import hudson.util.IOException2;
import net.sf.json.JSONObject;
import org.apache.tools.ant.types.FileSet;
import org.kohsuke.stapler.StaplerRequest;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.SAXException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
/**
* Collects SQE test reports and convert them into JUnit format.
*
* @author Kohsuke Kawaguchi
*/
public class SQETestResultPublisher extends Recorder implements Serializable {
private final String includes;
/**
* Flag to capture if test should be considered as executable TestObject
*/
boolean considerTestAsTestObject = false;
public SQETestResultPublisher(String includes, boolean considerTestAsTestObject) {
this.includes = includes;
this.considerTestAsTestObject = considerTestAsTestObject;
}
/**
* Ant "<fileset @includes="..." /> pattern to specify SQE XML files
*/
public String getIncludes() {
return includes;
}
public boolean getConsiderTestAsTestObject() {
return considerTestAsTestObject;
}
@Override
public Action getProjectAction(AbstractProject<?,?> project) {
return new TestResultProjectAction(project);
}
public BuildStepMonitor getRequiredMonitorService() {
return BuildStepMonitor.BUILD;
}
/**
* Indicates an orderly abortion of the processing.
*/
private static final class AbortException extends RuntimeException {
public AbortException(String s) {
super(s);
}
}
@Override
public boolean perform(AbstractBuild<?,?> build, Launcher launcher, final BuildListener listener) throws IOException, InterruptedException {
// Should always be a Build because isApplicable requires Project type:
if (!(build instanceof Build)) return true;
final long buildTime = build.getTimestamp().getTimeInMillis();
listener.getLogger().println("Collecting JWSDP SQE reports");
// target directory
File dataDir = SQETestAction.getDataDir(build);
dataDir.mkdirs();
final FilePath target = new FilePath(dataDir);
try {
build.getWorkspace().act(new FileCallable<Void>() {
public Void invoke(File ws, VirtualChannel channel) throws IOException {
FileSet fs = new FileSet();
org.apache.tools.ant.Project p = new org.apache.tools.ant.Project();
fs.setProject(p);
fs.setDir(ws);
fs.setIncludes(includes);
String[] includedFiles = fs.getDirectoryScanner(p).getIncludedFiles();
if(includedFiles.length==0)
// no test result. Most likely a configuration error or fatal problem
throw new AbortException("No SQE test report files were found. Configuration error?");
int counter=0;
SAXParser parser = createParser();
// archive report files
for (String file : includedFiles) {
File src = new File(ws, file);
if(src.lastModified()<buildTime) {
listener.getLogger().println("Skipping "+src+" because it's not up to date");
continue; // not up to date.
}
// verify that this is indeed an XML file, while we still know the original file name.
try {
parser.parse(src,new DefaultHandler());
} catch (SAXException e) {
listener.getLogger().println("Skipping "+src+" because it doesn't look like an XML file");
continue;
}
try {
new FilePath(src).copyTo(target.child("report"+(counter++)+".xml"));
} catch (InterruptedException e) {
throw new IOException2("aborted while copying "+src,e);
}
}
return null;
}
private SAXParser createParser() throws IOException {
SAXParser parser;
try {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
parser = spf.newSAXParser();
} catch (ParserConfigurationException e) {
throw new IOException2(e);
} catch (SAXException e) {
throw new IOException2(e);
}
return parser;
}
private static final long serialVersionUID = 1L;
});
} 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; /// but this is not a fatal error
}
// Type checked above, so cast is ok:
SQETestAction action = new SQETestAction((Build)build, listener, considerTestAsTestObject);
build.getActions().add(action);
Report r = action.getResult();
if(r.getTotalCount()==0) {
listener.getLogger().println("Test reports were found but none of them are new. Did tests run?");
// no test result. Most likely a configuration error or fatal problem
build.setResult(Result.FAILURE);
}
if(r.getFailCount()>0)
build.setResult(Result.UNSTABLE);
return true;
}
private static final long serialVersionUID = 1L;
@Override
public BuildStepDescriptor<Publisher> getDescriptor() {
return DescriptorImpl.DESCRIPTOR;
}
public static class DescriptorImpl extends BuildStepDescriptor<Publisher> {
@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
public DescriptorImpl() {
super(SQETestResultPublisher.class);
}
public String getDisplayName() {
return "Publish SQE test result report";
}
@Override
public String getHelpFile() {
return "/plugin/jwsdp-sqe/help.html";
}
@Override
public Publisher newInstance(StaplerRequest req, JSONObject formData) {
return new SQETestResultPublisher(req.getParameter("sqetest_includes"),(req.getParameter("sqetest_testobject")!=null));
}
@Override
public boolean isApplicable(Class<? extends AbstractProject> jobType) {
return Project.class.isAssignableFrom(jobType);
}
}
}