Skip to content

Commit c2d53ba

Browse files
authored
Merge pull request #2522 from artilleryio/feat/async-hooks
2 parents 095cc1d + 6c8e3f5 commit c2d53ba

File tree

15 files changed

+179
-68
lines changed

15 files changed

+179
-68
lines changed

packages/artillery-plugin-memory-inspector/index.js

-2
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ function ArtilleryPluginMemoryInspector(script, events) {
7878
continue;
7979
}
8080
}
81-
82-
return next();
8381
}
8482

8583
script.scenarios = script.scenarios.map((scenario) => {

packages/artillery/lib/platform/aws-ecs/legacy/bom.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const fs = require('fs');
33
const A = require('async');
44

55
const isBuiltinModule = require('is-builtin-module');
6-
const detective = require('detective');
6+
const detective = require('detective-es6');
77
const depTree = require('dependency-tree');
88

99
const walkSync = require('walk-sync');

packages/artillery/lib/platform/local/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class PlatformLocal {
103103
return {};
104104
}
105105

106-
const runnableScript = loadProcessor(
106+
const runnableScript = await loadProcessor(
107107
prepareScript(this.script, _.cloneDeep(this.payload)),
108108
this.opts
109109
);

packages/artillery/lib/platform/local/worker.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ async function prepare(opts) {
106106
});
107107

108108
const { script: _script, payload, options } = opts;
109-
const script = loadProcessor(_script, options);
109+
const script = await loadProcessor(_script, options);
110110

111111
global.artillery.testRunId = opts.testRunId;
112112

packages/artillery/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@
110110
"csv-parse": "^4.16.3",
111111
"debug": "^4.3.1",
112112
"dependency-tree": "^10.0.9",
113-
"detective": "^5.1.0",
113+
"detective-es6": "^4.0.1",
114114
"dotenv": "^16.0.1",
115115
"esbuild-wasm": "^0.19.8",
116116
"eventemitter3": "^4.0.4",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
const tap = require('tap');
2+
const { execute, generateTmpReportPath } = require('../cli/_helpers.js');
3+
const fs = require('fs');
4+
5+
let reportFilePath;
6+
tap.beforeEach(async (t) => {
7+
reportFilePath = generateTmpReportPath(t.name, 'json');
8+
});
9+
10+
tap.test('async hooks with ESM', async (t) => {
11+
const [exitCode, output] = await execute([
12+
'run',
13+
'-o',
14+
`${reportFilePath}`,
15+
'test/scripts/scenario-async-esm-hooks/test.yml'
16+
]);
17+
18+
t.equal(exitCode, 0, 'CLI should exit with code 0');
19+
t.ok(
20+
output.stdout.includes('Got context using lodash: true'),
21+
'Should be able to use lodash in a scenario to get context'
22+
);
23+
const json = JSON.parse(fs.readFileSync(reportFilePath, 'utf8'));
24+
25+
console.log(output);
26+
27+
t.equal(
28+
json.aggregate.counters['http.codes.200'],
29+
10,
30+
'Should have made 10 requests'
31+
);
32+
33+
t.equal(
34+
json.aggregate.counters['hey_from_esm'],
35+
10,
36+
'Should have emitted 10 custom metrics from ts processor'
37+
);
38+
39+
t.equal(
40+
json.aggregate.counters['errors.error_from_async_hook'],
41+
10,
42+
'Should have emitted 10 errors from an exception in an async hook'
43+
);
44+
45+
t.equal(
46+
json.aggregate.counters['vusers.failed'],
47+
10,
48+
'Should have no completed VUs'
49+
);
50+
});

packages/artillery/test/cli/run-typescript.test.js

-31
Original file line numberDiff line numberDiff line change
@@ -95,34 +95,3 @@ tap.test('Runs correctly when package is marked as external', async (t) => {
9595

9696
await deleteFile(bundleLocation);
9797
});
98-
99-
tap.test(
100-
'Failure from a Typescript processor has a resolvable stack trace via source maps',
101-
async (t) => {
102-
const [exitCode, output] = await execute([
103-
'run',
104-
'-o',
105-
`${reportFilePath}`,
106-
'test/scripts/scenarios-typescript/error.yml'
107-
]);
108-
109-
t.equal(exitCode, 11, 'CLI should exit with code 11');
110-
t.ok(
111-
output.stdout.includes('error_from_ts_processor'),
112-
'Should have logged error from ts processor'
113-
);
114-
115-
// // Search for the path
116-
// const pathRegex = /\((.*?):\d+:\d+\)/;
117-
// const match = output.stdout.match(pathRegex);
118-
119-
// // Extract the path if found
120-
// const extractedPath = match ? match[1] : null;
121-
122-
// t.ok(
123-
// extractedPath.includes('.ts'),
124-
// 'Should be using source maps to resolve the path to a .ts file'
125-
// );
126-
// t.ok(fs.existsSync(extractedPath), 'Error path should exist');
127-
}
128-
);

packages/artillery/test/cloud-e2e/fargate/fixtures/ts-external-pkg/processor.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,11 @@ const AddressSchema = z.object({
99
country: z.string()
1010
});
1111

12-
export const checkAddress = async (context, ee, next) => {
12+
export const checkAddress = async (context, ee) => {
1313
const address = context.vars.address;
1414
const result = AddressSchema.safeParse(address);
1515

1616
if (!result.success) {
1717
ee.emit('error', 'invalid_address');
1818
}
19-
20-
next();
2119
};

packages/artillery/test/cloud-e2e/fargate/run-fargate.test.js

+26
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const {
77
getTestTags,
88
execute
99
} = require('../../cli/_helpers.js');
10+
const path = require('path');
1011

1112
const A9 = process.env.A9 || 'artillery';
1213

@@ -224,6 +225,31 @@ test('Run with typescript processor and external package', async (t) => {
224225
);
225226
});
226227

228+
test('Run a test with an ESM processor', async (t) => {
229+
// The main thing we're checking here is that ESM + dependencies get bundled correctly by BOM
230+
const scenarioPath = path.resolve(
231+
`${__dirname}/../../scripts/scenario-async-esm-hooks/test.yml`
232+
);
233+
234+
const output =
235+
await $`${A9} run-fargate ${scenarioPath} --output ${reportFilePath} --record --tags ${baseTags}`;
236+
237+
t.equal(output.exitCode, 0, 'CLI exit code should be 0');
238+
239+
const report = JSON.parse(fs.readFileSync(reportFilePath, 'utf8'));
240+
t.equal(
241+
report.aggregate.counters['http.codes.200'],
242+
10,
243+
'Should have made 10 requests'
244+
);
245+
246+
t.equal(
247+
report.aggregate.counters['hey_from_esm'],
248+
10,
249+
'Should have emitted 10 custom metrics from ts processor'
250+
);
251+
});
252+
227253
test('Run lots-of-output', async (t) => {
228254
$.verbose = false; // we don't want megabytes of output on the console
229255

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import _ from 'lodash';
2+
3+
export const emitCustomMetric = async (context, ee) => {
4+
const isESM = _.get(context, 'vars.isESM');
5+
console.log(`Got context using lodash: ${JSON.stringify(isESM)}`);
6+
ee.emit('counter', 'hey_from_esm', 1);
7+
};
8+
9+
export const hookThatThrows = async (context, ee) => {
10+
throw new Error('error_from_async_hook');
11+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
config:
2+
target: "http://asciiart.artillery.io:8080"
3+
phases:
4+
- duration: 10
5+
arrivalRate: 1
6+
name: "Phase 1"
7+
processor: "./helpers.mjs"
8+
variables:
9+
isESM: true
10+
11+
scenarios:
12+
- flow:
13+
- function: emitCustomMetric
14+
- get:
15+
url: "/"
16+
- function: hookThatThrows
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
import _ from 'lodash';
22

3-
export const myTest = async (context, ee, next) => {
3+
export const myTest = async (context, ee) => {
44
const isTypescript = _.get(context, 'vars.isTypescript');
55

66
console.log(`Got context using lodash: ${JSON.stringify(isTypescript)}`);
77

88
ee.emit('counter', 'hey_from_ts', 1);
9-
10-
next();
119
};
1210

13-
export const processorWithError = async (context, ee, next) => {
11+
export const processorWithError = async (context, ee) => {
1412
throw new Error('error_from_ts_processor');
15-
next();
1613
};

packages/core/lib/engine_http.js

+41-15
Original file line numberDiff line numberDiff line change
@@ -220,9 +220,19 @@ HttpEngine.prototype.step = function step(requestSpec, ee, opts) {
220220
return function (context, callback) {
221221
let processFunc = self.config.processor[requestSpec.function];
222222
if (processFunc) {
223-
return processFunc(context, ee, function (hookErr) {
224-
return callback(hookErr, context);
225-
});
223+
if (processFunc.constructor.name === 'Function') {
224+
return processFunc(context, ee, function (hookErr) {
225+
return callback(hookErr, context);
226+
});
227+
} else {
228+
return processFunc(context, ee)
229+
.then(() => {
230+
callback(null, context);
231+
})
232+
.catch((err) => {
233+
callback(err, context);
234+
});
235+
}
226236
} else {
227237
debug(`Function "${requestSpec.function}" not defined`);
228238
debug('processor: %o', self.config.processor);
@@ -309,12 +319,16 @@ HttpEngine.prototype.step = function step(requestSpec, ee, opts) {
309319
console.log(`WARNING: custom function ${fn} could not be found`); // TODO: a 'warning' event
310320
}
311321

312-
processFunc(requestParams, context, ee, function (err) {
313-
if (err) {
314-
return next(err);
315-
}
316-
return next(null);
317-
});
322+
if (processFunc.constructor.name === 'Function') {
323+
processFunc(requestParams, context, ee, function (err) {
324+
if (err) {
325+
return next(err);
326+
}
327+
return next(null);
328+
});
329+
} else {
330+
processFunc(requestParams, context, ee).then(next).catch(next);
331+
}
318332
},
319333
function done(err) {
320334
if (err) {
@@ -608,12 +622,24 @@ HttpEngine.prototype.step = function step(requestSpec, ee, opts) {
608622
// Got does not have res.body which Request.js used to have, so we attach it here:
609623
res.body = body;
610624

611-
processFunc(requestParams, res, context, ee, function (err) {
612-
if (err) {
613-
return next(err);
614-
}
615-
return next(null);
616-
});
625+
if (processFunc.constructor.name === 'Function') {
626+
processFunc(
627+
requestParams,
628+
res,
629+
context,
630+
ee,
631+
function (err) {
632+
if (err) {
633+
return next(err);
634+
}
635+
return next(null);
636+
}
637+
);
638+
} else {
639+
processFunc(requestParams, res, context, ee)
640+
.then(next)
641+
.catch(next);
642+
}
617643
},
618644
function (err) {
619645
if (err) {

packages/core/lib/engine_ws.js

+13-3
Original file line numberDiff line numberDiff line change
@@ -141,9 +141,19 @@ WSEngine.prototype.step = function (requestSpec, ee) {
141141
return function (context, callback) {
142142
const processFunc = self.config.processor[requestSpec.function];
143143
if (processFunc) {
144-
processFunc(context, ee, function () {
145-
return callback(null, context);
146-
});
144+
if (processFunc.constructor.name === 'Function') {
145+
processFunc(context, ee, function () {
146+
return callback(null, context);
147+
});
148+
} else {
149+
return processFunc(context, ee)
150+
.then(() => {
151+
callback(null, context);
152+
})
153+
.catch((err) => {
154+
callback(err, context);
155+
});
156+
}
147157
}
148158
};
149159
}

packages/core/lib/runner.js

+15-5
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,25 @@ function loadEngines(
7777
return { loadedEngines, warnings };
7878
}
7979

80-
function loadProcessor(script, options) {
80+
async function loadProcessor(script, options) {
81+
const absoluteScriptPath = path.resolve(process.cwd(), options.scriptPath);
8182
if (script.config.processor) {
82-
const absoluteScriptPath = path.resolve(process.cwd(), options.scriptPath);
8383
const processorPath = path.resolve(
8484
path.dirname(absoluteScriptPath),
8585
script.config.processor
8686
);
87-
const processor = require(processorPath);
88-
script.config.processor = processor;
87+
88+
if (processorPath.endsWith('.mjs')) {
89+
const exports = await import(processorPath);
90+
script.config.processor = Object.assign(
91+
{},
92+
script.config.processor,
93+
exports
94+
);
95+
} else {
96+
// CJS (possibly transplied from TS)
97+
script.config.processor = require(processorPath);
98+
}
8999
}
90100

91101
return script;
@@ -439,7 +449,7 @@ function createContext(script, contextVars, additionalProperties = {}) {
439449
$environment: script._environment,
440450
$processEnvironment: process.env, // TODO: deprecate
441451
$env: process.env,
442-
$testId: global.artillery.testRunId,
452+
$testId: global.artillery.testRunId
443453
},
444454
contextVars || {}
445455
),

0 commit comments

Comments
 (0)