Skip to content

Commit 2e0f62e

Browse files
committed
Add new tests
1 parent bc8a655 commit 2e0f62e

File tree

11 files changed

+215
-18
lines changed

11 files changed

+215
-18
lines changed

packages/@aws-cdk/tmp-toolkit-helpers/src/api/rwlock.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export class RWLock {
3232
public async acquireWrite(): Promise<IWriteLock> {
3333
await this.assertNoOtherWriters();
3434

35-
const readers = await this.currentReaders();
35+
const readers = await this._currentReaders();
3636
if (readers.length > 0) {
3737
throw new ToolkitError(`Other CLIs (PID=${readers}) are currently reading from ${this.directory}. Invoke the CLI in sequence, or use '--output' to synth into different directories.`);
3838
}
@@ -99,16 +99,20 @@ export class RWLock {
9999
}
100100

101101
private async assertNoOtherWriters() {
102-
const writer = await this.currentWriter();
102+
const writer = await this._currentWriter();
103103
if (writer) {
104104
throw new ToolkitError(`Another CLI (PID=${writer}) is currently synthing to ${this.directory}. Invoke the CLI in sequence, or use '--output' to synth into different directories.`);
105105
}
106106
}
107107

108108
/**
109109
* Check the current writer (if any)
110+
*
111+
* Publicly accessible for testing purposes. Do not use.
112+
*
113+
* @internal
110114
*/
111-
private async currentWriter(): Promise<number | undefined> {
115+
public async _currentWriter(): Promise<number | undefined> {
112116
const contents = await readFileIfExists(this.writerFile);
113117
if (!contents) {
114118
return undefined;
@@ -126,8 +130,12 @@ export class RWLock {
126130

127131
/**
128132
* Check the current readers (if any)
133+
*
134+
* Publicly accessible for testing purposes. Do not use.
135+
*
136+
* @internal
129137
*/
130-
private async currentReaders(): Promise<number[]> {
138+
public async _currentReaders(): Promise<number[]> {
131139
const re = /^read\.([^.]+)\.[^.]+\.lock$/;
132140
const ret = new Array<number>();
133141

packages/@aws-cdk/toolkit-lib/test/_helpers/index.ts

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import * as fs from 'node:fs';
22
import * as os from 'node:os';
33
import * as path from 'node:path';
4-
import type { AssemblyDirectoryProps, Toolkit } from '../../lib';
4+
import type { AssemblyDirectoryProps, ICloudAssemblySource } from '../../lib';
55
import { ToolkitError } from '../../lib';
6+
import type { CloudAssemblySourceBuilder } from '../../lib/api/cloud-assembly/private';
67

78
export * from './test-cloud-assembly-source';
89
export * from './test-io-host';
@@ -11,7 +12,7 @@ function fixturePath(...parts: string[]): string {
1112
return path.normalize(path.join(__dirname, '..', '_fixtures', ...parts));
1213
}
1314

14-
export async function appFixture(toolkit: Toolkit, name: string, context?: { [key: string]: any }) {
15+
export async function appFixture(toolkit: CloudAssemblySourceBuilder, name: string, context?: { [key: string]: any }) {
1516
const appPath = fixturePath(name, 'app.js');
1617
if (!fs.existsSync(appPath)) {
1718
throw new ToolkitError(`App Fixture ${name} does not exist in ${appPath}`);
@@ -24,7 +25,7 @@ export async function appFixture(toolkit: Toolkit, name: string, context?: { [ke
2425
});
2526
}
2627

27-
export function builderFixture(toolkit: Toolkit, name: string, context?: { [key: string]: any }) {
28+
export function builderFixture(toolkit: CloudAssemblySourceBuilder, name: string, context?: { [key: string]: any }) {
2829
// eslint-disable-next-line @typescript-eslint/no-require-imports
2930
const builder = require(path.join(__dirname, '..', '_fixtures', name)).default;
3031
return toolkit.fromAssemblyBuilder(builder, {
@@ -33,7 +34,7 @@ export function builderFixture(toolkit: Toolkit, name: string, context?: { [key:
3334
});
3435
}
3536

36-
export function cdkOutFixture(toolkit: Toolkit, name: string, props: AssemblyDirectoryProps = {}) {
37+
export function cdkOutFixture(toolkit: CloudAssemblySourceBuilder, name: string, props: AssemblyDirectoryProps = {}) {
3738
const outdir = path.join(__dirname, '..', '_fixtures', name, 'cdk.out');
3839
if (!fs.existsSync(outdir)) {
3940
throw new ToolkitError(`Assembly Dir Fixture ${name} does not exist in ${outdir}`);
@@ -44,3 +45,44 @@ export function cdkOutFixture(toolkit: Toolkit, name: string, props: AssemblyDir
4445
function tmpOutdir(): string {
4546
return fs.mkdtempSync(path.join(fs.realpathSync(os.tmpdir()), 'cdk.out'));
4647
}
48+
49+
/**
50+
* A temporary directory that cleans when it goes out of scope
51+
*
52+
* Use with `using`. Could have been async but it's depending
53+
* on an already-sync API, so why not sync?
54+
*/
55+
export function autoCleanOutDir() {
56+
const dir = tmpOutdir();
57+
return {
58+
dir,
59+
[Symbol.dispose]: async () => {
60+
fs.rmSync(dir, { force: true, recursive: true });
61+
},
62+
};
63+
}
64+
65+
export async function disposableCloudAssemblySource(
66+
toolkit: CloudAssemblySourceBuilder,
67+
): Promise<[ICloudAssemblySource, ReturnType<typeof jest.fn>]> {
68+
// We just need any kind of assembly
69+
const fixtureSource = await cdkOutFixture(toolkit, 'stack-with-bucket');
70+
const cloudAssembly = await fixtureSource.produce();
71+
72+
const mockDispose = jest.fn();
73+
const assemblySource: ICloudAssemblySource = {
74+
produce() {
75+
return Promise.resolve({
76+
cloudAssembly: cloudAssembly.cloudAssembly,
77+
78+
_unlock: jest.fn(),
79+
80+
// Doesn't matter which one we use
81+
dispose: mockDispose,
82+
[Symbol.asyncDispose]: mockDispose,
83+
});
84+
},
85+
};
86+
87+
return [assemblySource, mockDispose];
88+
}

packages/@aws-cdk/toolkit-lib/test/actions/bootstrap.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type { BootstrapOptions } from '../../lib/actions/bootstrap';
1414
import { BootstrapEnvironments, BootstrapSource, BootstrapStackParameters } from '../../lib/actions/bootstrap';
1515
import { SdkProvider } from '../../lib/api/shared-private';
1616
import { Toolkit } from '../../lib/toolkit';
17-
import { TestIoHost, builderFixture } from '../_helpers';
17+
import { TestIoHost, builderFixture, disposableCloudAssemblySource } from '../_helpers';
1818
import {
1919
MockSdkProvider,
2020
MockSdk,
@@ -478,6 +478,24 @@ describe('bootstrap', () => {
478478
}));
479479
});
480480

481+
test('action disposes of assembly produced by source', async () => {
482+
// GIVEN
483+
const mockStack1 = createMockStack([
484+
{ OutputKey: 'BucketName', OutputValue: 'BUCKET_NAME_1' },
485+
{ OutputKey: 'BucketDomainName', OutputValue: 'BUCKET_ENDPOINT_1' },
486+
{ OutputKey: 'BootstrapVersion', OutputValue: '1' },
487+
]);
488+
setupMockCloudFormationClient(mockStack1);
489+
490+
const [assemblySource, mockDispose] = await disposableCloudAssemblySource(toolkit);
491+
492+
// WHEN
493+
await toolkit.bootstrap(BootstrapEnvironments.fromCloudAssemblySource(assemblySource), { });
494+
495+
// THEN
496+
expect(mockDispose).toHaveBeenCalled();
497+
});
498+
481499
describe('error handling', () => {
482500
test('returns correct BootstrapResult for successful bootstraps', async () => {
483501
// GIVEN

packages/@aws-cdk/toolkit-lib/test/actions/deploy.test.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import type { DeployStackOptions, DeployStackResult } from '../../lib/api/shared
33
import * as apis from '../../lib/api/shared-private';
44
import { RequireApproval } from '../../lib/api/shared-private';
55
import { Toolkit } from '../../lib/toolkit';
6-
import { builderFixture, cdkOutFixture, TestIoHost } from '../_helpers';
6+
import { builderFixture, cdkOutFixture, disposableCloudAssemblySource, TestIoHost } from '../_helpers';
77
import { MockSdk } from '../_helpers/mock-sdk';
88

99
let ioHost: TestIoHost;
@@ -362,6 +362,17 @@ describe('deploy', () => {
362362
],
363363
});
364364
});
365+
366+
test('action disposes of assembly produced by source', async () => {
367+
// GIVEN
368+
const [assemblySource, mockDispose] = await disposableCloudAssemblySource(toolkit);
369+
370+
// WHEN
371+
await toolkit.deploy(assemblySource);
372+
373+
// THEN
374+
expect(mockDispose).toHaveBeenCalled();
375+
});
365376
});
366377

367378
function successfulDeployment() {

packages/@aws-cdk/toolkit-lib/test/actions/destroy.test.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import * as chalk from 'chalk';
22
import { StackSelectionStrategy } from '../../lib/api/shared-public';
3+
import type { RollbackResult } from '../../lib/toolkit';
34
import { Toolkit } from '../../lib/toolkit';
4-
import { builderFixture, TestIoHost } from '../_helpers';
5+
import { builderFixture, disposableCloudAssemblySource, TestIoHost } from '../_helpers';
56

67
const ioHost = new TestIoHost();
78
const toolkit = new Toolkit({ ioHost });
8-
jest.spyOn(toolkit, 'rollback').mockResolvedValue();
9+
jest.spyOn(toolkit, 'rollback').mockResolvedValue({ stacks: [] } satisfies RollbackResult);
910

1011
let mockDestroyStack = jest.fn();
1112

@@ -86,6 +87,19 @@ describe('destroy', () => {
8687
message: expect.stringContaining('destroy failed'),
8788
}));
8889
});
90+
91+
test('action disposes of assembly produced by source', async () => {
92+
// GIVEN
93+
const [assemblySource, mockDispose] = await disposableCloudAssemblySource(toolkit);
94+
95+
// WHEN
96+
await toolkit.destroy(assemblySource, {
97+
stacks: { strategy: StackSelectionStrategy.ALL_STACKS },
98+
});
99+
100+
// THEN
101+
expect(mockDispose).toHaveBeenCalled();
102+
});
89103
});
90104

91105
function successfulDestroy() {

packages/@aws-cdk/toolkit-lib/test/actions/diff.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import * as apis from '../../lib/api/shared-private';
66
import { RequireApproval } from '../../lib/api/shared-private';
77
import { StackSelectionStrategy } from '../../lib/api/shared-public';
88
import { Toolkit } from '../../lib/toolkit';
9-
import { builderFixture, TestIoHost } from '../_helpers';
9+
import { builderFixture, disposableCloudAssemblySource, TestIoHost } from '../_helpers';
1010
import { MockSdk } from '../_helpers/mock-sdk';
1111

1212
let ioHost: TestIoHost;
@@ -262,4 +262,17 @@ describe('diff', () => {
262262
// }));
263263
// });
264264
});
265+
266+
test('action disposes of assembly produced by source', async () => {
267+
// GIVEN
268+
const [assemblySource, mockDispose] = await disposableCloudAssemblySource(toolkit);
269+
270+
// WHEN
271+
await toolkit.diff(assemblySource, {
272+
stacks: { strategy: StackSelectionStrategy.ALL_STACKS },
273+
});
274+
275+
// THEN
276+
expect(mockDispose).toHaveBeenCalled();
277+
});
265278
});

packages/@aws-cdk/toolkit-lib/test/actions/list.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ArtifactMetadataEntryType } from '@aws-cdk/cloud-assembly-schema';
22
import { StackSelectionStrategy } from '../../lib/api/shared-private';
33
import { Toolkit } from '../../lib/toolkit';
4-
import { TestIoHost } from '../_helpers';
4+
import { disposableCloudAssemblySource, TestIoHost } from '../_helpers';
55
import type { TestStackArtifact } from '../_helpers/test-cloud-assembly-source';
66
import { TestCloudAssemblySource } from '../_helpers/test-cloud-assembly-source';
77

@@ -458,6 +458,19 @@ describe('list', () => {
458458
await expect(() => toolkit.list(cx)).rejects.toThrow('Could not determine ordering');
459459
expect(ioHost.notifySpy).not.toHaveBeenCalled();
460460
});
461+
462+
test('action disposes of assembly produced by source', async () => {
463+
// GIVEN
464+
const [assemblySource, mockDispose] = await disposableCloudAssemblySource(toolkit);
465+
466+
// WHEN
467+
await toolkit.list(assemblySource, {
468+
stacks: { strategy: StackSelectionStrategy.ALL_STACKS },
469+
});
470+
471+
// THEN
472+
expect(mockDispose).toHaveBeenCalled();
473+
});
461474
});
462475

463476
const MOCK_STACK_A: TestStackArtifact = {

packages/@aws-cdk/toolkit-lib/test/actions/rollback.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { StackSelectionStrategy } from '../../lib/api/shared-private';
22
import { Toolkit } from '../../lib/toolkit';
3-
import { builderFixture, TestIoHost } from '../_helpers';
3+
import { builderFixture, disposableCloudAssemblySource, TestIoHost } from '../_helpers';
44

55
const ioHost = new TestIoHost();
66
const toolkit = new Toolkit({ ioHost });
@@ -82,6 +82,19 @@ describe('rollback', () => {
8282
stacks: { strategy: StackSelectionStrategy.ALL_STACKS },
8383
})).rejects.toThrow(/Rollback failed/);
8484
});
85+
86+
test('action disposes of assembly produced by source', async () => {
87+
// GIVEN
88+
const [assemblySource, mockDispose] = await disposableCloudAssemblySource(toolkit);
89+
90+
// WHEN
91+
await toolkit.rollback(assemblySource, {
92+
stacks: { strategy: StackSelectionStrategy.ALL_STACKS },
93+
});
94+
95+
// THEN
96+
expect(mockDispose).toHaveBeenCalled();
97+
});
8598
});
8699

87100
function successfulRollback() {

packages/@aws-cdk/toolkit-lib/test/actions/synth.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Toolkit } from '../../lib/toolkit';
2-
import { appFixture, builderFixture, TestIoHost } from '../_helpers';
2+
import { appFixture, builderFixture, disposableCloudAssemblySource, TestIoHost } from '../_helpers';
33

44
// these tests often run a bit longer than the default
55
jest.setTimeout(10_000);
@@ -77,4 +77,18 @@ describe('synth', () => {
7777
}),
7878
}));
7979
});
80+
81+
test('output of synth can be used in other toolkit actions, but source is only disposed at the end', async () => {
82+
// GIVEN
83+
const [assemblySource, mockDispose] = await disposableCloudAssemblySource(toolkit);
84+
const synthResult = await toolkit.synth(assemblySource);
85+
86+
// WHEN
87+
await toolkit.list(synthResult);
88+
expect(mockDispose).not.toHaveBeenCalled();
89+
90+
// WHEN
91+
await synthResult.dispose();
92+
expect(mockDispose).toHaveBeenCalled();
93+
});
8094
});

packages/@aws-cdk/toolkit-lib/test/actions/watch.test.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ jest.mock('chokidar', () => ({
4747
import * as path from 'node:path';
4848
import { HotswapMode } from '../../lib/actions/deploy';
4949
import { Toolkit } from '../../lib/toolkit';
50-
import { builderFixture, TestIoHost } from '../_helpers';
50+
import { builderFixture, disposableCloudAssemblySource, TestIoHost } from '../_helpers';
5151

5252
const ioHost = new TestIoHost();
5353
const toolkit = new Toolkit({ ioHost });
@@ -238,6 +238,21 @@ describe('watch', () => {
238238
extraUserAgent: 'cdk-watch/hotswap-on',
239239
}));
240240
});
241+
242+
test('action disposes of assembly produced by source', async () => {
243+
// GIVEN
244+
const [assemblySource, mockDispose] = await disposableCloudAssemblySource(toolkit);
245+
246+
// WHEN
247+
const watcher = await toolkit.watch(assemblySource, {
248+
include: [],
249+
hotswap: undefined, // force the default
250+
});
251+
await watcher.dispose();
252+
253+
// THEN
254+
expect(mockDispose).toHaveBeenCalled();
255+
});
241256
});
242257

243258
// @todo unit test watch with file events

0 commit comments

Comments
 (0)