Permalink
Browse files

[FIXED JENKINS-47109] stage-level failFast w/ parallel stages

Might want to also reject if you use failFast in a non-parallel-parent
stage, but it's pretty much harmless, so...eh.
  • Loading branch information...
abayer committed Sep 28, 2017
1 parent 5c2b7d1 commit 1a7baae6efcc0358a0ff2ec46e8141cafcee5f5c
@@ -117,6 +117,9 @@ public String toGroovy() {
result.append(environment.toGroovy());
}
if (branches.isEmpty() && parallel != null) {
if (failFast != null && failFast) {
result.append("failFast true\n");
}
result.append("parallel {\n");
result.append(parallel.toGroovy());
result.append("}\n");
@@ -55,9 +55,11 @@ public class Stage implements Serializable {

Stages parallel

boolean failFast

@Whitelisted
Stage(String name, StepsBlock steps, Agent agent, PostStage post, StageConditionals when, Tools tools,
Environment environment, Stages parallel) {
Environment environment, Stages parallel, boolean failFast) {
this.name = name
this.steps = steps
this.agent = agent
@@ -66,6 +68,7 @@ public class Stage implements Serializable {
this.tools = tools
this.environment = environment
this.parallel = parallel
this.failFast = failFast
}

/**
@@ -167,7 +167,7 @@ class JSONParser implements Parser {
stage.branches.add(parseBranch(branches.append(JsonPointer.of(i))))
}

if (j.node.has("failFast") && stage.branches.size() > 1) {
if (j.node.has("failFast") && (stage.branches.size() > 1 || j.node.has("parallel"))) {
stage.failFast = j.node.get("failFast")?.asBoolean()
}

@@ -557,6 +557,17 @@ class ModelParser implements Parser {
case 'parallel':
stage.parallel = parseStages(s)
break
case 'failFast':
List<Expression> args = ((TupleExpression) mc.arguments).expressions

ConstantExpression exp = castOrNull(ConstantExpression.class, args[0])
if (exp == null || !(exp.value instanceof Boolean)) {
errorCollector.error(new ModelASTKey(mc.method),
Messages.ModelParser_ExpectedFailFast())
} else {
stage.setFailFast((Boolean)exp.value)
}
break
default:
errorCollector.error(stage, Messages.ModelParser_UnknownStageSection(name))
}
@@ -634,7 +634,8 @@ class RuntimeASTTransformer {
transformStageConditionals(original.when),
transformTools(original.tools),
transformEnvironment(original.environment),
transformStages(original.parallel)))
transformStages(original.parallel),
constX(original.failFast != null ? original.failFast : false)))
}

return constX(null)
@@ -167,6 +167,9 @@ public class ModelInterpreter implements Serializable {
evaluateStage(root, thisStage.agent ?: parentAgent, parallelStage, firstError, thisStage))
}
}
if (!parallelStages.isEmpty() && thisStage.failFast) {
parallelStages.put("failFast", thisStage.failFast)
}

return parallelStages

@@ -164,7 +164,8 @@ public void setUp() throws Exception {
"stagePost",
"when/changelog/changelog",
"when/changelog/changeset",
"backslashReductionInEnv"
"backslashReductionInEnv",
"parallelStagesFailFast"
);

public static Iterable<Object[]> configsWithErrors() {
@@ -1134,4 +1134,18 @@ public void fromEvaluate() throws Exception {
.go();

}
}

@Issue("JENKINS-47109")
@Test
public void parallelStagesFailFast() throws Exception {
expect(Result.ABORTED, "parallelStagesFailFast")
.logContains("[Pipeline] { (foo)",
"[first] { (Branch: first)",
"[Pipeline] [first] { (first)",
"[second] { (Branch: second)",
"[Pipeline] [second] { (second)",
"SECOND STAGE ABORTED")
.hasFailureCase()
.go();
}
}
@@ -77,7 +77,11 @@ public ExecuteConvertedTest(String configName) {
List<Object[]> result = new ArrayList<>();
for (String c : AbstractModelDefTest.SHOULD_PASS_CONFIGS) {
// Temporary hack to skip Docker and globalLibrary
if (!c.equals("agentDocker") && !c.contains("globalLibrary") && !c.contains("jsonSchemaNull")) {
if (!c.equals("agentDocker") &&
!c.contains("globalLibrary") &&
!c.contains("jsonSchemaNull") &&
// parallelStagesFailFast is expected to, well, fail, so skip it.
!c.contains("parallelStagesFailFast")) {
result.add(new Object[]{c});
}
}
@@ -0,0 +1,69 @@
{"pipeline": {
"stages": [ {
"name": "foo",
"parallel": [
{
"name": "first",
"branches": [ {
"name": "default",
"steps": [ {
"name": "error",
"arguments": [ {
"key": "message",
"value": {
"isLiteral": true,
"value": "First branch"
}
}]
}]
}]
},
{
"name": "second",
"branches": [ {
"name": "default",
"steps": [
{
"name": "sleep",
"arguments": [ {
"key": "time",
"value": {
"isLiteral": true,
"value": 10
}
}]
},
{
"name": "echo",
"arguments": [ {
"key": "message",
"value": {
"isLiteral": true,
"value": "Second branch"
}
}]
}
]
}],
"post": {"conditions": [ {
"condition": "aborted",
"branch": {
"name": "default",
"steps": [ {
"name": "echo",
"arguments": [ {
"key": "message",
"value": {
"isLiteral": true,
"value": "SECOND STAGE ABORTED"
}
}]
}]
}
}]}
}
],
"failFast": true
}],
"agent": {"type": "none"}
}}
@@ -0,0 +1,54 @@
/*
* The MIT License
*
* Copyright (c) 2017, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

pipeline {
agent none
stages {
stage("foo") {
failFast true
parallel {
stage("first") {
steps {
error "First branch"
}
}
stage("second") {
steps {
sleep 10
echo "Second branch"
}
post {
aborted {
echo "SECOND STAGE ABORTED"
}
}
}
}
}
}
}




0 comments on commit 1a7baae

Please sign in to comment.