Skip to content

Commit 2d1a1f1

Browse files
Merge branch 'main' into bernardobridge/art-1547-allow-beforeafter-validate-script-schema-to-run-arbitrary
2 parents 4baa50b + 049205b commit 2d1a1f1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1705
-62
lines changed

.github/workflows/docker-ecs-worker-image.yml

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,15 @@ on:
55
push:
66
branches:
77
- main
8+
workflow_call:
9+
inputs:
10+
ref:
11+
description: 'Branch ref to checkout. Needed for pull_request_target to be able to pull correct ref.'
12+
type: string
13+
secrets:
14+
ECR_WORKER_IMAGE_PUSH_ROLE_ARN:
15+
description: 'ARN of the IAM role to assume to push the image to ECR.'
16+
required: true
817

918
permissions:
1019
id-token: write
@@ -16,6 +25,7 @@ jobs:
1625
steps:
1726
- uses: actions/checkout@v3
1827
with:
28+
ref: ${{ inputs.ref || null }}
1929
fetch-depth: 0
2030

2131
- name: Show git ref

.github/workflows/run-aws-tests.yml

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
name: Run AWS tests
2+
3+
on:
4+
pull_request_target:
5+
branches: [main]
6+
#opened, reopened and synchronize will cause the workflow to fail on forks due to permissions
7+
#once labeled, that will then be overridden by the is-collaborator job
8+
types: [opened, labeled, synchronize, reopened]
9+
10+
jobs:
11+
is-collaborator:
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Get User Permission
15+
id: checkAccess
16+
uses: actions-cool/check-user-permission@cd622002ff25c2311d2e7fb82107c0d24be83f9b
17+
with:
18+
require: write
19+
username: ${{ github.actor }}
20+
env:
21+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
22+
- name: Check User Permission
23+
if: steps.checkAccess.outputs.require-result == 'false'
24+
run: |
25+
echo "${{ github.actor }} does not have permissions on this repo."
26+
echo "Current permission level is ${{ steps.checkAccess.outputs.user-permission }}"
27+
exit 1
28+
29+
publish-branch-image:
30+
if: contains( github.event.pull_request.labels.*.name, 'run-aws-tests' )
31+
needs: is-collaborator
32+
uses: ./.github/workflows/docker-ecs-worker-image.yml
33+
permissions:
34+
contents: read
35+
id-token: write
36+
secrets:
37+
ECR_WORKER_IMAGE_PUSH_ROLE_ARN: ${{ secrets.ECR_WORKER_IMAGE_PUSH_ROLE_ARN }}
38+
with:
39+
ref: ${{ github.event.pull_request.head.sha || null }} # this should only be run with this ref if is-collaborator has been run and passed
40+
41+
run-tests:
42+
if: contains( github.event.pull_request.labels.*.name, 'run-aws-tests' )
43+
needs: publish-branch-image
44+
timeout-minutes: 40
45+
runs-on: ubuntu-latest
46+
permissions:
47+
contents: read
48+
id-token: write
49+
steps:
50+
- uses: actions/checkout@v3
51+
with:
52+
ref: ${{ github.event.pull_request.head.sha || null }} # this should only be run with this ref if is-collaborator has been run and passed
53+
- name: Configure AWS Credentials
54+
uses: aws-actions/configure-aws-credentials@v2
55+
env:
56+
SHOW_STACK_TRACE: true
57+
with:
58+
aws-region: eu-west-1
59+
role-to-assume: ${{ secrets.ARTILLERY_AWS_CLI_ROLE_ARN_TEST1 }}
60+
role-session-name: OIDCSession
61+
mask-aws-account-id: true
62+
- name: Use Node.js 18.x
63+
uses: actions/setup-node@v2
64+
with:
65+
node-version: 18.x
66+
- run: npm install
67+
- run: npm run build
68+
- run: npm run test:aws --workspace artillery
69+
env:
70+
FORCE_COLOR: 1
71+
ECR_IMAGE_VERSION: ${{ github.sha }} # the image is published with the sha of the commit within this repo
72+
ARTILLERY_CLOUD_ENDPOINT: ${{ secrets.ARTILLERY_CLOUD_ENDPOINT_TEST }}
73+
ARTILLERY_CLOUD_API_KEY: ${{ secrets.ARTILLERY_CLOUD_API_KEY_TEST }}
74+
GITHUB_REPO: ${{ github.repository }}
75+
GITHUB_ACTOR: ${{ github.actor }}

packages/artillery/lib/cmds/run.js

+28-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ const fs = require('fs');
2222
const path = require('path');
2323
const crypto = require('crypto');
2424
const os = require('os');
25-
const esbuild = require('esbuild-wasm');
2625
const createLauncher = require('../launch-platform');
2726
const createConsoleReporter = require('../../console-reporter');
2827

@@ -406,6 +405,10 @@ function replaceProcessorIfTypescript(script, scriptPath, platform) {
406405
`${processorFileName}-${Date.now()}.js`
407406
);
408407

408+
//TODO: move require to top of file when Lambda bundle size issue is solved
409+
//must be conditionally required for now as this package is removed in Lambda for now to avoid bigger package sizes
410+
const esbuild = require('esbuild-wasm');
411+
409412
try {
410413
esbuild.buildSync({
411414
entryPoints: [actualProcessorPath],
@@ -634,6 +637,30 @@ async function sendTelemetry(script, flags, extraProps) {
634637
}
635638
}
636639

640+
// publish-metrics reporters
641+
if (script.config.plugins['publish-metrics']) {
642+
const OFFICIAL_REPORTERS = [
643+
'datadog',
644+
'open-telemetry',
645+
'lightstep',
646+
'newrelic',
647+
'splunk',
648+
'dynatrace',
649+
'cloudwatch',
650+
'honeycomb',
651+
'mixpanel',
652+
'prometheus'
653+
];
654+
655+
properties.officialMonitoringReporters = script.config.plugins[
656+
'publish-metrics'
657+
].map((reporter) => {
658+
if (OFFICIAL_REPORTERS.includes(reporter.type)) {
659+
return reporter.type;
660+
}
661+
});
662+
}
663+
637664
// before/after hooks
638665
if (script.before) {
639666
properties.beforeHook = true;

packages/artillery/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
"test:unit": "export ARTILLERY_TELEMETRY_DEFAULTS='{\"source\":\"test-suite\"}' && tap --no-coverage --timeout=420 --color test/unit/*.test.js",
4747
"test:acceptance": "export ARTILLERY_TELEMETRY_DEFAULTS='{\"source\":\"test-suite\"}' && tap --no-coverage --timeout=420 test/cli/*.test.js && bash test/lib/run.sh && tap --no-coverage --color test/testcases/plugins/*.test.js",
4848
"test": "npm run test:unit && npm run test:acceptance",
49-
"test:cloud": "export ARTILLERY_TELEMETRY_DEFAULTS='{\"source\":\"test-suite\"}' && tap --no-coverage --timeout=300 test/cloud-e2e/*.test.js",
49+
"test:aws": "export ARTILLERY_TELEMETRY_DEFAULTS='{\"source\":\"test-suite\"}' && tap --no-coverage --color --timeout=3600 test/cloud-e2e/**/*.test.js",
5050
"lint": "eslint --ext \".js,.ts,.tsx\" .",
5151
"lint-fix": "npm run lint -- --fix"
5252
},

packages/artillery/test/cli/_helpers.js

+25-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const path = require('path');
55
const os = require('os');
66
const { getBinPathSync } = require('get-bin-path');
77
const a9path = getBinPathSync();
8+
const { createHash } = require('crypto');
89

910
async function execute(args, options) {
1011
try {
@@ -30,4 +31,27 @@ async function getRootPath(filename) {
3031
return path.resolve(__dirname, '..', '..', filename);
3132
}
3233

33-
module.exports = { execute, deleteFile, getRootPath, returnTmpPath };
34+
function generateTmpReportPath(testName, extension) {
35+
return returnTmpPath(
36+
`report-${createHash('md5')
37+
.update(testName)
38+
.digest('hex')}-${Date.now()}.${extension}`
39+
);
40+
}
41+
42+
function getTestTags(additionalTags) {
43+
const actorTag = `actor:${process.env.GITHUB_ACTOR || 'localhost'}`;
44+
const repoTag = `repo:${process.env.GITHUB_REPO || 'artilleryio/artillery'}`;
45+
const ciTag = `ci:${process.env.GITHUB_ACTIONS ? 'true' : 'false'}`;
46+
47+
return `${repoTag},${actorTag},${ciTag},${additionalTags.join(',')}`;
48+
}
49+
50+
module.exports = {
51+
execute,
52+
deleteFile,
53+
getRootPath,
54+
returnTmpPath,
55+
generateTmpReportPath,
56+
getTestTags
57+
};

packages/artillery/test/cli/command-report.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
const tap = require('tap');
2-
const { execute, returnTmpPath } = require('../cli/_helpers.js');
2+
const { execute, generateTmpReportPath } = require('../cli/_helpers.js');
33

44
tap.test('If we report specifying output, no browser is opened', async (t) => {
5-
const outputFilePath = returnTmpPath('report.html');
5+
const outputFilePath = generateTmpReportPath(t.name, 'html');
66

77
const [exitCode] = await execute([
88
'report',

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

+3-7
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,16 @@ const {
33
execute,
44
deleteFile,
55
getRootPath,
6-
returnTmpPath
6+
returnTmpPath,
7+
generateTmpReportPath
78
} = require('../cli/_helpers.js');
89
const fs = require('fs');
910
const path = require('path');
1011
const execa = require('execa');
11-
const { createHash } = require('crypto');
1212

1313
let reportFilePath;
1414
tap.beforeEach(async (t) => {
15-
reportFilePath = returnTmpPath(
16-
`report-${createHash('md5')
17-
.update(t.name)
18-
.digest('hex')}-${Date.now()}.json`
19-
);
15+
reportFilePath = generateTmpReportPath(t.name, 'json');
2016
});
2117

2218
tap.test('Run a simple script', async (t) => {

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

+2-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
const tap = require('tap');
2-
const { execute, returnTmpPath } = require('../cli/_helpers.js');
3-
const { createHash } = require('crypto');
2+
const { execute, generateTmpReportPath } = require('../cli/_helpers.js');
43
const fs = require('fs');
54

65
let reportFilePath;
76
tap.beforeEach(async (t) => {
8-
reportFilePath = returnTmpPath(
9-
`report-${createHash('md5')
10-
.update(t.name)
11-
.digest('hex')}-${Date.now()}.json`
12-
);
7+
reportFilePath = generateTmpReportPath(t.name, 'json');
138
});
149

1510
tap.test('Can run a Typescript processor', async (t) => {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
config:
2+
target: "http://asciiart.artillery.io:8080"
3+
plugins:
4+
expect: {}
5+
ensure:
6+
p99: 10000
7+
thresholds:
8+
- "http.response_time.p95": 1
9+
phases:
10+
- duration: 5
11+
arrivalRate: 1
12+
scenarios:
13+
- name: expect-ensure-exit-condition-test
14+
flow:
15+
- get:
16+
url: "/"
17+
expect:
18+
- statusCode: 300
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
config:
2+
target: "http://asciiart.artillery.io:8080"
3+
plugins:
4+
expect: {}
5+
phases:
6+
- duration: 5
7+
arrivalRate: 1
8+
scenarios:
9+
- name: expect-exit-condition-test
10+
flow:
11+
- get:
12+
url: "/"
13+
expect:
14+
- statusCode: 300
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
DOTENV1=/
2+
DOTENV2=/dino
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
config:
2+
target: http://asciiart.artillery.io:8080
3+
plugins:
4+
ensure: {}
5+
phases:
6+
- duration: 20
7+
arrivalRate: 1
8+
ensure:
9+
p99: 10000
10+
thresholds:
11+
- "http.response_time.p99": 10000
12+
scenarios:
13+
- name: load homepage
14+
flow:
15+
- get:
16+
url: "{{$processEnvironment.SECRET1}}"
17+
- get:
18+
url: "{{$processEnvironment.SECRET2}}"
19+
- get:
20+
url: "{{$processEnvironment.DOTENV1}}"
21+
- get:
22+
url: "{{$processEnvironment.DOTENV2}}"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
config:
2+
target: http://asciiart.artillery.io:8080
3+
phases:
4+
- arrivalRate: 1
5+
duration: 60
6+
defaults:
7+
headers:
8+
user-agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"
9+
processor: "./processor.js"
10+
scenarios:
11+
- flow:
12+
- get:
13+
url: "/"
14+
afterResponse: logOutput
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
function logOutput(req, res, userContext, events, done) {
2+
for (let i = 0; i < 10; i++) {
3+
events.emit(
4+
'counter',
5+
`very.very.long.name.for.a.counter.metric.so.that.we.generate.a.lot.of.console.output.${Date.now()}${i}`,
6+
1
7+
);
8+
events.emit(
9+
'histogram',
10+
`very.very.long.name.for.a.histogram.metric.so.that.we.generate.a.lot.of.console.output.${Date.now()}${i}`,
11+
100
12+
);
13+
}
14+
return done();
15+
}
16+
17+
module.exports = {
18+
logOutput
19+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# 10 VUs, making 10 calls each to a function that allocates 100MB -> ~10GB total
2+
# This will fail with default configuration, which is:
3+
# - 4GB RAM given to workers on Fargate
4+
# - Node.js memory limit of 4GB
5+
6+
config:
7+
target: http://asciiart.artillery.io:8080
8+
phases:
9+
- arrivalRate: 1
10+
duration: 10
11+
processor: './processor.js'
12+
13+
scenarios:
14+
- flow:
15+
- loop:
16+
- get:
17+
url: "/armadillo"
18+
beforeRequest: hogSomeRam
19+
- think: 1
20+
count: 10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
let data = [];
2+
3+
console.log('NODE_OPTIONS:');
4+
console.log(process.env.NODE_OPTIONS);
5+
6+
function hogSomeRam(req, context, events, next) {
7+
// Allocate 100MB
8+
data.push(Buffer.alloc(1024 * 1024 * 100, 1));
9+
10+
console.log(new Date(), 'allocated more data');
11+
console.log('RSS (MB):', process.memoryUsage().rss / 1024 / 1024);
12+
13+
return next();
14+
}
15+
16+
module.exports = {
17+
hogSomeRam
18+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const bc = require('@babel/core');
2+
const uuid = require('uuid');
3+
const client = require('aws-sdk/clients/lambda');
4+
5+
module.exports = {
6+
setUrl: require('./set-url')
7+
};

0 commit comments

Comments
 (0)