Skip to content

Commit 43a971e

Browse files
committed
test(fargate): add test for early Fargate task termination
1 parent c3be075 commit 43a971e

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
config:
2+
target: http://asciiart.artillery.io:8080
3+
phases:
4+
- duration: 10m
5+
arrivalRate: 1
6+
scenarios:
7+
- name: load homepage
8+
flow:
9+
- get:
10+
url: "/dino"
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
const { test, before, beforeEach } = require('tap');
2+
const { $ } = require('zx');
3+
const fs = require('fs');
4+
const got = require('got');
5+
const generateId = require('../../../lib/util/generate-id');
6+
const region = 'us-east-1';
7+
const AWS = require('aws-sdk');
8+
const ecs = new AWS.ECS({
9+
apiVersion: '2014-11-13',
10+
region
11+
});
12+
13+
const { generateTmpReportPath, getTestTags } = require('../../helpers');
14+
const sleep = require('../../helpers/sleep');
15+
16+
const {
17+
checkForNegativeValues,
18+
checkAggregateCounterSums
19+
} = require('../../helpers/expectations');
20+
21+
const A9_PATH = process.env.A9_PATH || 'artillery';
22+
const baseTags = getTestTags(['type:acceptance']);
23+
24+
before(async () => {
25+
await $`${A9_PATH} -V`;
26+
});
27+
28+
beforeEach(async (t) => {
29+
$.verbose = true;
30+
t.context.reportFilePath = generateTmpReportPath(t.name, 'json');
31+
});
32+
33+
test('Correctly handles early task termination', async (t) => {
34+
const scenarioPath = `${__dirname}/fixtures/sigterm.yml`;
35+
process.env.ARTILLERY_TEST_RUN_ID = generateId('t');
36+
37+
let testRunProcess;
38+
let exitCode;
39+
let output;
40+
41+
function setTestRunInfo(info) {
42+
exitCode = info?.exitCode;
43+
output = info?.stdout;
44+
}
45+
// We run the test but do not await as we need to stop the Fargate task while the test is running
46+
// We use the setTestRunInfo function for both resolve and reject cases of the testRunProcess promise (if Artillery exits early like it should, the promise will be rejected)
47+
testRunProcess =
48+
$`${A9_PATH} run-fargate ${scenarioPath} --output ${t.context.reportFilePath} --record --tags ${baseTags}`.then(
49+
setTestRunInfo,
50+
setTestRunInfo
51+
);
52+
53+
// We use Artillery's Cloud API to get task ID and check if the test started
54+
const testRunCloudEndpoint = `${process.env.ARTILLERY_CLOUD_ENDPOINT}/api/load-tests/${process.env.ARTILLERY_TEST_RUN_ID}`;
55+
56+
const maxRetry = 5;
57+
const delay = 30000;
58+
let retryNum = 0;
59+
let res;
60+
let testStarted;
61+
while (!testStarted && retryNum <= maxRetry) {
62+
await sleep(delay);
63+
try {
64+
res = await got(testRunCloudEndpoint, {
65+
headers: {
66+
'x-auth-token': process.env.ARTILLERY_CLOUD_API_KEY
67+
},
68+
throwHttpErrors: false
69+
});
70+
} catch (err) {
71+
t.error(`Error fetching data from Artillery Cloud API: ${err}`);
72+
}
73+
// Make sure the workers have started before stopping the task
74+
testStarted =
75+
res?.body &&
76+
JSON.parse(res.body).events?.some(
77+
(event) => event.eventName === 'phaseStarted'
78+
);
79+
retryNum++;
80+
}
81+
82+
const taskIds = JSON.parse(res.body).tasks;
83+
taskIds.forEach(async (taskId) => {
84+
console.log('Stopping task: ', taskId);
85+
try {
86+
const stoppedTask = await ecs
87+
.stopTask({ task: taskId, cluster: 'artilleryio-cluster' })
88+
.promise();
89+
} catch (err) {
90+
t.error(`Error calling ecs.stopTask: ${err}`);
91+
}
92+
});
93+
94+
// If testRunProcess takes longer than 60sec to finish, it means the process didn't exit early.
95+
96+
const timeout = new Promise((resolve) => {
97+
setTimeout(() => {
98+
resolve(
99+
new Error('Artillery test run did not exit within 60s as expected.')
100+
);
101+
}, 60000);
102+
});
103+
104+
try {
105+
await Promise.race([testRunProcess, timeout]);
106+
} catch (err) {
107+
t.fail(err.message);
108+
}
109+
110+
const reportExists = fs.existsSync(t.context.reportFilePath);
111+
t.ok(exitCode && output, 'Artillery should exit early when task is stopped');
112+
t.equal(exitCode, 7, 'Exit code should be 7');
113+
t.ok(output.includes('Summary report'), 'Should log the summary report');
114+
t.ok(reportExists, 'Should generate report file');
115+
116+
if (reportExists) {
117+
const report = JSON.parse(
118+
fs.readFileSync(t.context.reportFilePath, 'utf8')
119+
);
120+
checkForNegativeValues(t, report);
121+
checkAggregateCounterSums(t, report);
122+
}
123+
});

0 commit comments

Comments
 (0)