diff --git a/src/main/java/hudson/plugins/textfinder/TextFinderModel.java b/src/main/java/hudson/plugins/textfinder/TextFinderModel.java new file mode 100644 index 0000000..efafc26 --- /dev/null +++ b/src/main/java/hudson/plugins/textfinder/TextFinderModel.java @@ -0,0 +1,123 @@ +package hudson.plugins.textfinder; + +import hudson.Extension; +import hudson.Util; +import hudson.model.AbstractDescribableImpl; +import hudson.model.Descriptor; +import java.io.Serializable; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; + +public final class TextFinderModel extends AbstractDescribableImpl + implements Serializable { + private static final long serialVersionUID = 1L; + + private String fileSet; + private final String regexp; + private boolean succeedIfFound; + private boolean unstableIfFound; + private boolean notBuiltIfFound; + /** True to also scan the whole console output */ + private boolean alsoCheckConsoleOutput; + + @DataBoundConstructor + public TextFinderModel(String regexp) { + this.regexp = regexp; + // Attempt to compile regular expression + try { + Pattern.compile(regexp); + } catch (PatternSyntaxException e) { + // falls through + } + } + + @DataBoundSetter + public void setFileSet(String fileSet) { + if (fileSet == null) { + this.fileSet = null; + } else { + if (fileSet.trim().isEmpty()) { + this.fileSet = null; + } else { + this.fileSet = fileSet.trim(); + } + } + } + + @DataBoundSetter + public void setSucceedIfFound(boolean succeedIfFound) { + this.succeedIfFound = succeedIfFound; + } + + @DataBoundSetter + public void setUnstableIfFound(boolean unstableIfFound) { + this.unstableIfFound = unstableIfFound; + } + + @DataBoundSetter + public void setNotBuiltIfFound(boolean notBuiltIfFound) { + this.notBuiltIfFound = notBuiltIfFound; + } + + @DataBoundSetter + public void setAlsoCheckConsoleOutput(boolean alsoCheckConsoleOutput) { + this.alsoCheckConsoleOutput = alsoCheckConsoleOutput; + } + + @Deprecated + private TextFinderModel( + String fileSet, + String regexp, + boolean succeedIfFound, + boolean unstableIfFound, + boolean alsoCheckConsoleOutput, + boolean notBuiltIfFound) { + this.fileSet = fileSet != null ? Util.fixEmpty(fileSet.trim()) : null; + this.regexp = regexp; + this.succeedIfFound = succeedIfFound; + this.unstableIfFound = unstableIfFound; + this.alsoCheckConsoleOutput = alsoCheckConsoleOutput; + this.notBuiltIfFound = notBuiltIfFound; + + // Attempt to compile regular expression + try { + Pattern.compile(regexp); + } catch (PatternSyntaxException e) { + // falls through + } + } + + public String getFileSet() { + return fileSet; + } + + public String getRegexp() { + return regexp; + } + + public boolean isSucceedIfFound() { + return succeedIfFound; + } + + public boolean isUnstableIfFound() { + return unstableIfFound; + } + + public boolean isNotBuiltIfFound() { + return notBuiltIfFound; + } + + public boolean isAlsoCheckConsoleOutput() { + return alsoCheckConsoleOutput; + } + + @Extension + public static class DescriptorImpl extends Descriptor { + @Override + public String getDisplayName() { + return "Text Finder"; + } + } +} diff --git a/src/main/java/hudson/plugins/textfinder/TextFinderPublisher.java b/src/main/java/hudson/plugins/textfinder/TextFinderPublisher.java index 22c5901..1ce3d2e 100644 --- a/src/main/java/hudson/plugins/textfinder/TextFinderPublisher.java +++ b/src/main/java/hudson/plugins/textfinder/TextFinderPublisher.java @@ -28,11 +28,14 @@ import java.io.Reader; import java.io.Serializable; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; import javax.servlet.ServletException; import jenkins.MasterToSlaveFileCallable; +import jenkins.model.Jenkins; import jenkins.tasks.SimpleBuildStep; import org.apache.tools.ant.DirectoryScanner; import org.apache.tools.ant.Project; @@ -50,18 +53,15 @@ */ public class TextFinderPublisher extends Recorder implements Serializable, SimpleBuildStep { - public String fileSet; - public final String regexp; - public boolean succeedIfFound; - public boolean unstableIfFound; - public boolean notBuiltIfFound; - /** True to also scan the whole console output */ - public boolean alsoCheckConsoleOutput; + /** This is the primary text finder in the configuration. */ + private final TextFinderModel primaryTextFinder; + + /** Additional text finder configurations are stored here. */ + private final List additionalTextFinders = new ArrayList<>();; @DataBoundConstructor public TextFinderPublisher(String regexp) { - this.regexp = regexp; - + this.primaryTextFinder = new TextFinderModel(regexp); // Attempt to compile regular expression try { Pattern.compile(regexp); @@ -70,43 +70,64 @@ public TextFinderPublisher(String regexp) { } } + /** + * @param fileSet Kept for backward compatibility with old configuration. + * @param regexp Kept for backward compatibility with old configuration. + * @param succeedIfFound Kept for backward compatibility with old configuration. + * @param unstableIfFound Kept for backward compatibility with old configuration. + * @param notBuiltIfFound Kept for backward compatibility with old configuration. + * @param alsoCheckConsoleOutput Kept for backward compatibility with old configuration. + * @param additionalTextFinders configuration for additional textFinders + */ @Deprecated - public TextFinderPublisher( + private TextFinderPublisher( String fileSet, String regexp, boolean succeedIfFound, boolean unstableIfFound, - boolean alsoCheckConsoleOutput) { - this(regexp); - this.fileSet = Util.fixEmpty(fileSet.trim()); - this.succeedIfFound = succeedIfFound; - this.unstableIfFound = unstableIfFound; - this.alsoCheckConsoleOutput = alsoCheckConsoleOutput; + boolean notBuiltIfFound, + boolean alsoCheckConsoleOutput, + List additionalTextFinders) { + this.primaryTextFinder = new TextFinderModel(regexp); + this.primaryTextFinder.setFileSet(Util.fixEmpty(fileSet != null ? fileSet.trim() : "")); + this.primaryTextFinder.setSucceedIfFound(succeedIfFound); + this.primaryTextFinder.setUnstableIfFound(unstableIfFound); + this.primaryTextFinder.setAlsoCheckConsoleOutput(alsoCheckConsoleOutput); + this.primaryTextFinder.setNotBuiltIfFound(notBuiltIfFound); + this.setAdditionalTextFinders(additionalTextFinders); } @DataBoundSetter public void setFileSet(String fileSet) { - this.fileSet = Util.fixEmpty(fileSet.trim()); + this.primaryTextFinder.setFileSet(fileSet); } @DataBoundSetter public void setSucceedIfFound(boolean succeedIfFound) { - this.succeedIfFound = succeedIfFound; + this.primaryTextFinder.setSucceedIfFound(succeedIfFound); } @DataBoundSetter public void setUnstableIfFound(boolean unstableIfFound) { - this.unstableIfFound = unstableIfFound; + this.primaryTextFinder.setUnstableIfFound(unstableIfFound); } @DataBoundSetter public void setNotBuiltIfFound(boolean notBuiltIfFound) { - this.notBuiltIfFound = notBuiltIfFound; + this.primaryTextFinder.setNotBuiltIfFound(notBuiltIfFound); } @DataBoundSetter public void setAlsoCheckConsoleOutput(boolean alsoCheckConsoleOutput) { - this.alsoCheckConsoleOutput = alsoCheckConsoleOutput; + this.primaryTextFinder.setAlsoCheckConsoleOutput(alsoCheckConsoleOutput); + } + + @DataBoundSetter + public void setAdditionalTextFinders(List additionalTextFinders) { + this.additionalTextFinders.clear(); + if (additionalTextFinders != null && !additionalTextFinders.isEmpty()) { + this.additionalTextFinders.addAll(additionalTextFinders); + } } @Override @@ -117,56 +138,64 @@ public BuildStepMonitor getRequiredMonitorService() { @Override public void perform(Run run, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException, IOException { - findText(run, workspace, listener); + findText(primaryTextFinder, run, workspace, listener); + for (TextFinderModel additionalTextFinder : additionalTextFinders) { + findText(additionalTextFinder, run, workspace, listener); + } } /** Indicates an orderly abortion of the processing. */ private static final class AbortException extends RuntimeException {} - private void findText(Run run, FilePath workspace, TaskListener listener) + private void findText( + TextFinderModel textFinder, Run run, FilePath workspace, TaskListener listener) throws IOException, InterruptedException { try { PrintStream logger = listener.getLogger(); boolean foundText = false; - if (alsoCheckConsoleOutput) { + if (textFinder.isAlsoCheckConsoleOutput()) { // Do not mention the pattern we are looking for to avoid false positives logger.println("[Text Finder] Scanning console output..."); - foundText |= checkConsole(run, compilePattern(logger, regexp), logger); + foundText |= + checkConsole(run, compilePattern(logger, textFinder.getRegexp()), logger); logger.println( "[Text Finder] Finished looking for pattern " + "'" - + regexp + + textFinder.getRegexp() + "'" + " in the console output"); } final RemoteOutputStream ros = new RemoteOutputStream(logger); - if (fileSet != null) { + if (textFinder.getFileSet() != null) { logger.println( "[Text Finder] Looking for pattern " + "'" - + regexp + + textFinder.getRegexp() + "'" + " in the files at " + "'" - + fileSet + + textFinder.getFileSet() + "'"); - foundText |= workspace.act(new FileChecker(ros, fileSet, regexp)); + foundText |= + workspace.act( + new FileChecker( + ros, textFinder.getFileSet(), textFinder.getRegexp())); } - if (foundText != succeedIfFound) { + if (foundText != textFinder.isSucceedIfFound()) { final Result finalResult; - if (notBuiltIfFound) { + if (textFinder.isNotBuiltIfFound()) { finalResult = Result.NOT_BUILT; } else { - finalResult = unstableIfFound ? Result.UNSTABLE : Result.FAILURE; + finalResult = textFinder.isUnstableIfFound() ? Result.UNSTABLE : Result.FAILURE; } run.setResult(finalResult); } } catch (AbortException e) { - // no test file found + // files presented, but no test file found. run.setResult(Result.UNSTABLE); } } @@ -244,6 +273,41 @@ private static Pattern compilePattern(PrintStream logger, String regexp) { return pattern; } + @SuppressWarnings("unused") + public List getAdditionalTextFinders() { + return additionalTextFinders; + } + + @SuppressWarnings("unused") + public String getFileSet() { + return this.primaryTextFinder.getFileSet(); + } + + @SuppressWarnings("unused") + public String getRegexp() { + return this.primaryTextFinder.getRegexp(); + } + + @SuppressWarnings("unused") + public boolean isSucceedIfFound() { + return this.primaryTextFinder.isSucceedIfFound(); + } + + @SuppressWarnings("unused") + public boolean isUnstableIfFound() { + return this.primaryTextFinder.isUnstableIfFound(); + } + + @SuppressWarnings("unused") + public boolean isNotBuiltIfFound() { + return this.primaryTextFinder.isNotBuiltIfFound(); + } + + @SuppressWarnings("unused") + public boolean isAlsoCheckConsoleOutput() { + return this.primaryTextFinder.isAlsoCheckConsoleOutput(); + } + @Symbol("findText") @Extension public static final class DescriptorImpl extends BuildStepDescriptor { @@ -262,6 +326,15 @@ public boolean isApplicable(Class jobType) { return true; } + public List getItemDescriptors() { + Jenkins jenkins = Jenkins.getInstance(); + if (jenkins != null) { + return jenkins.getDescriptorList(TextFinderModel.class); + } else { + throw new NullPointerException("not able to get Jenkins instance"); + } + } + /** * Checks the regular expression validity. * diff --git a/src/main/resources/hudson/plugins/textfinder/TextFinderModel/config.jelly b/src/main/resources/hudson/plugins/textfinder/TextFinderModel/config.jelly new file mode 100644 index 0000000..a130914 --- /dev/null +++ b/src/main/resources/hudson/plugins/textfinder/TextFinderModel/config.jelly @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/hudson/plugins/textfinder/TextFinderPublisher/config.jelly b/src/main/resources/hudson/plugins/textfinder/TextFinderPublisher/config.jelly index 41debca..59c555c 100644 --- a/src/main/resources/hudson/plugins/textfinder/TextFinderPublisher/config.jelly +++ b/src/main/resources/hudson/plugins/textfinder/TextFinderPublisher/config.jelly @@ -19,4 +19,12 @@ + + + diff --git a/src/test/java/hudson/plugins/textfinder/TextFinderPublisherAgentTest.java b/src/test/java/hudson/plugins/textfinder/TextFinderPublisherAgentTest.java index 0e5303c..91b67fb 100644 --- a/src/test/java/hudson/plugins/textfinder/TextFinderPublisherAgentTest.java +++ b/src/test/java/hudson/plugins/textfinder/TextFinderPublisherAgentTest.java @@ -68,4 +68,30 @@ public void failureIfFoundInConsoleOnAgent() throws Exception { TestUtils.assertConsoleContainsMatch(ECHO_UNIQUE_TEXT, rule, build, true); rule.assertBuildStatus(Result.FAILURE, build); } + + @Test + public void lastFinderWins() throws Exception { + DumbSlave agent = rule.createOnlineSlave(); + WorkflowJob project = rule.createProject(WorkflowJob.class, "pipeline"); + project.setDefinition( + new CpsFlowDefinition( + String.format( + "node('%s') {isUnix() ? sh('echo foobar') : bat(\"prompt \\$G\\r\\necho foobar\")}\n" + + "node('%s') {" + + "findText regexp: 'foobar', alsoCheckConsoleOutput: true\n" + + "findText regexp: 'foobar', unstableIfFound: true, alsoCheckConsoleOutput: true\n" + + "findText regexp: 'foobar', notBuiltIfFound: true, alsoCheckConsoleOutput: true\n" + + "}", + agent.getNodeName(), agent.getNodeName()))); + WorkflowRun build = project.scheduleBuild2(0).get(); + rule.waitForCompletion(build); + rule.assertLogContains("[Text Finder] Scanning console output...", build); + rule.assertLogContains( + "[Text Finder] Finished looking for pattern '" + + UNIQUE_TEXT + + "' in the console output", + build); + TestUtils.assertConsoleContainsMatch(ECHO_UNIQUE_TEXT, rule, build, true); + rule.assertBuildStatus(Result.NOT_BUILT, build); + } } diff --git a/src/test/java/hudson/plugins/textfinder/TextFinderPublisherFreestyleTest.java b/src/test/java/hudson/plugins/textfinder/TextFinderPublisherFreestyleTest.java index 938e45a..436ea44 100644 --- a/src/test/java/hudson/plugins/textfinder/TextFinderPublisherFreestyleTest.java +++ b/src/test/java/hudson/plugins/textfinder/TextFinderPublisherFreestyleTest.java @@ -1,10 +1,14 @@ package hudson.plugins.textfinder; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import com.gargoylesoftware.htmlunit.WebClientUtil; +import com.gargoylesoftware.htmlunit.html.HtmlAnchor; +import com.gargoylesoftware.htmlunit.html.HtmlElement; import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlInput; import com.gargoylesoftware.htmlunit.html.HtmlPage; import hudson.Functions; import hudson.model.FreeStyleBuild; @@ -13,6 +17,8 @@ import hudson.tasks.BatchFile; import hudson.tasks.CommandInterpreter; import hudson.tasks.Shell; +import java.util.ArrayList; +import java.util.List; import org.junit.Rule; import org.junit.Test; import org.jvnet.hudson.test.JenkinsRule; @@ -110,6 +116,44 @@ public void notFoundInConsole() throws Exception { rule.assertBuildStatus(Result.SUCCESS, build); } + @Test + public void lastFinderWins() throws Exception { + FreeStyleProject project = rule.createFreeStyleProject("freestyle"); + CommandInterpreter command = + Functions.isWindows() + ? new BatchFile("prompt $G\n" + ECHO_UNIQUE_TEXT) + : new Shell(ECHO_UNIQUE_TEXT); + project.getBuildersList().add(command); + + List finders = new ArrayList<>(); + TextFinderModel t1 = new TextFinderModel(UNIQUE_TEXT); // 2nd + t1.setAlsoCheckConsoleOutput(true); + t1.setSucceedIfFound(true); + TextFinderModel t2 = + new TextFinderModel(UNIQUE_TEXT); // 3rd, this one must win; note that not_build is + // unmodificable + t2.setAlsoCheckConsoleOutput(true); + t2.setUnstableIfFound(true); + finders.add(t1); + finders.add(t2); + TextFinderPublisher textFinder = new TextFinderPublisher(UNIQUE_TEXT); + textFinder.setAlsoCheckConsoleOutput(true); + textFinder.setSucceedIfFound(true); + textFinder.setAdditionalTextFinders(finders); + project.getPublishersList().add(textFinder); + + FreeStyleBuild build = project.scheduleBuild2(0).get(); + rule.waitForCompletion(build); + rule.assertLogContains("[Text Finder] Scanning console output...", build); + rule.assertLogContains( + "[Text Finder] Finished looking for pattern '" + + UNIQUE_TEXT + + "' in the console output", + build); + TestUtils.assertConsoleContainsMatch(ECHO_UNIQUE_TEXT, rule, build, true); + rule.assertBuildStatus(Result.UNSTABLE, build); + } + @Test public void createTextFinderViaWebClient() throws Exception { FreeStyleProject project = rule.createFreeStyleProject(); @@ -139,9 +183,82 @@ public void createTextFinderViaWebClient() throws Exception { // Ensure that the Text Finder was configured correctly. assertEquals(1, project.getPublishersList().size()); TextFinderPublisher textFinder = (TextFinderPublisher) project.getPublishersList().get(0); - assertEquals("file1", textFinder.fileSet); - assertEquals(UNIQUE_TEXT, textFinder.regexp); - assertTrue(textFinder.unstableIfFound); - assertTrue(textFinder.alsoCheckConsoleOutput); + assertEquals("file1", textFinder.getFileSet()); + assertEquals(UNIQUE_TEXT, textFinder.getRegexp()); + assertTrue(textFinder.isUnstableIfFound()); + assertTrue(textFinder.isAlsoCheckConsoleOutput()); + } + + @Test + public void createMultipleTextFindersViaWebClient() throws Exception { + FreeStyleProject project = rule.createFreeStyleProject(); + assertEquals(0, project.getPublishersList().size()); + + // Go to the "Configure" page. + JenkinsRule.WebClient webClient = rule.createWebClient(); + HtmlPage page = webClient.goTo(project.getUrl() + "/configure"); + + // Add a Text Finder. + HtmlForm config = page.getFormByName("config"); + rule.getButtonByCaption(config, "Add post-build action").click(); + HtmlAnchor a1 = page.getAnchorByText("Text Finder"); + a1.click(); + + // Wait for the YUI JavaScript to load. + WebClientUtil.waitForJSExec(page.getWebClient()); + + // Configure the Text Finder. + HtmlInput i0 = config.getInputByName("_.fileSet"); + i0.setValueAttribute("file1"); + HtmlInput i1 = config.getInputByName("_.regexp"); + i1.setValueAttribute(UNIQUE_TEXT); + HtmlInput i2 = config.getInputByName("_.unstableIfFound"); + i2.click(); + HtmlInput i3 = config.getInputByName("_.alsoCheckConsoleOutput"); + i3.click(); + // add another Text Finder + List al1 = page.getAnchors(); + rule.getButtonByCaption(config, "Add additional Text Finder").click(); + List al2 = page.getAnchors(); + // no need to click to the first "add additional textfinder" + // we can click the invisibel one right away + List linksButtons = + page.getDocumentElement().getElementsByAttribute("a", "href", "#"); + for (HtmlElement e : linksButtons) { + String s = e.getTextContent(); + if (s.trim().equals("Text Finder")) { + e.click(); + // Wait for the YUI JavaScript to load. + WebClientUtil.waitForJSExec(page.getWebClient()); + } + } + // Configure the second Text Finder + List ii0 = config.getInputsByName("_.fileSet"); + ii0.get(1).setValueAttribute("file2"); + List ii1 = config.getInputsByName("_.regexp"); + ii1.get(1).setValueAttribute(UNIQUE_TEXT); + List ii2 = config.getInputsByName("_.notBuiltIfFound"); + ii2.get(1).click(); + List ii3 = config.getInputsByName("_.alsoCheckConsoleOutput"); + ii3.get(1).click(); + + // Submit the page. + rule.submit(config); + + // Ensure that the Text Finder was configured correctly. + assertEquals(1, project.getPublishersList().size()); + TextFinderPublisher textFinder = (TextFinderPublisher) project.getPublishersList().get(0); + assertEquals("file1", textFinder.getFileSet()); + assertEquals(UNIQUE_TEXT, textFinder.getRegexp()); + assertTrue(textFinder.isUnstableIfFound()); + assertFalse(textFinder.isNotBuiltIfFound()); + assertTrue(textFinder.isAlsoCheckConsoleOutput()); + assertEquals(1, textFinder.getAdditionalTextFinders().size()); + TextFinderModel textFinder2 = textFinder.getAdditionalTextFinders().get(0); + assertEquals("file2", textFinder2.getFileSet()); + assertEquals(UNIQUE_TEXT, textFinder2.getRegexp()); + assertFalse(textFinder2.isUnstableIfFound()); + assertTrue(textFinder2.isAlsoCheckConsoleOutput()); + assertTrue(textFinder2.isNotBuiltIfFound()); } } diff --git a/src/test/java/hudson/plugins/textfinder/TextFinderPublisherPipelineTest.java b/src/test/java/hudson/plugins/textfinder/TextFinderPublisherPipelineTest.java index 87ce1ff..685f2bc 100644 --- a/src/test/java/hudson/plugins/textfinder/TextFinderPublisherPipelineTest.java +++ b/src/test/java/hudson/plugins/textfinder/TextFinderPublisherPipelineTest.java @@ -303,4 +303,27 @@ public void notFoundInConsole() throws Exception { + "' in the console output", build); } + + @Test + public void lastFinderWins() throws Exception { + WorkflowJob project = rule.createProject(WorkflowJob.class, "pipeline"); + project.setDefinition( + new CpsFlowDefinition( + "node {isUnix() ? sh('echo foobar') : bat(\"prompt \\$G\\r\\necho foobar\")}\n" + + "node {" + + "findText regexp: 'foobar', alsoCheckConsoleOutput: true\n" + + "findText regexp: 'foobar', unstableIfFound: true, alsoCheckConsoleOutput: true\n" + + "findText regexp: 'foobar', notBuiltIfFound: true, alsoCheckConsoleOutput: true\n" + + "}")); + WorkflowRun build = project.scheduleBuild2(0).get(); + rule.waitForCompletion(build); + rule.assertLogContains("[Text Finder] Scanning console output...", build); + rule.assertLogContains( + "[Text Finder] Finished looking for pattern '" + + UNIQUE_TEXT + + "' in the console output", + build); + TestUtils.assertConsoleContainsMatch(ECHO_UNIQUE_TEXT, rule, build, true); + rule.assertBuildStatus(Result.NOT_BUILT, build); + } }