Skip to content

Commit

Permalink
Handle NOT_BUILT and ABORTED as other results
Browse files Browse the repository at this point in the history
Use case
--------

My use case is a post build script trigger a job which does not need to
fully complete. The step can be cancelled while waiting (or building)
and that caused  the triggering build to be marked as a failure.

To fix that, I need the plugin to pass the result of the build step
through BlockingBehaviour which lets one define how to behave when the
triggered build got NOT_BUILT or ABORTED, in my case I need it to never
block.

See: https://phabricator.wikimedia.org/T352319

Solution
--------

There are multiple reasons for a job to not fully complete:

- it is interrupted, an InterruptedException is thrown, this is still
  rethrow and Jenkins will make the build as ABORTED.

- it can be cancelled from the build queue raising a
  CancellationException. This previously raised an AbortException which
  marks the build as a failure, I have changed it to a NOT_BUILT result
  which can be process as other results (notably never block on it).

The Jenkins Result class ranks the results as:
- SUCCESS
- UNSTABLE
- FAILURE
- NOT_BUILT
- ABORTED.

The NOT_BUILT and ABORTED results are thus worse than a FAILURE and
would be matched as such in BlockingBehavior mapBuildStepResult() and
mapBuildResult() which uses isWorseOrEqualTo() for comparison.

Add a test testCancelledFromBuildQueue() to cover the
CancellationException() is caught and it results in a SUCCESS (since the
test blocking behavior is to never block).

ResultConditionTest covers the BlockingBehavior is able to map NOT_BUILD
and ABORTED since it has two tests explicitly cancelling and
interrupting jobs.

Examples
--------

When a build is aborted, by aborting it:

  Waiting for the completion of downstream-project
  downstream-project #7 started.
  downstream-project #7 completed. Result was ABORTED
  Build step 'Trigger/call builds on other projects' marked build as failure
  Finished: FAILURE

When it is waiting in the build queue and cancelled:

  Waiting for the completion of downstream-project
  Not built: downstream-project has been cancelled while waiting in the queue.
  Build step 'Trigger/call builds on other projects' marked build as failure
  Finished: FAILURE
  • Loading branch information
hashar committed Nov 14, 2024
1 parent 458bf27 commit a455d7e
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,55 +150,59 @@ public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListen
continue;
}
for (QueueTaskFuture<AbstractBuild> future : futures.get(p)) {
try {
if (future == null) {
listener.getLogger()
.println("Skipping " + ModelHyperlinkNote.encodeTo(p)
+ ". The project was not triggered for some reason.");
continue;
}

if (future == null) {
listener.getLogger()
.println("Waiting for the completion of "
+ HyperlinkNote.encodeTo('/' + p.getUrl(), p.getFullDisplayName()));
Run startedRun;
try {
startedRun = future.waitForStart();
} catch (InterruptedException x) {
listener.getLogger()
.println("Build aborting: cancelling queued project "
+ HyperlinkNote.encodeTo('/' + p.getUrl(), p.getFullDisplayName()));
future.cancel(true);
throw x; // rethrow so that the triggering project get flagged as cancelled
}
.println("Skipping " + ModelHyperlinkNote.encodeTo(p)
+ ". The project was not triggered for some reason.");
continue;
}

listener.getLogger()
.println("Waiting for the completion of "
+ HyperlinkNote.encodeTo('/' + p.getUrl(), p.getFullDisplayName()));
Run startedRun;
Result completedResult;
try {
startedRun = future.waitForStart();
listener.getLogger()
.println(HyperlinkNote.encodeTo(
'/' + startedRun.getUrl(), startedRun.getFullDisplayName())
+ " started.");

Run completedRun = future.get();
Result completedResult = completedRun.getResult();
completedResult = completedRun.getResult();
listener.getLogger()
.println(HyperlinkNote.encodeTo(
'/' + completedRun.getUrl(), completedRun.getFullDisplayName())
+ " completed. Result was " + completedResult);

BuildInfoExporterAction.addBuildInfoExporterAction(
build,
completedRun.getParent().getFullName(),
completedRun.getNumber(),
completedResult);

if (buildStepResult && config.getBlock().mapBuildStepResult(completedResult)) {
Result r = config.getBlock().mapBuildResult(completedResult);
if (r != null) { // The blocking job is not a success
build.setResult(r);
}
} else {
buildStepResult = false;
}
} catch (InterruptedException x) {
listener.getLogger()
.println("Build aborting: cancelling queued project "
+ HyperlinkNote.encodeTo('/' + p.getUrl(), p.getFullDisplayName()));
future.cancel(true);
throw x; // rethrow so that the triggering project get flagged as cancelled
} catch (CancellationException x) {
throw new AbortException(p.getFullDisplayName() + " aborted.");
listener.getLogger()
.println("Not built: " + p.getFullDisplayName()
+ " has been cancelled while waiting in the queue.");
completedResult = Result.NOT_BUILT;
}

if (buildStepResult && config.getBlock().mapBuildStepResult(completedResult)) {

Result r = config.getBlock().mapBuildResult(completedResult);
if (r != null) { // The blocking job is not a success
build.setResult(r);
}
} else {
buildStepResult = false;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,39 @@ public void testCancelsDownstreamBuildWhenInterrupted() throws Exception {
assertEquals("No build left in queue", 0, r.jenkins.getQueue().countBuildableItems());
}

@Test
public void testCancelledFromBuildQueue() throws Exception {
r.jenkins.setNumExecutors(1); // the downstream-project would be in the build queue

FreeStyleProject triggerProject = r.createFreeStyleProject("upstream-project");
FreeStyleProject downstreamProject = r.createFreeStyleProject("downstream-project");

TriggerBuilder triggerBuilder = new TriggerBuilder(createTriggerConfig("downstream-project"));

triggerProject.getBuildersList().add(triggerBuilder);
QueueTaskFuture<FreeStyleBuild> parentBuild = triggerProject.scheduleBuild2(0);

parentBuild.waitForStart();
Thread.sleep(500);

assertEquals(
"Downstream project is in build queue", 1, r.jenkins.getQueue().countBuildableItems());

// Cancel the queued build
r.jenkins.getQueue().clear();
parentBuild.get();

assertLines(
triggerProject.getLastBuild(),
"Waiting for the completion of downstream-project",
"Not built: downstream-project has been cancelled while waiting in the queue.",
// The test class configures the BlockingBehaviour to never
// fail and that includes cancelled job.
"Finished: SUCCESS");
assertNull("No downstream build has been run", downstreamProject.getLastBuild());
assertEquals("No build left in queue", 0, r.jenkins.getQueue().countBuildableItems());
}

@Test
public void testConsoleOutputWithCounterParameters() throws Exception {
r.createFreeStyleProject("project1");
Expand Down

0 comments on commit a455d7e

Please sign in to comment.