package hudson.plugins.textfinder; import hudson.FilePath.FileCallable; import hudson.Launcher; import hudson.Util; import hudson.Extension; import static hudson.Util.fixEmpty; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Result; import hudson.remoting.RemoteOutputStream; import hudson.remoting.VirtualChannel; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Publisher; import hudson.tasks.Recorder; import hudson.util.FormValidation; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.types.FileSet; import org.apache.commons.io.IOUtils; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import javax.servlet.ServletException; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.io.Serializable; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; /** * Text Finder plugin for Hudson. Search in the workspace using a regular * expression and determine build outcome based on matches. * * @author Santiago.PericasGeertsen@sun.com */ public class TextFinderPublisher extends Recorder implements Serializable { public final String fileSet; public final String regexp; public final boolean succeedIfFound; public final boolean unstableIfFound; /** * True to also scan the whole console output */ public final boolean alsoCheckConsoleOutput; @DataBoundConstructor public TextFinderPublisher(String fileSet, String regexp, boolean succeedIfFound, boolean unstableIfFound, boolean alsoCheckConsoleOutput) { this.fileSet = Util.fixEmpty(fileSet.trim()); this.regexp = regexp; this.succeedIfFound = succeedIfFound; this.unstableIfFound = unstableIfFound; this.alsoCheckConsoleOutput = alsoCheckConsoleOutput; // Attempt to compile regular expression try { Pattern.compile(regexp); } catch (PatternSyntaxException e) { // falls through } } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.BUILD; } public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { findText(build, listener.getLogger()); return true; } /** * Indicates an orderly abortion of the processing. */ private static final class AbortException extends RuntimeException { } private void findText(AbstractBuild build, PrintStream logger) throws IOException, InterruptedException { try { boolean foundText = false; if(alsoCheckConsoleOutput) { logger.println("Checking console output"); foundText |= checkFile(build.getLogFile(), compilePattern(logger), logger, true); } else { // printing this when checking console output will cause the plugin // to find this line, which would be pointless. // doing this only when fileSet!=null to avoid logger.println("Checking " + regexp); } final RemoteOutputStream ros = new RemoteOutputStream(logger); if(fileSet!=null) { foundText |= build.getWorkspace().act(new FileCallable<Boolean>() { public Boolean invoke(File ws, VirtualChannel channel) throws IOException { PrintStream logger = new PrintStream(ros); // Collect list of files for searching FileSet fs = new FileSet(); org.apache.tools.ant.Project p = new org.apache.tools.ant.Project(); fs.setProject(p); fs.setDir(ws); fs.setIncludes(fileSet); DirectoryScanner ds = fs.getDirectoryScanner(p); // Any files in the final set? String[] files = ds.getIncludedFiles(); if (files.length == 0) { logger.println("Hudson Text Finder: File set '" + fileSet + "' is empty"); throw new AbortException(); } Pattern pattern = compilePattern(logger); boolean foundText = false; for (String file : files) { File f = new File(ws, file); if (!f.exists()) { logger.println("Hudson Text Finder: Unable to" + " find file '" + f + "'"); continue; } if (!f.canRead()) { logger.println("Hudson Text Finder: Unable to" + " read from file '" + f + "'"); continue; } foundText |= checkFile(f, pattern, logger, false); } return foundText; } }); } if (foundText != succeedIfFound) build.setResult(unstableIfFound ? Result.UNSTABLE : Result.FAILURE); } catch (AbortException e) { // no test file found build.setResult(Result.UNSTABLE); } } /** * Search the given regexp pattern in the file. * * @param abortAfterFirstHit * true to return immediately as soon as the first hit is found. this is necessary * when we are scanning the console output, because otherwise we'll loop forever. */ private boolean checkFile(File f, Pattern pattern, PrintStream logger, boolean abortAfterFirstHit) { boolean logFilename = true; boolean foundText = false; BufferedReader reader=null; try { // Assume default encoding and text files String line; reader = new BufferedReader(new FileReader(f)); while ((line = reader.readLine()) != null) { Matcher matcher = pattern.matcher(line); if (matcher.find()) { if (logFilename) {// first occurrence logger.println(f + ":"); logFilename = false; } logger.println(line); foundText = true; if(abortAfterFirstHit) return true; } } } catch (IOException e) { logger.println("Hudson Text Finder: Error reading" + " file '" + f + "' -- ignoring"); } finally { IOUtils.closeQuietly(reader); } return foundText; } private Pattern compilePattern(PrintStream logger) { Pattern pattern; try { pattern = Pattern.compile(regexp); } catch (PatternSyntaxException e) { logger.println("Hudson Text Finder: Unable to compile" + "regular expression '" + regexp + "'"); throw new AbortException(); } return pattern; } @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Publisher> { public String getDisplayName() { return "Hudson Text Finder"; } @Override public String getHelpFile() { return "/plugin/text-finder/help.html"; } public boolean isApplicable(Class<? extends AbstractProject> jobType) { return true; } /** * Checks the regular expression validity. */ public FormValidation doCheckRegexp(@QueryParameter String value) throws IOException, ServletException { value = fixEmpty(value); if(value==null) return FormValidation.ok(); // not entered yet try { Pattern.compile(value); return FormValidation.ok(); } catch (PatternSyntaxException e) { return FormValidation.error(e.getMessage()); } } } private static final long serialVersionUID = 1L; }