test: cdk watch integ tests for cli and toolkit lib#1139
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #1139 +/- ##
=======================================
Coverage 87.75% 87.76%
=======================================
Files 72 72
Lines 10137 10137
Branches 1339 1338 -1
=======================================
+ Hits 8896 8897 +1
+ Misses 1216 1215 -1
Partials 25 25
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
| // Use separate output dir to avoid conflicts with lingering watch process | ||
| await fixture.cdkDestroy('test-1', { options: ['--output', 'cdk-destroy.out'] }); |
There was a problem hiding this comment.
No need to manually delete. The rest harness is doing this for us.
| // Use separate output dir to avoid conflicts with lingering watch process | |
| await fixture.cdkDestroy('test-1', { options: ['--output', 'cdk-destroy.out'] }); |
| }), | ||
| ); | ||
|
|
||
| integTest( |
There was a problem hiding this comment.
Only one test per file. Pls move to separate file.
| async function waitForOutput(getOutput: () => string, searchString: string, timeoutMs: number): Promise<void> { | ||
| const startTime = Date.now(); | ||
| return new Promise((resolve, reject) => { | ||
| const check = () => { | ||
| if (getOutput().includes(searchString)) return resolve(); | ||
| if (Date.now() - startTime > timeoutMs) { | ||
| return reject(new Error(`Timeout waiting for: "${searchString}"`)); | ||
| } | ||
| setTimeout(check, 1000); | ||
| }; | ||
| check(); | ||
| }); | ||
| } | ||
|
|
||
| async function waitForCondition(condition: () => boolean, timeoutMs: number, description: string): Promise<void> { | ||
| const startTime = Date.now(); | ||
| return new Promise((resolve, reject) => { | ||
| const check = () => { | ||
| if (condition()) return resolve(); | ||
| if (Date.now() - startTime > timeoutMs) { | ||
| return reject(new Error(`Timeout waiting for ${description}`)); | ||
| } | ||
| setTimeout(check, 1000); | ||
| }; | ||
| check(); | ||
| }); | ||
| } |
There was a problem hiding this comment.
Are there really no existing helpers for this?
There was a problem hiding this comment.
Nope, I couldn't find any that poll like this. I'm adding a negative test, though, so moving these to a shared file under the watch dir.
| }); | ||
|
|
||
| try { | ||
| await waitForOutput(() => output, "Triggering initial 'cdk deploy'", 120000); |
There was a problem hiding this comment.
how did you come up with these timeout durations?
There was a problem hiding this comment.
Not really based on anything in particular. The initial deployment takes longer than subsequent ones, so I was generous here.
| ); | ||
|
|
||
| integTest( | ||
| 'toolkit watch excludes node_modules and dotfiles by default', |
There was a problem hiding this comment.
why is this test not part of the cli integ tests also?
There was a problem hiding this comment.
Because it will add more time to the test, while offering limited utility IMO. Probably another minute or so. See my PR description where I addressed this discrepancy.
There was a problem hiding this comment.
i dont understand why this would be the case. i think you need to
a) touch node_modules / dotfiles
b) assert that we do not see Detected change to '<file>' -- this happens nearly instantly if the file is being watched, so i dont see why it would take another minute.
as for whether this offers utility, i think its worse if we accidentally don't exclude files from watch than if we accidentally don't include files.
| const cdkJsonPath = path.join(fixture.integTestDir, 'cdk.json'); | ||
| const cdkJson = JSON.parse(fs.readFileSync(cdkJsonPath, 'utf-8')); | ||
| cdkJson.watch = { | ||
| include: ['**/*.ts', '**/*.js'], |
There was a problem hiding this comment.
nit: including the .js files is a bad idea for some use cases (say you're also auto-compiling ts changes, now you're double counting). doesn't seem necessary in your test
There was a problem hiding this comment.
I concur, removed **/*.js.
| const cdkJson = JSON.parse(fs.readFileSync(cdkJsonPath, 'utf-8')); | ||
| cdkJson.watch = { | ||
| include: ['**/*.ts', '**/*.js'], | ||
| exclude: ['node_modules/**', 'cdk.out/**', '**/*.d.ts'], |
There was a problem hiding this comment.
this part isn't tested. in this test or a new one, we should be testing that changes to exclude files do not trigger deployment.
There was a problem hiding this comment.
I included this to model a default config, but you're right that we don't need it. Removed.
...g/tests/cli-integ-tests/watch/cdk-watch-detects-file-changes-with-glob-patterns.integtest.ts
Show resolved
Hide resolved
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
| await waitForOutput(() => output, "Triggering initial 'cdk deploy'", 120000); | ||
| fixture.log('✓ Watch started'); | ||
|
|
||
| await waitForOutput(() => output, 'deployment time', 300000); | ||
| fixture.log('✓ Initial deployment completed'); | ||
|
|
||
| // Modify the test file to trigger a watch event | ||
| fs.writeFileSync(testFile, 'export const modified = true;'); | ||
|
|
||
| await waitForOutput(() => output, 'Detected change to', 60000); | ||
| fixture.log('✓ Watch detected file change'); |
There was a problem hiding this comment.
Do we need individual timeout expectations for each of these? Feels like a top-level timeout would be enough to ensure it doesn't go overboard.
There was a problem hiding this comment.
Removed individual timeouts, just left the file-level one.
| fixture.log('✓ Initial deployment completed'); | ||
|
|
||
| // Modify the test file to trigger a watch event | ||
| fs.writeFileSync(testFile, 'export const modified = true;'); |
There was a problem hiding this comment.
You could also just touch the file: child_process.spawn('touch', [testFile])
There was a problem hiding this comment.
Changed to this approach.
...g/tests/cli-integ-tests/watch/cdk-watch-detects-file-changes-with-glob-patterns.integtest.ts
Outdated
Show resolved
Hide resolved
| }); | ||
|
|
||
| await waitForOutput(() => output, "Triggering initial 'cdk deploy'", 120000); | ||
| fixture.log('✓ Watch started'); |
There was a problem hiding this comment.
?
| fixture.log('✓ Watch started'); | |
| fixture.log('✓ Watch start detected'); |
|
|
||
| await waitForOutput(() => output, 'Detected change to', 60000); | ||
| fixture.log('✓ Watch detected file change'); | ||
| }), |
There was a problem hiding this comment.
Are we not checking that the second deployment runs?
| await waitForOutput(() => output, 'Detected change to', 60000); | ||
| fixture.log('✓ Watch detected file change'); | ||
| }), | ||
| ); |
There was a problem hiding this comment.
how is watch terminated here?
There was a problem hiding this comment.
It's not, I'll end the process.
| if (detectedExcluded) { | ||
| throw new Error('Watch should NOT have detected changes to excluded file'); | ||
| } |
There was a problem hiding this comment.
Why not testing primitives like expect etc?
There was a problem hiding this comment.
Will use expect instead, oops
| // Verify deployment count hasn't increased | ||
| const deploymentsAfter = (output.match(/deployment time/g) || []).length; | ||
| if (deploymentsAfter > deploymentsBefore) { | ||
| throw new Error(`Unexpected deployment triggered. Before: ${deploymentsBefore}, After: ${deploymentsAfter}`); |
| // Start cdk watch with detached process group for clean termination | ||
| const watchProcess = child_process.spawn('cdk', [ | ||
| 'watch', '--hotswap', '-v', fixture.fullStackName('test-1'), | ||
| ], { | ||
| cwd: fixture.integTestDir, | ||
| shell: true, | ||
| detached: true, | ||
| env: { ...process.env, ...fixture.cdkShellEnv() }, | ||
| }); | ||
|
|
||
| watchProcess.stdout?.on('data', (data) => { | ||
| output += data.toString(); | ||
| fixture.log(data.toString()); | ||
| }); | ||
| watchProcess.stderr?.on('data', (data) => { | ||
| output += data.toString(); | ||
| fixture.log(data.toString()); | ||
| }); |
There was a problem hiding this comment.
nit: this could also be a helper on the fixture, similar to cdkDeploy
| fixture.log('Created excluded file: should-be-ignored.excluded.ts'); | ||
|
|
||
| // Wait a reasonable time for any potential (unwanted) detection | ||
| await sleep(5000); |
| const detectedExcluded = output.includes('Detected change to') && | ||
| output.includes('excluded'); |
There was a problem hiding this comment.
| const detectedExcluded = output.includes('Detected change to') && | |
| output.includes('excluded'); | |
| const detectedExcluded = output.includes('Detected change to') && output.includes('excluded'); |
| const configMsg = configMessages.find(m => m.includes("'exclude' patterns")); | ||
|
|
||
| if (!configMsg) { | ||
| throw new Error('Did not receive exclude patterns configuration message'); |
|
|
||
| // Check that default excludes are present | ||
| if (!configMsg.includes('node_modules')) { | ||
| throw new Error('Default excludes should include node_modules'); |
| throw new Error('Default excludes should include node_modules'); | ||
| } | ||
| if (!configMsg.includes('.*')) { | ||
| throw new Error('Default excludes should include dotfiles (.*)'); |
| ); | ||
|
|
||
| if (!hasReadyOrObserving) { | ||
| throw new Error('Watcher did not emit ready/observing events'); |
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Tests #1134
Description of tests
cdk watchin CLIToolkit.watch()We used AI 🤖 to help with this contribution.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license