Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from ndeloof/JENKINS-11594
Jenkins 11594
- Loading branch information
Showing
5 changed files
with
244 additions
and
190 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
104 changes: 104 additions & 0 deletions
104
src/main/java/com/chikli/hudson/plugin/naginator/NaginatorListener.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package com.chikli.hudson.plugin.naginator; | ||
|
||
import hudson.model.AbstractBuild; | ||
import hudson.model.Result; | ||
import hudson.model.TaskListener; | ||
import hudson.model.listeners.RunListener; | ||
|
||
import java.io.BufferedReader; | ||
import java.io.File; | ||
import java.io.FileReader; | ||
import java.io.IOException; | ||
import java.util.logging.Level; | ||
import java.util.logging.Logger; | ||
import java.util.regex.Matcher; | ||
import java.util.regex.Pattern; | ||
|
||
/** | ||
* @author <a href="mailto:nicolas.deloof@cloudbees.com">Nicolas De loof</a> | ||
*/ | ||
public class NaginatorListener extends RunListener<AbstractBuild<?,?>> { | ||
|
||
|
||
@Override | ||
public void onCompleted(AbstractBuild<?, ?> build, TaskListener listener) { | ||
if (build.getResult() == Result.SUCCESS) { | ||
return; | ||
} | ||
|
||
NaginatorPublisher naginator = build.getProject().getPublishersList().get(NaginatorPublisher.class); | ||
|
||
// If we're not set to rerun if unstable, and the build's unstable, return true. | ||
if ((!naginator.isRerunIfUnstable()) && (build.getResult() == Result.UNSTABLE)) { | ||
return; | ||
} | ||
|
||
// If we're supposed to check for a regular expression in the build output before | ||
// scheduling a new build, do so. | ||
if (naginator.isCheckRegexp()) { | ||
LOGGER.log(Level.FINEST, "Got checkRegexp == true"); | ||
|
||
String regexpForRerun = naginator.getRegexpForRerun(); | ||
if ((regexpForRerun !=null) && (!regexpForRerun.equals(""))) { | ||
LOGGER.log(Level.FINEST, "regexpForRerun - " + regexpForRerun); | ||
|
||
try { | ||
// If parseLog returns false, we didn't find the regular expression, | ||
// so return true. | ||
if (!parseLog(build.getLogFile(), regexpForRerun)) { | ||
LOGGER.log(Level.FINEST, "regexp not in logfile"); | ||
return; | ||
} | ||
} catch (IOException e) { | ||
e.printStackTrace(listener | ||
.error("error while parsing logs for naginator - forcing rebuild.")); | ||
} | ||
} | ||
} | ||
|
||
// if a build fails for a reason that cannot be immediately fixed, | ||
// immediate rescheduling may cause a very tight loop. | ||
// combined with publishers like e-mail, IM, this could flood the users. | ||
// | ||
// so to avoid this problem, progressively introduce delay until the next build | ||
|
||
// delay = the number of consective build problems * 5 mins | ||
// back off at most 3 hours | ||
int n=0; | ||
for(AbstractBuild<?,?> b=build; b!=null && b.getResult()!=Result.SUCCESS && n<60; b=b.getPreviousBuild()) | ||
n+=5; | ||
|
||
LOGGER.log(Level.FINE, "about to try to schedule a build"); | ||
scheduleBuild(build, n); | ||
} | ||
|
||
|
||
/** | ||
* Wrapper method for mocking purposes. | ||
*/ | ||
public boolean scheduleBuild(AbstractBuild<?, ?> build, int n) { | ||
return build.getProject().scheduleBuild(n*60, new NaginatorCause()); | ||
} | ||
|
||
private boolean parseLog(File logFile, String regexp) throws IOException { | ||
|
||
if (regexp == null) { | ||
return false; | ||
} | ||
|
||
// Assume default encoding and text files | ||
String line; | ||
Pattern pattern = Pattern.compile(regexp); | ||
BufferedReader reader = new BufferedReader(new FileReader(logFile)); | ||
while ((line = reader.readLine()) != null) { | ||
Matcher matcher = pattern.matcher(line); | ||
if (matcher.find()) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private static final Logger LOGGER = Logger.getLogger(NaginatorListener.class.getName()); | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
133 changes: 133 additions & 0 deletions
133
src/test/java/com/chikli/hudson/plugin/naginator/NaginatorListenerTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,133 @@ | ||
package com.chikli.hudson.plugin.naginator; | ||
|
||
import hudson.Launcher; | ||
import hudson.model.AbstractBuild; | ||
import hudson.model.BuildListener; | ||
import hudson.model.FreeStyleBuild; | ||
import hudson.model.FreeStyleProject; | ||
import hudson.model.Hudson; | ||
import hudson.model.Queue; | ||
import hudson.model.Result; | ||
import hudson.tasks.BuildWrapper; | ||
import hudson.tasks.BuildWrapper.Environment; | ||
import hudson.tasks.Builder; | ||
|
||
import java.io.IOException; | ||
import java.util.concurrent.ExecutionException; | ||
|
||
import org.jvnet.hudson.test.HudsonTestCase; | ||
|
||
public class NaginatorListenerTest extends HudsonTestCase { | ||
|
||
private final static class MyBuilder extends Builder { | ||
private final String text; | ||
private final Result result; | ||
private final int duration; | ||
|
||
public MyBuilder(String text, Result result) { | ||
super(); | ||
this.text = text; | ||
this.result = result; | ||
this.duration = 0; | ||
} | ||
|
||
private MyBuilder(String text, Result result, int duration) { | ||
this.text = text; | ||
this.result = result; | ||
this.duration = duration; | ||
} | ||
|
||
@Override | ||
public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, | ||
BuildListener listener) throws InterruptedException, | ||
IOException { | ||
if (duration > 0) Thread.sleep(duration); | ||
|
||
listener.getLogger().println(text); | ||
build.setResult(result); | ||
return true; | ||
} | ||
} | ||
|
||
public void testSuccessNoRebuild() throws Exception { | ||
assertEquals(false, isScheduledForRetry("build log", Result.SUCCESS, "foo", false, false)); | ||
} | ||
|
||
public void testUnstableNoRebuild() throws Exception { | ||
assertEquals(false, isScheduledForRetry("build log", Result.SUCCESS, "foo", false, false)); | ||
} | ||
|
||
public void testUnstableWithRebuild() throws Exception { | ||
assertEquals(true, isScheduledForRetry("build log", Result.UNSTABLE, "foo", true, false)); | ||
} | ||
|
||
public void testFailureWithRebuild() throws Exception { | ||
assertEquals(true, isScheduledForRetry("build log", Result.FAILURE, "foo", false, false)); | ||
} | ||
|
||
public void testFailureWithUnstableRebuild() throws Exception { | ||
assertEquals(true, isScheduledForRetry("build log", Result.FAILURE, "foo", true, false)); | ||
} | ||
|
||
public void testFailureWithoutRebuildRegexp() throws Exception { | ||
assertEquals(false, isScheduledForRetry("build log", Result.FAILURE, "foo", false, true)); | ||
} | ||
|
||
public void testFailureWithRebuildRegexp() throws Exception { | ||
assertEquals(true, isScheduledForRetry("build log foo", Result.FAILURE, "foo", false, true)); | ||
} | ||
|
||
public void testUnstableWithoutRebuildRegexp() throws Exception { | ||
assertEquals(false, isScheduledForRetry("build log", Result.UNSTABLE, "foo", true, true)); | ||
} | ||
|
||
public void testUnstableWithRebuildRegexp() throws Exception { | ||
assertEquals(true, isScheduledForRetry("build log foo", Result.UNSTABLE, "foo", true, true)); | ||
} | ||
|
||
public void testWithBuildWrapper() throws Exception { | ||
|
||
FreeStyleProject project = createFreeStyleProject(); | ||
project.getBuildersList().add(new MyBuilder("foo", Result.SUCCESS, 1000)); | ||
NaginatorPublisher nag = new NaginatorPublisher("foo", false, false); | ||
project.getPublishersList().add(nag); | ||
BuildWrapper failTheBuild = new FailTheBuild(); | ||
project.getBuildWrappersList().add(failTheBuild); | ||
|
||
assertEquals(true, isScheduledForRetry(project)); | ||
} | ||
|
||
|
||
private boolean isScheduledForRetry(String buildLog, Result result, String regexpForRerun, | ||
boolean rerunIfUnstable, boolean checkRegexp) throws Exception { | ||
FreeStyleProject project = createFreeStyleProject(); | ||
project.getBuildersList().add(new MyBuilder(buildLog, result)); | ||
NaginatorPublisher nag = new NaginatorPublisher(regexpForRerun, rerunIfUnstable, checkRegexp); | ||
project.getPublishersList().add(nag); | ||
|
||
return isScheduledForRetry(project); | ||
} | ||
|
||
private boolean isScheduledForRetry(FreeStyleProject project) throws InterruptedException, ExecutionException { | ||
FreeStyleBuild build = project.scheduleBuild2(0).get(); | ||
|
||
Queue queue = Hudson.getInstance().getQueue(); | ||
Queue.Item[] tasks = queue.getItems(); | ||
boolean scheduled = tasks.length > 0; | ||
queue.clear(); | ||
return scheduled; | ||
} | ||
|
||
private static final class FailTheBuild extends BuildWrapper { | ||
@Override | ||
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException { | ||
return new Environment() { | ||
@Override | ||
public boolean tearDown(AbstractBuild build, BuildListener listener) throws IOException, InterruptedException { | ||
build.setResult(Result.FAILURE); | ||
return true; | ||
} | ||
}; | ||
} | ||
} | ||
} |
Oops, something went wrong.