Skip to content

Stop bundling in tests #1844

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 4, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/mighty-jokes-argue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'skuba': minor
---

lint: Add a **skuba** patch to automatically stop CDK snapshot tests from calling esbuild bundling.

Calling esbuild bundling during unit tests can be slow. This change looks for `new App()` use in `infra` folder test files, and if found, replacing with `new App({ context: { 'aws:cdk:bundling-stacks': [] } })`. This context signals AWS CDK to skip bundling for the test stack.
5 changes: 5 additions & 0 deletions .changeset/new-cars-fix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'skuba': patch
---

template/lambda-sqs-worker-cdk: Update test to skip esbuild bundling by using the AWS CDK context key `aws:cdk:bundling-stacks`
8 changes: 8 additions & 0 deletions src/cli/__snapshots__/format.int.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Patch applied: Upgrade Node.js to version 22 upgrade-skuba

Patch skipped: Remove yarn --ignore-optional flags in Dockerfiles - no Dockerfiles found upgrade-skuba

Patch skipped: Stop bundling inside CDK unit tests - no CDK test files found upgrade-skuba

skuba update complete. upgrade-skuba

Refreshed .gitignore. refresh-config-files
Expand Down Expand Up @@ -131,6 +133,8 @@ Patch applied: Upgrade Node.js to version 22 upgrade-skuba

Patch skipped: Remove yarn --ignore-optional flags in Dockerfiles - no Dockerfiles found upgrade-skuba

Patch skipped: Stop bundling inside CDK unit tests - no CDK test files found upgrade-skuba

skuba update complete. upgrade-skuba

Refreshed .gitignore. refresh-config-files
Expand Down Expand Up @@ -219,6 +223,8 @@ Patch applied: Upgrade Node.js to version 22 upgrade-skuba

Patch skipped: Remove yarn --ignore-optional flags in Dockerfiles - no Dockerfiles found upgrade-skuba

Patch skipped: Stop bundling inside CDK unit tests - no CDK test files found upgrade-skuba

skuba update complete. upgrade-skuba

Refreshed .gitignore. refresh-config-files
Expand Down Expand Up @@ -278,6 +284,8 @@ Patch applied: Upgrade Node.js to version 22 upgrade-skuba

Patch skipped: Remove yarn --ignore-optional flags in Dockerfiles - no Dockerfiles found upgrade-skuba

Patch skipped: Stop bundling inside CDK unit tests - no CDK test files found upgrade-skuba

skuba update complete. upgrade-skuba

Refreshed .gitignore. refresh-config-files
Expand Down
10 changes: 10 additions & 0 deletions src/cli/lint/internalLints/upgrade/patches/10.1.0/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Patches } from '../..';

import { tryStopBundlingInCDKTests } from './stopBundlingInCDKTests';

export const patches: Patches = [
{
apply: tryStopBundlingInCDKTests,
description: 'Stop bundling inside CDK unit tests',
},
];
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import memfs, { vol } from 'memfs';

import type { PatchConfig } from '../..';
import { configForPackageManager } from '../../../../../../utils/packageManager';

import {
hasAppImportRegex,
tryStopBundlingInCDKTests,
} from './stopBundlingInCDKTests';

const volToJson = () => vol.toJSON(process.cwd(), undefined, true);

jest.mock('fs-extra', () => memfs);
jest.mock('fast-glob', () => ({
glob: (pat: any, opts: any) =>
jest.requireActual('fast-glob').glob(pat, { ...opts, fs: memfs }),
}));

beforeEach(() => vol.reset());

describe('stopBundlingInCDKTests', () => {
const baseArgs = {
manifest: {} as PatchConfig['manifest'],
packageManager: configForPackageManager('yarn'),
};

afterEach(() => jest.resetAllMocks());

describe.each(['lint', 'format'] as const)('%s', (mode) => {
it('should skip if no CDK test files are found', async () => {
await expect(
tryStopBundlingInCDKTests({
...baseArgs,
mode,
}),
).resolves.toEqual({
result: 'skip',
reason: 'no CDK test files found',
});

expect(volToJson()).toEqual({});
});

it('should skip if no App import is found from aws-cdk-lib', async () => {
const input = `const myApp = new App();`;

vol.fromJSON({
'apps/my-worker/infra/myWorkerStack.test.ts': input,
});

await expect(
tryStopBundlingInCDKTests({
...baseArgs,
mode,
}),
).resolves.toEqual({
result: 'skip',
reason: 'no CDK test files need patching',
});

expect(volToJson()).toEqual({
'apps/my-worker/infra/myWorkerStack.test.ts': input,
});
});

it('should patch a detected bare App() constructor', async () => {
const input = `import { App } from "aws-cdk-lib";\nconst app = new App();\n\nother stuff\n\nconst secondApp = new App();`;

const inputVolume = {
'infra/myWorkerStack.test.ts': input,
};

vol.fromJSON(inputVolume);

await expect(
tryStopBundlingInCDKTests({
...baseArgs,
mode,
}),
).resolves.toEqual({
result: 'apply',
});

expect(volToJson()).toEqual(
mode === 'lint'
? inputVolume
: {
'infra/myWorkerStack.test.ts': `import { App } from "aws-cdk-lib";\nconst app = new App({ context: { 'aws:cdk:bundling-stacks': [] } });\n\nother stuff\n\nconst secondApp = new App({ context: { 'aws:cdk:bundling-stacks': [] } });`,
},
);
});
});
});

describe('hasAppImportRegex', () => {
it.each([
['Bare', 'import { App } from "aws-cdk-lib";'],
['Other after', 'import { App, aws_sns } from "aws-cdk-lib";'],
[
'Surrounded',
"import { aws_sns, App, aws_secretsmanager } from 'aws-cdk-lib';",
],
['Other before', 'import { aws_sns, App } from "aws-cdk-lib";'],
['newline', 'import { App\n} from "aws-cdk-lib";'],
[
'newline surrounded',
'import {\n aws_secretsmanager\n, App\n, aws_sns\n} from "aws-cdk-lib";',
],
])('should match %s', (_, input: string) => {
expect(input).toMatch(hasAppImportRegex);
});

it.each([
['No App', 'import { aws_sns } from "aws-cdk-lib";'],
['Other import', 'import { aws_sns } from "aws-cdk-lib";'],
['Other lib', 'import { App } from "aws-some-other-lib";'],
[
'App on one line and then aws-cdk-lib on another',
'import { App } from "some-lib";\nimport { aws_sns } from "aws-cdk-lib";',
],
])('should not match %s', (_, input: string) => {
expect(input).not.toMatch(hasAppImportRegex);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { inspect } from 'util';

import { glob } from 'fast-glob';
import { promises as fs } from 'fs-extra';

import type { PatchFunction, PatchReturnType } from '../..';
import { log } from '../../../../../../utils/logging';

const fetchFiles = async (files: string[]) =>
Promise.all(
files.map(async (file) => {
const contents = await fs.readFile(file, 'utf8');

return {
file,
contents,
};
}),
);

export const hasAppImportRegex =
/import\s*\{\s*(?:[^}]*,\s*)?App\s*(?:,\s*[^}]*)?\s*\}\s*from\s*['"]aws-cdk-lib['"]/gm;

const addBundlingContext = (contents: string) => {
if (contents.includes('new App()') && hasAppImportRegex.test(contents)) {
return contents.replaceAll(
'new App()',
"new App({ context: { 'aws:cdk:bundling-stacks': [] } })",
);
}

return contents;
};

const stopBundlingInCDKTests: PatchFunction = async ({
mode,
}): Promise<PatchReturnType> => {
const infraTestFileNames = await glob(['**/infra/**/*.test.ts']);

if (!infraTestFileNames.length) {
return {
result: 'skip',
reason: 'no CDK test files found',
};
}

const infraTestFiles = await fetchFiles(infraTestFileNames);

const mapped = infraTestFiles.map(({ file, contents }) => ({
file,
before: contents,
after: addBundlingContext(contents),
}));

if (!mapped.some(({ before, after }) => before !== after)) {
return {
result: 'skip',
reason: 'no CDK test files need patching',
};
}

if (mode === 'lint') {
return {
result: 'apply',
};
}

await Promise.all(
mapped.map(async ({ file, after }) => {
await fs.writeFile(file, after);
}),
);

return { result: 'apply' };
};

export const tryStopBundlingInCDKTests: PatchFunction = async (config) => {
try {
return await stopBundlingInCDKTests(config);
} catch (err) {
log.warn('Failed to remove bundling in CDK tests');
log.subtle(inspect(err));
return { result: 'skip', reason: 'due to an error' };
}
};
2 changes: 1 addition & 1 deletion template/lambda-sqs-worker-cdk/infra/appStack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ it.each(['dev', 'prod'])(
(scope, id) => new aws_secretsmanager.Secret(scope, id),
);

const app = new App();
const app = new App({ context: { 'aws:cdk:bundling-stacks': [] } });

const stack = new AppStack(app, 'appStack');

Expand Down