package hudson.plugins.fitnesse; import hudson.Extension; import hudson.Launcher; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Descriptor; import hudson.model.ModelObject; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import hudson.util.FormValidation; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.HashMap; import java.util.Map; import javax.servlet.ServletException; import net.sf.json.JSONObject; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; /** * Execute fitnesse tests, either by starting a new fitnesse instance * or by using a fitnesse instance running elsewhere. * * @author Tim Bacon */ public class FitnesseBuilder extends Builder { public static final String START_FITNESSE = "fitnesseStart"; public static final String FITNESSE_HOST = "fitnesseHost"; public static final String FITNESSE_PORT = "fitnessePort"; public static final String FITNESSE_PORT_REMOTE = "fitnessePortRemote"; public static final String FITNESSE_PORT_LOCAL = "fitnessePortLocal"; public static final String JAVA_OPTS = "fitnesseJavaOpts"; public static final String PATH_TO_JAR = "fitnessePathToJar"; public static final String PATH_TO_ROOT = "fitnessePathToRoot"; public static final String TARGET_PAGE = "fitnesseTargetPage"; public static final String TARGET_IS_SUITE = "fitnesseTargetIsSuite"; public static final String PATH_TO_RESULTS = "fitnessePathToXmlResultsOut"; public static final String HTTP_TIMEOUT = "fitnesseHttpTimeout"; public static final String JAVA_WORKING_DIRECTORY = "fitnesseJavaWorkingDirectory"; static final int _URL_READ_TIMEOUT_MILLIS = 60*1000; static final String _LOCALHOST = "localhost"; private Map<String, String> options; @DataBoundConstructor public FitnesseBuilder(Map<String, String> options) { // Use n,v pairs to ease future extension this.options = options; } private String getOption(String key, String valueIfKeyNotFound) { if (options.containsKey(key)) { String value = options.get(key); if (value!=null && !"".equals(value)) return value; } return valueIfKeyNotFound; } /** * referenced in config.jelly */ public boolean getFitnesseStart() { return Boolean.parseBoolean(getOption(START_FITNESSE, "False")); } /** * referenced in config.jelly */ public String getFitnesseHost() { if (getFitnesseStart()) return _LOCALHOST; return getOption(FITNESSE_HOST, "unknown_host"); } /** * referenced in config.jelly */ public String getFitnesseJavaOpts() { return getOption(JAVA_OPTS, ""); } /** * referenced in config.jelly */ public String getFitnesseJavaWorkingDirectory() { String fitnessePathToJar = getFitnessePathToJar(), fitnesseJarDir = ""; if (!"".equals(fitnessePathToJar)) { File jarFile = new File(fitnessePathToJar); if (jarFile.exists()) { fitnesseJarDir = jarFile.getParentFile().getAbsolutePath(); } else { fitnesseJarDir = jarFile.getParent(); if (fitnesseJarDir == null) fitnesseJarDir = ""; } } return getOption(JAVA_WORKING_DIRECTORY, fitnesseJarDir); } /** * referenced in config.jelly */ public int getFitnessePort() { return Integer.parseInt( getOption(FITNESSE_PORT_REMOTE, getOption(FITNESSE_PORT_LOCAL, getOption(FITNESSE_PORT, "-1")))); } /** * referenced in config.jelly */ public String getFitnessePathToJar() { return getOption(PATH_TO_JAR, "fitnesse.jar"); } /** * referenced in config.jelly */ public String getFitnessePathToRoot() { return getOption(PATH_TO_ROOT, "FitNesseRoot"); } /** * referenced in config.jelly */ public String getFitnesseTargetPage() { return getOption(TARGET_PAGE, ""); } /** * referenced in config.jelly */ public boolean getFitnesseTargetIsSuite() { return Boolean.parseBoolean(getOption(TARGET_IS_SUITE, "False")); } /** * referenced in config.jelly */ public String getFitnessePathToXmlResultsOut() { return getOption(PATH_TO_RESULTS, "fitnesse-results.xml"); } /** * referenced in config.jelly */ public int getFitnesseHttpTimeout() { return Integer.parseInt(getOption(HTTP_TIMEOUT, String.valueOf(_URL_READ_TIMEOUT_MILLIS))); } /** * {@link Builder} */ @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { PrintStream logger = listener.getLogger(); logger.println(getClass().getName() + ": " + options); FitnesseExecutor fitnesseExecutor = new FitnesseExecutor(this); return fitnesseExecutor.execute(build, launcher, logger, build.getEnvironment(listener)); } /** * {@link Builder} */ @Override public DescriptorImpl getDescriptor() { return (DescriptorImpl)super.getDescriptor(); } /** * See <tt>src/main/resources/hudson/plugins/fitnesse/FitnesseBuilder/config.jelly</tt> */ @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Builder> { public FormValidation doCheckFitnesseHost(@QueryParameter String value) throws IOException, ServletException { if (value.length()==0) return FormValidation.error("Please specify the host of the fitnesse instance."); return FormValidation.ok(); } public FormValidation doCheckFitnessePort(@QueryParameter String value) throws IOException, ServletException { if (value.length()==0) return FormValidation.error("Please specify the fitnesse port."); try { int intValue = Integer.parseInt(value); if (intValue < 1) return FormValidation.error("Port must be a positive integer."); } catch (NumberFormatException e) { return FormValidation.error("Port must be a number."); } return FormValidation.ok(); } public FormValidation doCheckFitnesseJavaOpts(@QueryParameter String value) throws IOException, ServletException { return FormValidation.ok(); } public FormValidation doCheckFitnesseJavaWorkingDirectory(@QueryParameter String value) throws IOException, ServletException { if (value.length()==0) return FormValidation.ok("Location of fitnesse.jar will be used as java working directory."); if (!new File(value).exists()) return FormValidation.error("Path does not exist."); return FormValidation.ok(); } public FormValidation doCheckFitnessePathToJar(@QueryParameter String value) throws IOException, ServletException { if (value.length()==0) return FormValidation.error("Please specify the path to 'fitnesse.jar'."); if (!value.endsWith("fitnesse.jar") && new File(value, "fitnesse.jar").exists()) return FormValidation.warning("Path does not end with 'fitnesse.jar': is that correct?"); return FormValidation.ok(); } public FormValidation doCheckFitnessePathToRoot(@QueryParameter String value) throws IOException, ServletException { if (value.length()==0) return FormValidation.error("Please specify the location of 'FitNesseRoot'."); if (!value.endsWith("FitNesseRoot") && new File(value, "FitNesseRoot").exists()) return FormValidation.warning("Path does not end with 'FitNesseRoot': is that correct?"); return FormValidation.ok(); } public FormValidation doCheckFitnesseTargetPage(@QueryParameter String value) throws IOException, ServletException { if (value.length()==0) return FormValidation.error("Please specify a page to execute."); return FormValidation.ok(); } public FormValidation doCheckFitnesseTargetIsSuite(@QueryParameter String value) throws IOException, ServletException { return FormValidation.ok(); } public FormValidation doCheckFitnesseHttpTimeout(@QueryParameter String value) throws IOException, ServletException { if (value.length()==0) return FormValidation.ok("Default timeout " + _URL_READ_TIMEOUT_MILLIS + "ms will be used."); try { if (Integer.parseInt(value) < 0) return FormValidation.error("Timeout must be a positive integer."); } catch (NumberFormatException e) { return FormValidation.error("Timeout must be a number."); } return FormValidation.ok(); } public FormValidation doCheckFitnessePathToXmlResultsOut(@QueryParameter String value) throws IOException, ServletException { if (value.length()==0) return FormValidation.error("Please specify where to write fitnesse results to."); if (!value.endsWith("xml")) return FormValidation.warning("File does not end with 'xml': is that correct?"); return FormValidation.ok(); } /** * {@link BuildStepDescriptor} */ public boolean isApplicable(Class<? extends AbstractProject> aClass) { // indicates that this builder can be used with all kinds of project types return true; } /** * {@link ModelObject} */ @Override public String getDisplayName() { return "Execute fitnesse tests"; } /** * {@link Descriptor} * config.jelly uses hide-able fields so take control of instance creation */ @Override public FitnesseBuilder newInstance(StaplerRequest req, JSONObject formData) throws FormException { String startFitnesseValue = formData.getJSONObject(START_FITNESSE).getString("value"); if (Boolean.parseBoolean(startFitnesseValue)) { return newFitnesseBuilder(startFitnesseValue, collectFormData(formData, new String[] { JAVA_OPTS, JAVA_WORKING_DIRECTORY, PATH_TO_JAR, PATH_TO_ROOT, FITNESSE_PORT_LOCAL, TARGET_PAGE, TARGET_IS_SUITE, HTTP_TIMEOUT, PATH_TO_RESULTS }) ); } return newFitnesseBuilder(startFitnesseValue, collectFormData(formData, new String[] { FITNESSE_HOST, FITNESSE_PORT_REMOTE, TARGET_PAGE, TARGET_IS_SUITE, HTTP_TIMEOUT, PATH_TO_RESULTS }) ); } private FitnesseBuilder newFitnesseBuilder(String startFitnesseValue, Map<String, String> collectedFormData) { collectedFormData.put(START_FITNESSE, startFitnesseValue); return new FitnesseBuilder(collectedFormData); } private Map<String, String> collectFormData(JSONObject formData, String[] keys) { Map<String, String> targetElements = new HashMap<String, String>(); for (String key: keys) { if (formData.has(key)) { targetElements.put(key, formData.getString(key)); } else { targetElements.put(key, formData.getJSONObject(START_FITNESSE).getString(key)); } } return targetElements; } } }