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 triggering a job which does not need
to fully complete. Since the build step can be cancelled while waiting
or building, it causes the triggering build to be marked as a failure
despite asking to never block.

To fix that, I need the plugin to pass the result of the build step
through BlockingBehaviour. That lets one define how to behave when the
triggered build is 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 mark the build as ABORTED.

- it can be cancelled from the build queue raising a
  CancellationException. This previously raised an AbortException which
  Jenkins handles by marking the build as a failure. I have changed it to
  a NOT_BUILT result which can be process as other results (addressing
  my use case to have it to never block).

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).

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

Examples
--------

When a build is ongoing and when 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 get 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 18, 2024
1 parent 8980535 commit d1e4c3a
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) {

Check warning on line 153 in src/main/java/hudson/plugins/parameterizedtrigger/TriggerBuilder.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 153 is only partially covered, one branch is missing
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;

Check warning on line 157 in src/main/java/hudson/plugins/parameterizedtrigger/TriggerBuilder.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 154-157 are not covered by tests
}

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)) {

Check warning on line 198 in src/main/java/hudson/plugins/parameterizedtrigger/TriggerBuilder.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 198 is only partially covered, 2 branches are missing

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 d1e4c3a

Please sign in to comment.