Skip to content

Commit

Permalink
[JENKINS-72059] Add new quality gate options to alter the stage only.
Browse files Browse the repository at this point in the history
  • Loading branch information
uhafner committed Dec 29, 2023
1 parent 910ab21 commit 2173b8c
Show file tree
Hide file tree
Showing 11 changed files with 207 additions and 14 deletions.
20 changes: 20 additions & 0 deletions src/main/java/io/jenkins/plugins/util/AbstractExecution.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,28 @@ protected Charset getCharset(final String charset) {
* if the user canceled the execution
* @throws IOException
* if the required {@link FlowNode} instance is not found
* @deprecated use {@link #createQualityGateNotifier()} instead
*/
@Deprecated
protected StageResultHandler createStageResultHandler() throws InterruptedException, IOException {
return createPipelineResultHandler();
}

/**
* Creates a {@link QualityGateNotifier} that sets build result of the {@link Run} or step.
*
* @return a {@link QualityGateNotifier} that sets the build result of the {@link Run} or step
* @throws InterruptedException
* if the user canceled the execution
* @throws IOException
* if the required {@link FlowNode} instance is not found
*
*/
protected QualityGateNotifier createQualityGateNotifier() throws InterruptedException, IOException {
return createPipelineResultHandler();
}

private PipelineResultHandler createPipelineResultHandler() throws IOException, InterruptedException {
return new PipelineResultHandler(getRun(), getContext().get(FlowNode.class));
}
}
26 changes: 24 additions & 2 deletions src/main/java/io/jenkins/plugins/util/PipelineResultHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import hudson.model.Run;

/**
* {@link StageResultHandler} that sets the overall build result of the {@link Run} and annotates the given Pipeline
* A {@link QualityGateNotifier} that sets the overall build result of the {@link Run} and annotates the given Pipeline
* step with a {@link WarningAction}.
*
* @author Devin Nusbaum
*/
public class PipelineResultHandler implements StageResultHandler {
@SuppressWarnings("deprecation")
public class PipelineResultHandler implements StageResultHandler, QualityGateNotifier {
private final Run<?, ?> run;
private final FlowNode flowNode;

Expand All @@ -31,9 +32,30 @@ public PipelineResultHandler(final Run<?, ?> run, final FlowNode flowNode) {
@Override
public void setResult(final Result result, final String message) {
run.setResult(result);

setStageResult(result, message);
}

private void setStageResult(final Result result, final String message) {
WarningAction existing = flowNode.getPersistentAction(WarningAction.class);
if (existing == null || existing.getResult().isBetterThan(result)) {
flowNode.addOrReplaceAction(new WarningAction(result).withMessage(message));
}
}

@Override
public void publishResult(final QualityGateStatus status, final String message) {
switch (status) {
case NOTE:
case ERROR:
setStageResult(status.getResult(), message);
break;
case WARNING:
case FAILED:
setResult(status.getResult(), message);
break;
default:
// ignore and do nothing
}
}
}
30 changes: 25 additions & 5 deletions src/main/java/io/jenkins/plugins/util/QualityGate.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@

import edu.hm.hafner.util.VisibleForTesting;

import org.kohsuke.stapler.AncestorInPath;
import org.kohsuke.stapler.DataBoundSetter;
import org.kohsuke.stapler.verb.POST;
import hudson.Extension;
import hudson.model.AbstractDescribableImpl;
import hudson.model.BuildableItem;
import hudson.model.Descriptor;
import hudson.model.FreeStyleProject;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;

Expand Down Expand Up @@ -77,10 +80,16 @@ public final QualityGateStatus getStatus() {
* Determines the Jenkins build result if the quality gate is failed.
*/
public enum QualityGateCriticality {
/** The build will be marked as unstable. */
/** The stage will be marked with a warning. */
NOTE(QualityGateStatus.NOTE),

/** The stage and the build will be marked as unstable. */
UNSTABLE(QualityGateStatus.WARNING),

/** The build will be marked as failed. */
/** The stage will be marked as failed. */
ERROR(QualityGateStatus.ERROR),

/** The stage and the build will be marked as failed. */
FAILURE(QualityGateStatus.FAILED);

private final QualityGateStatus status;
Expand Down Expand Up @@ -124,15 +133,26 @@ public QualityGateDescriptor() {
/**
* Returns a model with all {@link QualityGateCriticality criticalities} that can be used in quality gates.
*
* @param project
* the project that is configured
*
* @return a model with all {@link QualityGateCriticality criticalities}.
*/
@POST
@SuppressWarnings("unused") // used by Stapler view data binding
public ListBoxModel doFillCriticalityItems() {
public ListBoxModel doFillCriticalityItems(@AncestorInPath final BuildableItem project) {
if (jenkins.hasPermission(Jenkins.READ)) {
ListBoxModel options = new ListBoxModel();
options.add(Messages.QualityGate_Unstable(), QualityGateCriticality.UNSTABLE.name());
options.add(Messages.QualityGate_Failure(), QualityGateCriticality.FAILURE.name());
if (project instanceof FreeStyleProject) {
options.add(Messages.QualityGate_Unstable(), QualityGateCriticality.UNSTABLE.name());
options.add(Messages.QualityGate_Failure(), QualityGateCriticality.FAILURE.name());
}
else {
options.add(Messages.QualityGate_UnstableStage(), QualityGateCriticality.NOTE.name());
options.add(Messages.QualityGate_UnstableRun(), QualityGateCriticality.UNSTABLE.name());
options.add(Messages.QualityGate_FailureStage(), QualityGateCriticality.FAILURE.name());
options.add(Messages.QualityGate_FailureRun(), QualityGateCriticality.FAILURE.name());
}
return options;
}
return new ListBoxModel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public QualityGateResult evaluate() {
protected abstract void evaluate(T qualityGate, QualityGateResult result);

/**
* Appends all the quality gates in the specified collection to the end of the list of quality gates.
* Appends all the specified quality gates to the end of the existing quality gates.
*
* @param additionalQualityGates
* the quality gates to add
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/io/jenkins/plugins/util/QualityGateNotifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package io.jenkins.plugins.util;

/**
* Notifies the build or stage about the quality gate result.
*
* @author Ullrich Hafner
*/
public interface QualityGateNotifier {
/**
* Called to notify the build or stage about the quality gate status.
*
* @param status
* the quality gate status
* @param message
* a message that describes the cause for the result
*/
void publishResult(QualityGateStatus status, String message);
}
6 changes: 6 additions & 0 deletions src/main/java/io/jenkins/plugins/util/QualityGateStatus.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,16 @@ public enum QualityGateStatus {
/** Quality gate has been passed. */
PASSED(Result.SUCCESS),

/** Quality gate has been missed: severity is a note. */
NOTE(Result.UNSTABLE),

/** Quality gate has been missed: severity is a warning. */
WARNING(Result.UNSTABLE),

/** Quality gate has been missed: severity is an error. */
ERROR(Result.FAILURE),

/** Quality gate has been missed: severity is a failure. */
FAILED(Result.FAILURE);

private final Result result;
Expand Down
14 changes: 11 additions & 3 deletions src/main/java/io/jenkins/plugins/util/RunResultHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import hudson.model.Run;

/**
* {@link StageResultHandler} that sets the overall build result of the {@link Run}.
* A {@link QualityGateNotifier} that sets the overall build result of the {@link Run}.
*
* @author Devin Nusbaum
*/
public class RunResultHandler implements StageResultHandler {
@SuppressWarnings("deprecation")
public class RunResultHandler implements StageResultHandler, QualityGateNotifier {
private final Run<?, ?> run;

/**
Expand All @@ -23,6 +24,13 @@ public RunResultHandler(final Run<?, ?> run) {

@Override
public void setResult(final Result result, final String message) {
run.setResult(result);
if (result.equals(Result.UNSTABLE) || result.equals(Result.FAILURE)) {
run.setResult(result);
}
}

@Override
public void publishResult(final QualityGateStatus status, final String message) {
setResult(status.getResult(), message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
/**
* Handles the setting of the results of a stage.
*
* @author Devin Nusbaum
* @author Devin Nusbaum
* @deprecated use {@link QualityGateNotifier} instead
*/
@Deprecated
public interface StageResultHandler {
/**
* Called to set the {@link Result} of a stage.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@ FieldValidator.Error.DefaultEncoding=Encoding must be a supported encoding of th
<a rel="noopener noreferrer" href="{0}">java.nio.charset.Charset</a>
FieldValidator.Error.WrongIdFormat=An ID must match the regexp pattern {0}, but {1} does not.

QualityGate.Failure=Fail the step if the quality gate has been missed
QualityGate.Unstable=Set the build status to unstable if the quality gate has been missed
QualityGate.Failure=Fail the build
QualityGate.Unstable=Mark the build as unstable

QualityGate.FailureRun=Fail the step and the build
QualityGate.FailureStage=Fail the step but not the build
QualityGate.UnstableRun=Mark the step and the build as unstable
QualityGate.UnstableStage=Mark the step as unstable

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package io.jenkins.plugins.util;

import org.junit.jupiter.api.Test;
import org.junitpioneer.jupiter.Issue;

import org.jenkinsci.plugins.workflow.actions.WarningAction;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import hudson.model.Action;
import hudson.model.Result;
import hudson.model.Run;

import static org.mockito.Mockito.*;

class PipelineResultHandlerTest {
private static final String MESSAGE = "message";

@Test
void shouldSetRunResult() {
var run = mock(Run.class);
var flowNode = mock(FlowNode.class);

var handler = new PipelineResultHandler(run, flowNode);
handler.publishResult(QualityGateStatus.PASSED, MESSAGE);

verifyNoInteractions(run);
verifyNoInteractions(flowNode);

handler.publishResult(QualityGateStatus.WARNING, MESSAGE);
verify(run).setResult(Result.UNSTABLE);
verify(flowNode).addOrReplaceAction(argThat(action -> hasFlowNode(action, Result.UNSTABLE)));

handler.publishResult(QualityGateStatus.FAILED, MESSAGE);
verify(run).setResult(Result.FAILURE);
verify(flowNode).addOrReplaceAction(argThat(action -> hasFlowNode(action, Result.FAILURE)));
}

@Test @Issue("JENKINS-72059")
void shouldSetStageResult() {
var run = mock(Run.class);
var flowNode = mock(FlowNode.class);

var handler = new PipelineResultHandler(run, flowNode);
handler.publishResult(QualityGateStatus.PASSED, MESSAGE);

verifyNoInteractions(run);
verifyNoInteractions(flowNode);

handler.publishResult(QualityGateStatus.NOTE, MESSAGE);
verifyNoInteractions(run);
verify(flowNode).addOrReplaceAction(argThat(action -> hasFlowNode(action, Result.UNSTABLE)));

handler.publishResult(QualityGateStatus.ERROR, MESSAGE);
verifyNoInteractions(run);
verify(flowNode).addOrReplaceAction(argThat(action -> hasFlowNode(action, Result.FAILURE)));
}

private boolean hasFlowNode(final Action action, final Result result) {
if (!(action instanceof WarningAction)) {
return false;
}
WarningAction warningAction = (WarningAction) action;
return result.equals(warningAction.getResult()) && MESSAGE.equals(warningAction.getMessage());
}
}
28 changes: 28 additions & 0 deletions src/test/java/io/jenkins/plugins/util/RunResultHandlerTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.jenkins.plugins.util;

import org.junit.jupiter.api.Test;

import hudson.model.Result;
import hudson.model.Run;

import static org.mockito.Mockito.*;

class RunResultHandlerTest {

Check notice

Code scanning / CodeQL

Unused classes and interfaces Note test

Unused class: RunResultHandlerTest is not referenced within this codebase. If not used as an external API it should be removed.
private static final String MESSAGE = "message";

@Test
void shouldSetRunResult() {
var run = mock(Run.class);

var handler = new RunResultHandler(run);
handler.publishResult(QualityGateStatus.PASSED, MESSAGE);

verifyNoInteractions(run);

handler.publishResult(QualityGateStatus.WARNING, MESSAGE);
verify(run).setResult(Result.UNSTABLE);

handler.publishResult(QualityGateStatus.FAILED, MESSAGE);
verify(run).setResult(Result.FAILURE);
}
}

0 comments on commit 2173b8c

Please sign in to comment.