Skip to content

Commit 14719e3

Browse files
authored
test(aws-serverless): Ensure aws-serverless E2E tests run locally (#20441)
I ran into a bunch of issues trying to run this locally. This adjusts things slightly so that it runs locally, taking care of setting up sam consistently. Relevant changes: * Renamed the e2e test application to be consistent with app name * Streamlined how/when we use a dedicated node version. Now, by default we use node 20 and a respective docker image. We also run variants on CI for node 18 and node 22. * Added a minimal samconfig.toml because I noticed locally it was loudly complaining about this missing * Streamlined the invocation of `sam` to be a bit more consistent. * Made sam stuff compatibler with macos arm setups, this failed locally for me. * Added a nicer error message if sam is not installed, as you need to manually install this locally to run this test.
1 parent 2798666 commit 14719e3

8 files changed

Lines changed: 75 additions & 37 deletions

File tree

dev-packages/e2e-tests/test-applications/aws-serverless/package.json

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
{
2-
"name": "aws-lambda-sam",
2+
"name": "aws-serverless",
33
"version": "1.0.0",
44
"private": true,
55
"type": "commonjs",
66
"scripts": {
77
"test": "playwright test",
88
"clean": "npx rimraf node_modules pnpm-lock.yaml",
9-
"test:build": "pnpm install && npx rimraf node_modules/@sentry/aws-serverless/nodejs",
9+
"test:pull-sam-image": "./pull-sam-image.sh",
10+
"test:build": "pnpm test:pull-sam-image && pnpm install && npx rimraf node_modules/@sentry/aws-serverless/nodejs",
1011
"test:assert": "pnpm test"
1112
},
1213
"//": "We just need the @sentry/aws-serverless layer zip file, not the NPM package",
@@ -15,12 +16,10 @@
1516
"@playwright/test": "~1.56.0",
1617
"@sentry-internal/test-utils": "link:../../../test-utils",
1718
"@sentry/aws-serverless": "link:../../../../packages/aws-serverless/build/aws/dist-serverless/",
18-
"@types/tmp": "^0.2.6",
1919
"aws-cdk-lib": "^2.210.0",
2020
"constructs": "^10.4.2",
2121
"glob": "^11.0.3",
22-
"rimraf": "^5.0.10",
23-
"tmp": "^0.2.5"
22+
"rimraf": "^5.0.10"
2423
},
2524
"volta": {
2625
"extends": "../../package.json"
@@ -34,12 +33,12 @@
3433
"sentryTest": {
3534
"variants": [
3635
{
37-
"build-command": "NODE_VERSION=20 ./pull-sam-image.sh && pnpm test:build",
38-
"assert-command": "NODE_VERSION=20 pnpm test:assert",
39-
"label": "aws-serverless (Node 20)"
36+
"build-command": "NODE_VERSION=22 pnpm test:build",
37+
"assert-command": "NODE_VERSION=22 pnpm test:assert",
38+
"label": "aws-serverless (Node 22)"
4039
},
4140
{
42-
"build-command": "NODE_VERSION=18 ./pull-sam-image.sh && pnpm test:build",
41+
"build-command": "NODE_VERSION=18 pnpm test:build",
4342
"assert-command": "NODE_VERSION=18 pnpm test:assert",
4443
"label": "aws-serverless (Node 18)"
4544
}

dev-packages/e2e-tests/test-applications/aws-serverless/pull-sam-image.sh

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
#!/bin/bash
22

3-
# Script to pull the correct Lambda docker image based on the NODE_VERSION environment variable.
3+
# Pull the Lambda Node docker image for SAM local. NODE_VERSION should be the major only (e.g. 20).
4+
# Defaults to 20 to match the repo's Volta Node major (see root package.json "volta.node").
45

56
set -e
67

7-
if [[ -z "$NODE_VERSION" ]]; then
8-
echo "Error: NODE_VERSION not set"
9-
exit 1
10-
fi
8+
NODE_VERSION="${NODE_VERSION:-20}"
119

1210
echo "Pulling Lambda Node $NODE_VERSION docker image..."
1311
docker pull "public.ecr.aws/lambda/nodejs:${NODE_VERSION}"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# SAM CLI expects this file in the project root; without it, `sam local start-lambda` logs
2+
# OSError / missing file errors when run from the e2e temp directory.
3+
# These values are placeholders — this app only uses `sam local`, not deploy.
4+
version = 0.1
5+
6+
[default.deploy.parameters]
7+
stack_name = "sentry-e2e-aws-serverless-local"
8+
region = "us-east-1"
9+
confirm_changeset = false
10+
capabilities = "CAPABILITY_IAM"

dev-packages/e2e-tests/test-applications/aws-serverless/src/stack.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,21 @@ import * as path from 'node:path';
44
import * as fs from 'node:fs';
55
import * as os from 'node:os';
66
import * as dns from 'node:dns/promises';
7-
import { platform } from 'node:process';
7+
import { arch, platform } from 'node:process';
88
import { globSync } from 'glob';
99
import { execFileSync } from 'node:child_process';
1010

1111
const LAMBDA_FUNCTIONS_WITH_LAYER_DIR = './src/lambda-functions-layer';
1212
const LAMBDA_FUNCTIONS_WITH_NPM_DIR = './src/lambda-functions-npm';
1313
const LAMBDA_FUNCTION_TIMEOUT = 10;
1414
const LAYER_DIR = './node_modules/@sentry/aws-serverless/';
15-
const DEFAULT_NODE_VERSION = '22';
1615
export const SAM_PORT = 3001;
1716

17+
/** Match SAM / Docker to this machine so Apple Silicon does not mix arm64 images with an x86_64 template default. */
18+
function samLambdaArchitecture(): 'arm64' | 'x86_64' {
19+
return arch === 'arm64' ? 'arm64' : 'x86_64';
20+
}
21+
1822
function resolvePackagesDir(): string {
1923
// When running via the e2e test runner, tests are copied to a temp directory
2024
// so we need the workspace root passed via env var
@@ -49,6 +53,7 @@ export class LocalLambdaStack extends Stack {
4953
properties: {
5054
ContentUri: path.join(LAYER_DIR, layerZipFile),
5155
CompatibleRuntimes: ['nodejs18.x', 'nodejs20.x', 'nodejs22.x'],
56+
CompatibleArchitectures: [samLambdaArchitecture()],
5257
},
5358
});
5459

@@ -122,12 +127,17 @@ export class LocalLambdaStack extends Stack {
122127
execFileSync('npm', ['install', '--install-links', '--prefix', lambdaPath], { stdio: 'inherit' });
123128
}
124129

130+
if (!process.env.NODE_VERSION) {
131+
throw new Error('[LocalLambdaStack] NODE_VERSION is not set');
132+
}
133+
125134
new CfnResource(this, functionName, {
126135
type: 'AWS::Serverless::Function',
127136
properties: {
137+
Architectures: [samLambdaArchitecture()],
128138
CodeUri: path.join(functionsDir, lambdaDir),
129139
Handler: 'index.handler',
130-
Runtime: `nodejs${process.env.NODE_VERSION ?? DEFAULT_NODE_VERSION}.x`,
140+
Runtime: `nodejs${process.env.NODE_VERSION}.x`,
131141
Timeout: LAMBDA_FUNCTION_TIMEOUT,
132142
Layers: addLayer ? [{ Ref: this.sentryLayer.logicalId }] : undefined,
133143
Environment: {

dev-packages/e2e-tests/test-applications/aws-serverless/start-event-proxy.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import { startEventProxyServer } from '@sentry-internal/test-utils';
22

33
startEventProxyServer({
44
port: 3031,
5-
proxyServerName: 'aws-serverless-lambda-sam',
5+
proxyServerName: 'aws-serverless',
66
});

dev-packages/e2e-tests/test-applications/aws-serverless/tests/lambda-fixtures.ts

Lines changed: 33 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,31 @@
11
import { test as base, expect } from '@playwright/test';
22
import { App } from 'aws-cdk-lib';
3-
import * as tmp from 'tmp';
43
import { LocalLambdaStack, SAM_PORT, getHostIp } from '../src/stack';
54
import { writeFileSync } from 'node:fs';
6-
import { spawn, execSync } from 'node:child_process';
5+
import { execSync, spawn } from 'node:child_process';
76
import { LambdaClient } from '@aws-sdk/client-lambda';
87

98
const DOCKER_NETWORK_NAME = 'lambda-test-network';
109
const SAM_TEMPLATE_FILE = 'sam.template.yml';
1110

11+
/** Major Node for SAM `--invoke-image`; default matches root `package.json` `volta.node` and `pull-sam-image.sh`. */
12+
const DEFAULT_NODE_VERSION_MAJOR = '20';
13+
14+
const SAM_INSTALL_ERROR =
15+
'You need to install sam, e.g. run `brew install aws-sam-cli`. Ensure `sam` is on your PATH when running tests.';
16+
1217
export { expect };
1318

1419
export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClient: LambdaClient }>({
1520
testEnvironment: [
1621
async ({}, use) => {
1722
console.log('[testEnvironment fixture] Setting up AWS Lambda test infrastructure');
1823

24+
const nodeVersionMajor = process.env.NODE_VERSION?.trim() || DEFAULT_NODE_VERSION_MAJOR;
25+
process.env.NODE_VERSION = nodeVersionMajor;
26+
27+
assertSamOnPath();
28+
1929
execSync('docker network prune -f');
2030
createDockerNetwork();
2131

@@ -26,11 +36,6 @@ export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClien
2636
const template = app.synth().getStackByName('LocalLambdaStack').template;
2737
writeFileSync(SAM_TEMPLATE_FILE, JSON.stringify(template, null, 2));
2838

29-
const debugLog = tmp.fileSync({ prefix: 'sentry_aws_lambda_tests_sam_debug', postfix: '.log' });
30-
if (!process.env.CI) {
31-
console.log(`[test_environment fixture] Writing SAM debug log to: ${debugLog.name}`);
32-
}
33-
3439
const args = [
3540
'local',
3641
'start-lambda',
@@ -42,16 +47,15 @@ export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClien
4247
'--docker-network',
4348
DOCKER_NETWORK_NAME,
4449
'--skip-pull-image',
50+
'--invoke-image',
51+
`public.ecr.aws/lambda/nodejs:${nodeVersionMajor}`,
4552
];
4653

47-
if (process.env.NODE_VERSION) {
48-
args.push('--invoke-image', `public.ecr.aws/lambda/nodejs:${process.env.NODE_VERSION}`);
49-
}
50-
5154
console.log(`[testEnvironment fixture] Running SAM with args: ${args.join(' ')}`);
5255

5356
const samProcess = spawn('sam', args, {
54-
stdio: process.env.CI ? 'inherit' : ['ignore', debugLog.fd, debugLog.fd],
57+
stdio: process.env.DEBUG ? 'inherit' : 'ignore',
58+
env: envForSamChild(),
5559
});
5660

5761
try {
@@ -91,6 +95,23 @@ export const test = base.extend<{ testEnvironment: LocalLambdaStack; lambdaClien
9195
},
9296
});
9397

98+
/** Avoid forcing linux/amd64 on Apple Silicon when `DOCKER_DEFAULT_PLATFORM` is set globally. */
99+
function envForSamChild(): NodeJS.ProcessEnv {
100+
const env = { ...process.env };
101+
if (process.arch === 'arm64') {
102+
delete env.DOCKER_DEFAULT_PLATFORM;
103+
}
104+
return env;
105+
}
106+
107+
function assertSamOnPath(): void {
108+
try {
109+
execSync('sam --version', { encoding: 'utf-8', stdio: 'pipe' });
110+
} catch {
111+
throw new Error(SAM_INSTALL_ERROR);
112+
}
113+
}
114+
94115
function createDockerNetwork() {
95116
try {
96117
execSync(`docker network create --driver bridge ${DOCKER_NETWORK_NAME}`);

dev-packages/e2e-tests/test-applications/aws-serverless/tests/layer.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { test, expect } from './lambda-fixtures';
44

55
test.describe('Lambda layer', () => {
66
test('tracing in CJS works', async ({ lambdaClient }) => {
7-
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
7+
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
88
return transactionEvent?.transaction === 'LayerTracingCjs';
99
});
1010

@@ -72,7 +72,7 @@ test.describe('Lambda layer', () => {
7272
});
7373

7474
test('tracing in ESM works', async ({ lambdaClient }) => {
75-
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
75+
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
7676
return transactionEvent?.transaction === 'LayerTracingEsm';
7777
});
7878

@@ -140,7 +140,7 @@ test.describe('Lambda layer', () => {
140140
});
141141

142142
test('capturing errors works', async ({ lambdaClient }) => {
143-
const errorEventPromise = waitForError('aws-serverless-lambda-sam', errorEvent => {
143+
const errorEventPromise = waitForError('aws-serverless', errorEvent => {
144144
return errorEvent?.exception?.values?.[0]?.value === 'test';
145145
});
146146

@@ -168,7 +168,7 @@ test.describe('Lambda layer', () => {
168168
});
169169

170170
test('capturing errors works in ESM', async ({ lambdaClient }) => {
171-
const errorEventPromise = waitForError('aws-serverless-lambda-sam', errorEvent => {
171+
const errorEventPromise = waitForError('aws-serverless', errorEvent => {
172172
return errorEvent?.exception?.values?.[0]?.value === 'test esm';
173173
});
174174

@@ -196,7 +196,7 @@ test.describe('Lambda layer', () => {
196196
});
197197

198198
test('streaming handlers work', async ({ lambdaClient }) => {
199-
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
199+
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
200200
return transactionEvent?.transaction === 'LayerStreaming';
201201
});
202202

dev-packages/e2e-tests/test-applications/aws-serverless/tests/npm.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { test, expect } from './lambda-fixtures';
44

55
test.describe('NPM package', () => {
66
test('tracing in CJS works', async ({ lambdaClient }) => {
7-
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
7+
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
88
return transactionEvent?.transaction === 'NpmTracingCjs';
99
});
1010

@@ -72,7 +72,7 @@ test.describe('NPM package', () => {
7272
});
7373

7474
test('tracing in ESM works', async ({ lambdaClient }) => {
75-
const transactionEventPromise = waitForTransaction('aws-serverless-lambda-sam', transactionEvent => {
75+
const transactionEventPromise = waitForTransaction('aws-serverless', transactionEvent => {
7676
return transactionEvent?.transaction === 'NpmTracingEsm';
7777
});
7878

0 commit comments

Comments
 (0)