package webtestpresenter; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Action; import hudson.model.BuildListener; import hudson.model.DirectoryBrowserSupport; import hudson.model.Result; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Notifier; import hudson.tasks.Publisher; import hudson.util.FormValidation; import java.io.File; import java.io.IOException; import java.io.Serializable; import net.sf.json.JSONObject; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; /** * Publishes links to canoo webtest results. * * @see http://webtest.canoo.com * * @author Adam Ambrose */ public class WebtestPublisher extends Notifier implements Serializable { // Uncomment for debugging: //private static final Logger LOG = // Logger.getLogger(WebtestPublisher.class.getName()); private static final String WEBTEST_REPORTS = "webtestReports"; private static final long serialVersionUID = 1L; private final String webtestResultsSrcDir; WebtestPublisher(String webtestResultsSrcDir) { this.webtestResultsSrcDir= webtestResultsSrcDir; } /** * We'll use this from the <tt>config.jelly</tt>. */ public String getWebtestResultsSrcDir() { return webtestResultsSrcDir; } /** * Gets the directory where the latest Webtest results are stored for the * given project. */ private static FilePath getWebtestReportDir(AbstractBuild<?,?> build) { //return new File(build.getArtifactsDir(), "webtestReports"); return new FilePath( new File(build.getRootDir(), WEBTEST_REPORTS)); } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.BUILD; } public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException { // TODO: does time check need to account for running on slave? // see hudson.tasks.junit.JUnitResultsArchiver final long buildTime = build.getTimestamp().getTimeInMillis(); FilePath webtestResults = build.getWorkspace().child(webtestResultsSrcDir); FilePath target = getWebtestReportDir(build); Action action = null; try { if (!webtestResults.exists()) { return true; } // Check if the results are stale. If not, copy them over // anyways. if (build.getResult().isWorseOrEqualTo(Result.FAILURE) && buildTime + 1000 /*error margin*/ > webtestResults.lastModified()) { listener.getLogger().println( "Webtest reports are stale. Not publishing."); return true; } StringBuffer msgBuf = new StringBuffer("Publishing webtest results: "); msgBuf.append(webtestResultsSrcDir); msgBuf.append(" to: "); msgBuf.append(target); listener.getLogger().println(msgBuf.toString()); webtestResults.copyRecursiveTo("**/*", target); action = new WebtestReportAction(build); } catch (IOException e) { Util.displayIOException(e,listener); String err = "Unable to copy Webtest results from " + webtestResults + " to " + target; e.printStackTrace(listener.fatalError(err)); build.setResult(Result.FAILURE); } build.getActions().add(action); return true; } public Action getProjectAction(AbstractBuild<?,?> build) { return new WebtestReportAction(build); } /** * Descriptor for {@link WebtestPublisher}. Used as a singleton. * The class is marked as public so that it can be accessed from views. * * <p> * See <tt>views/hudson/plugins/hello_world/HelloWorldBuilder/*.jelly</tt> * for the actual HTML fragment for the configuration screen. */ @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> { /** * To persist global configuration information, * simply store it in a field and call save(). * * <p> * If you don't want fields to be persisted, use <tt>transient</tt>. */ public static final String CONFIG_PARAM = "webtest_publisher.webtestResultsSrc"; private String webtestResultsSrc; public DescriptorImpl() { super(WebtestPublisher.class); } /** * This human readable name is used in the configuration screen. */ public String getDisplayName() { return "Publish Webtest results"; } public String getWebtestResultsSrc() { return webtestResultsSrc; } @Override public boolean configure(StaplerRequest req, JSONObject formData) throws FormException { // to persist global configuration information, // set that to properties and call save(). webtestResultsSrc = req.getParameter(CONFIG_PARAM); save(); return super.configure(req, formData); } /** * Performs on-the-fly validation on the file mask wildcard. */ public FormValidation doCheck(@AncestorInPath AbstractProject project, @QueryParameter String value) throws IOException { return FilePath.validateFileMask(project.getSomeWorkspace(), value); } @Override public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } /** * Creates a new instance of {@link WebtestPublisher} from a submitted * form. */ @Override public WebtestPublisher newInstance(StaplerRequest req, JSONObject formData) throws FormException { // see config.jelly and you'll find "hello_world.name" form entry. return new WebtestPublisher(req.getParameter(CONFIG_PARAM)); } } public static final class WebtestReportAction implements Action { private static final long serialVersionUID = -3578312514160132918L; /** * Project that owns this action. */ public final AbstractBuild<?,?> build; public WebtestReportAction(AbstractBuild<?,?> build) { this.build = build; } public String getUrlName() { return "webtestResults"; } public String getDisplayName() { return "Webtest Results"; } public String getIconFileName() { return "clipboard.gif"; } public Object getTarget() { if (build != null) { return getWebtestReportDir(build); } return null; } public DirectoryBrowserSupport doDynamic(StaplerRequest req, StaplerResponse rsp) { if(this.build != null) { return new DirectoryBrowserSupport(this, getWebtestReportDir(this.build), "webtest", "clipboard.gif", false); } return null; } } }