Skip to content

Commit

Permalink
feat(3042): Add basic stages functional tests (#3061)
Browse files Browse the repository at this point in the history
  • Loading branch information
tkyi authored May 2, 2024
1 parent 1f6edec commit d46fbd9
Show file tree
Hide file tree
Showing 18 changed files with 546 additions and 34 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ For more information about Screwdriver, check out our [homepage](http://screwdri

### Plugins

This API comes preloaded with 16 (sixteen) resources:
This API comes preloaded with 18 (eighteen) resources:

- [auth](plugins/auth/README.md)
- [banners](plugins/banners/README.md)
Expand All @@ -37,6 +37,8 @@ This API comes preloaded with 16 (sixteen) resources:
- [jobs](plugins/jobs/README.md)
- [pipelines](plugins/pipelines/README.md)
- [secrets](plugins/secrets/README.md)
- [stages](plugins/stages/README.md)
- [stageBuilds](plugins/stageBuilds/README.md)
- [templates](plugins/templates/README.md)
- [tokens](plugins/tokens/README.md)
- [webhooks](plugins/webhooks/README.md)
Expand Down
155 changes: 155 additions & 0 deletions features/stages.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
@stage
Feature: Stage

Users should be allowed to tie together jobs from the same pipeline so that they can
convey the nature of the jobs in the workflow graph. They can either
set setup and teardown jobs explicitly or it will be created implicitly as a virtual job.

Eg: The following pipeline workflow:
(~commit) -> (hub) -> (a) -> (b) -> (c) -> (target)

Assume a, b, and c are part of a stage.

Rules:
- Users should be able to define pipeline stages, and add a job to a stage.
- A job can belong to only one stage.
- Relation between jobs will still be defined using requires.
- Jobs with empty requires (requires:[]) indicate the start of a stage.
- Setup and teardown jobs can be implicitly or explicitly created.
- Teardown build should run when execution of any job in a stage is not successful; downstream jobs should not continue, and stageBuild should be updated accordingly.
- Setup build should run before a stage.

Scenario: Downstream builds are not triggered if required stage is not successful.
Given an existing pipeline on branch "stageFail1" with the workflow jobs:
| job | requires |
| target | stage@simple_fail:teardown |
| stage@simple_fail:setup | ~hub |
And the pipeline has the following stages:
| stage | jobs |
| simple_fail | a, b, c |
When the "hub" job on branch "stageFail1" is started
And the "hub" build succeeded
And the "a" job is triggered
And the "a" build succeeded
And the "b" job is triggered
And the "b" build succeeded
And the "c" job is triggered
And the "c" build failed
Then the "stage@simple_fail" stageBuild status is "FAILURE"
And the "stage@simple_fail:teardown" job is triggered
And the "target" job is not triggered
And the "stage@simple_fail:teardown" build succeeded
And the "stage@simple_fail" stageBuild status is "FAILURE"

Scenario: Downstream builds within a stage are not triggered if upstream build in stage is not successful.
Given an existing pipeline on branch "stageFail2" with the workflow jobs:
| job | requires |
| target | stage@incomplete_fail:teardown |
| stage@incomplete_fail:setup | ~hub |
And the pipeline has the following stages:
| stage | jobs |
| incomplete_fail | a, b, c |
When the "hub" job on branch "stageFail2" is started
And the "hub" build succeeded
And the "a" job is triggered
And the "a" build succeeded
And the "b" job is triggered
And the "b" build failed
Then the "stage@incomplete_fail" stageBuild status is "FAILURE"
And the "stage@incomplete_fail:teardown" job is triggered
And the "c" job is not triggered
And the "target" job is not triggered
And the "stage@incomplete_fail:teardown" build succeeded
And the "stage@incomplete_fail" stageBuild status is "FAILURE"

Scenario: Downstream builds are triggered if required stage is successful.
Given an existing pipeline on branch "stageSuccess1" with the workflow jobs:
| job | requires |
| target | stage@simple_success:teardown |
| stage@simple_success:setup | ~hub |
And the pipeline has the following stages:
| stage | jobs |
| simple_success | a, b, c |
When the "hub" job on branch "stageSuccess1" is started
And the "hub" build succeeded
And the "a" job is triggered
And the "a" build succeeded
And the "b" job is triggered
And the "b" build succeeded
And the "c" job is triggered
And the "c" build succeeded
Then the "stage@simple_success" stageBuild status is "SUCCESS"
And the "stage@simple_success:teardown" job is triggered
And the "target" job is triggered
And the "target" build succeeded
And the "stage@simple_success:teardown" build succeeded
And the "stage@simple_success" stageBuild status is "SUCCESS"

Scenario: Downstream builds are not triggered if stage setup job is not successful.
Given an existing pipeline on branch "setupFail" with the workflow jobs:
| job | requires |
| target | stage@setup_fail:teardown |
| stage@setup_fail:setup | ~hub |
And the pipeline has the following stages:
| stage | jobs |
| setup_fail | a, b, c |
When the "hub" job on branch "setupFail" is started
And the "hub" build succeeded
And the "stage@setup_fail:setup" job is triggered
And the "stage@setup_fail:setup" build failed
Then the "stage@setup_fail" stageBuild status is "FAILURE"
And the "stage@setup_fail:teardown" job is triggered
And the "a" job is not triggered
And the "target" job is not triggered
And the "stage@setup_fail:teardown" build succeeded
And the "stage@setup_fail" stageBuild status is "FAILURE"

Scenario: Downstream builds are not triggered if stage teardown job is not successful.
Given an existing pipeline on branch "teardownFail" with the workflow jobs:
| job | requires |
| target | stage@teardown_fail:teardown |
| stage@teardown_fail:setup | ~hub |
And the pipeline has the following stages:
| stage | jobs |
| teardown_fail | a, b, c |
When the "hub" job on branch "teardownFail" is started
And the "hub" build succeeded
And the "a" job is triggered
And the "a" build succeeded
And the "b" job is triggered
And the "b" build succeeded
And the "c" job is triggered
And the "c" build succeeded
Then the "stage@teardown_fail" stageBuild status is "SUCCESS"
And the "stage@teardown_fail:teardown" job is triggered
And the "stage@teardown_fail:teardown" build failed
And the "stage@teardown_fail" stageBuild status is "FAILURE"
And the "target" job is not triggered

Scenario: Downstream stage is triggered if required stage is successful.
Given an existing pipeline on branch "twoStageSuccess" with the workflow jobs:
| job | requires |
| target | stage@simple_success2:teardown |
| stage@simple_success2:setup | stage@simple_success1:teardown |
| stage@simple_success1:setup | ~hub |
And the pipeline has the following stages:
| stage | jobs |
| simple_success1 | a, b |
| simple_success2 | c, d |
When the "hub" job on branch "twoStageSuccess" is started
And the "hub" build succeeded
And the "a" job is triggered
And the "a" build succeeded
And the "b" job is triggered
And the "b" build succeeded
And the "stage@simple_success1:teardown" job is triggered
Then the "stage@simple_success1" stageBuild status is "SUCCESS"
And the "c" job is triggered
And the "c" build succeeded
And the "d" job is triggered
And the "d" build succeeded
And the "stage@simple_success2:teardown" job is triggered
And the "stage@simple_success2:teardown" build succeeded
And the "stage@simple_success2" stageBuild status is "SUCCESS"
And the "target" job is triggered
And the "target" build succeeded
57 changes: 57 additions & 0 deletions features/step_definitions/stage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
'use strict';

const Assert = require('chai').assert;
const { Before, Given, Then } = require('@cucumber/cucumber');
const sdapi = require('../support/sdapi');

const TIMEOUT = 240 * 1000;

Before(
{
tags: '@stage'
},
function hook() {
this.repoOrg = this.testOrg;
this.repoName = 'functional-stage';
this.buildId = null;
this.eventId = null;
this.pipelineId = null;
this.stageName = null;
this.stageId = null;
this.hubJobId = null;
this.pipelines = {};
}
);

Given(
/^the pipeline has the following stages:$/,
{
timeout: TIMEOUT
},
async function step(table) {
await this.ensureStageExists({
table
});
}
);

Then(
/^the "(?:stage@(simple_fail|incomplete_fail|simple_success))" stageBuild status is "(SUCCESS|FAILURE)"$/,
{ timeout: TIMEOUT },
async function step(stage, stageBuildStatus) {
const config = {
eventId: this.eventId,
jwt: this.jwt,
stageId: this.stageId,
instance: this.instance,
desiredStatus: stageBuildStatus
};

return sdapi.waitForStageBuildStatus(config).then(stageBuild => {
Assert.equal(stageBuild.status, stageBuildStatus);

this.stageBuildId = stageBuild.id;
this.stageBuildStatus = stageBuild.status;
});
}
);
6 changes: 4 additions & 2 deletions features/step_definitions/trigger.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,11 +140,11 @@ When(
);

When(
/^the "(fail_A|success_A|parallel_A)" job on branch "(?:.*)" is started$/,
/^the "(fail_A|success_A|parallel_A|hub)" job on branch "([^"]*)" is started$/,
{
timeout: TIMEOUT
},
function step(jobName) {
function step(jobName, branchName) {
const jobId = jobName ? this[`${jobName}JobId`] : this.jobId;
const buildVarName = jobName ? `${jobName}BuildId` : 'buildId';

Expand All @@ -162,6 +162,8 @@ When(

this[buildVarName] = resp.body.id;
this.buildId = resp.body.id;
this.branchName = branchName;
this.eventId = resp.body.eventId;
});
}
);
Expand Down
33 changes: 18 additions & 15 deletions features/step_definitions/workflow.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,23 +160,26 @@ Then(
timeout: TIMEOUT
},
function step(jobName) {
return sdapi
.searchForBuild({
instance: this.instance,
pipelineId: this.pipelineId,
desiredSha: this.sha,
desiredStatus: ['QUEUED', 'RUNNING', 'SUCCESS', 'FAILURE'],
jobName,
jwt: this.jwt
})
.then(build => {
this.eventId = build.eventId;
const job = this.jobs.find(j => j.name === jobName);
const config = {
instance: this.instance,
pipelineId: this.pipelineId,
desiredStatus: ['QUEUED', 'RUNNING', 'SUCCESS', 'FAILURE'],
jobName,
jwt: this.jwt
};

if (this.sha) {
config.desiredSha = this.sha;
}

Assert.equal(build.jobId, job.id);
return sdapi.searchForBuild(config).then(build => {
this.eventId = build.eventId;
const job = this.jobs.find(j => j.name === jobName);

this.buildId = build.id;
});
Assert.equal(build.jobId, job.id);

this.buildId = build.id;
});
}
);

Expand Down
42 changes: 39 additions & 3 deletions features/support/sdapi.js
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,7 @@ function searchForBuilds(config) {
* @return {Object} Build data
*/
function waitForBuildStatus(config) {
const { buildId } = config;
const { desiredStatus } = config;
const { instance } = config;
const { buildId, desiredStatus, instance } = config;

return request({
method: 'GET',
Expand All @@ -270,6 +268,43 @@ function waitForBuildStatus(config) {
});
}

/**
* Waits for a specific stageBuild to reach a desired status. If a stageBuild is found to not be
* in the desired state, it waits an arbitrarily short amount of time before querying
* the stageBuild status again.
*
* @method waitForStageBuildStatus
* @param {Object} config Configuration object
* @param {Number} config.eventId Event ID
* @param {String} config.instance Screwdriver instance to test against
* @param {Number} config.stageId Stage ID
* @param {String} config.jwt JWT
* @param {String} config.desiredStatus Desired status
* @return {Object} StageBuild data
*/
function waitForStageBuildStatus(config) {
const { eventId, desiredStatus, instance, jwt, stageId } = config;

return request({
method: 'GET',
url: `${instance}/v4/events/${eventId}/stageBuilds`,
context: {
token: jwt
}
}).then(response => {
const stageBuildData = response.body;

// Find stageBuild for stage
const stageBuild = stageBuildData.find(sb => sb.stageId === stageId);

if (stageBuild && stageBuild.status === desiredStatus) {
return stageBuild;
}

return promiseToWait(WAIT_TIME).then(() => waitForStageBuildStatus(config));
});
}

/**
* Remove the test token
* @method cleanupToken
Expand Down Expand Up @@ -385,5 +420,6 @@ module.exports = {
searchForBuild,
searchForBuilds,
waitForBuildStatus,
waitForStageBuildStatus,
promiseToWait
};
Loading

0 comments on commit d46fbd9

Please sign in to comment.