Skip to content

Commit 9bf7e24

Browse files
authored
chore(cli): integ tests don't buffer shell output (#31903)
What should happen is that the output of shell commands gets buffered, and only printed if the test running them fails. In practice, we see the output being printed directly. The buffering code is a bit confusing, and we don't exactly understand why it's not working. Try to simplify the code a bit and remove mutable variable manipulation, to address the above. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 84d71a4 commit 9bf7e24

File tree

8 files changed

+42
-37
lines changed

8 files changed

+42
-37
lines changed

packages/@aws-cdk-testing/cli-integ/lib/shell.ts

+21-21
Original file line numberDiff line numberDiff line change
@@ -14,21 +14,19 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom
1414
throw new Error('Use either env or modEnv but not both');
1515
}
1616

17+
const outputs = new Set(options.outputs);
18+
const writeToOutputs = (x: string) => {
19+
for (const outputStream of outputs) {
20+
outputStream.write(x);
21+
}
22+
};
23+
1724
// Always output the command
18-
(options.output ?? process.stdout).write(`💻 ${command.join(' ')}\n`);
19-
20-
let output: NodeJS.WritableStream | undefined = options.output ?? process.stdout;
21-
switch (options.show ?? 'always') {
22-
case 'always':
23-
break;
24-
case 'never':
25-
case 'error':
26-
output = undefined;
27-
break;
28-
}
25+
writeToOutputs(`💻 ${command.join(' ')}\n`);
26+
const show = options.show ?? 'always';
2927

3028
if (process.env.VERBOSE) {
31-
output = process.stdout;
29+
outputs.add(process.stdout);
3230
}
3331

3432
const env = options.env ?? (options.modEnv ? { ...process.env, ...options.modEnv } : process.env);
@@ -46,12 +44,16 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom
4644
const stderr = new Array<Buffer>();
4745

4846
child.stdout!.on('data', chunk => {
49-
output?.write(chunk);
47+
if (show === 'always') {
48+
writeToOutputs(chunk);
49+
}
5050
stdout.push(chunk);
5151
});
5252

5353
child.stderr!.on('data', chunk => {
54-
output?.write(chunk);
54+
if (show === 'always') {
55+
writeToOutputs(chunk);
56+
}
5557
if (options.captureStderr ?? true) {
5658
stderr.push(chunk);
5759
}
@@ -66,8 +68,8 @@ export async function shell(command: string[], options: ShellOptions = {}): Prom
6668
if (code === 0 || options.allowErrExit) {
6769
resolve(out);
6870
} else {
69-
if (options.show === 'error') {
70-
(options.output ?? process.stdout).write(out + '\n');
71+
if (show === 'error') {
72+
writeToOutputs(`${out}\n`);
7173
}
7274
reject(new Error(`'${command.join(' ')}' exited with error code ${code}.`));
7375
}
@@ -97,10 +99,8 @@ export interface ShellOptions extends child_process.SpawnOptions {
9799

98100
/**
99101
* Pass output here
100-
*
101-
* @default stdout unless quiet=true
102102
*/
103-
readonly output?: NodeJS.WritableStream;
103+
readonly outputs?: NodeJS.WritableStream[];
104104

105105
/**
106106
* Only return stderr. For example, this is used to validate
@@ -127,9 +127,9 @@ export class ShellHelper {
127127
private readonly _cwd: string,
128128
private readonly _output: NodeJS.WritableStream) { }
129129

130-
public async shell(command: string[], options: Omit<ShellOptions, 'cwd' | 'output'> = {}): Promise<string> {
130+
public async shell(command: string[], options: Omit<ShellOptions, 'cwd' | 'outputs'> = {}): Promise<string> {
131131
return shell(command, {
132-
output: this._output,
132+
outputs: [this._output],
133133
cwd: this._cwd,
134134
...options,
135135
});

packages/@aws-cdk-testing/cli-integ/lib/staging/maven.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export async function uploadJavaPackages(packages: string[], login: LoginInforma
4242
`-Dfile=${pkg.replace(/.pom$/, '.jar')}`,
4343
...await pathExists(sourcesFile) ? [`-Dsources=${sourcesFile}`] : [],
4444
...await pathExists(javadocFile) ? [`-Djavadoc=${javadocFile}`] : []], {
45-
output,
45+
outputs: [output],
4646
modEnv: {
4747
// Do not try to JIT the Maven binary
4848
MAVEN_OPTS: `${NO_JIT} ${process.env.MAVEN_OPTS ?? ''}`.trim(),

packages/@aws-cdk-testing/cli-integ/lib/staging/npm.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export async function uploadNpmPackages(packages: string[], login: LoginInformat
3131
await shell(['node', require.resolve('npm'), 'publish', path.resolve(pkg)], {
3232
modEnv: npmEnv(usageDir, login),
3333
show: 'error',
34-
output,
34+
outputs: [output],
3535
});
3636

3737
console.log(`✅ ${pkg}`);

packages/@aws-cdk-testing/cli-integ/lib/staging/nuget.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ export async function uploadDotnetPackages(packages: string[], usageDir: UsageDi
2525
'--disable-buffering',
2626
'--timeout', '600',
2727
'--skip-duplicate'], {
28-
output,
28+
outputs: [output],
2929
});
3030

3131
console.log(`✅ ${pkg}`);

packages/@aws-cdk-testing/cli-integ/lib/staging/pypi.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export async function uploadPythonPackages(packages: string[], login: LoginInfor
3232
TWINE_REPOSITORY_URL: login.pypiEndpoint,
3333
},
3434
show: 'error',
35-
output,
35+
outputs: [output],
3636
});
3737

3838
console.log(`✅ ${pkg}`);

packages/@aws-cdk-testing/cli-integ/lib/with-cdk-app.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -242,9 +242,9 @@ export interface CdkCliOptions extends ShellOptions {
242242
* Prepare a target dir byreplicating a source directory
243243
*/
244244
export async function cloneDirectory(source: string, target: string, output?: NodeJS.WritableStream) {
245-
await shell(['rm', '-rf', target], { output });
246-
await shell(['mkdir', '-p', target], { output });
247-
await shell(['cp', '-R', source + '/*', target], { output });
245+
await shell(['rm', '-rf', target], { outputs: output ? [output] : [] });
246+
await shell(['mkdir', '-p', target], { outputs: output ? [output] : [] });
247+
await shell(['cp', '-R', source + '/*', target], { outputs: output ? [output] : [] });
248248
}
249249

250250
interface CommonCdkBootstrapCommandOptions {

packages/@aws-cdk-testing/cli-integ/lib/with-sam.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ export function withSamIntegrationFixture(block: (context: SamIntegrationTestFix
119119
export class SamIntegrationTestFixture extends TestFixture {
120120
public async samShell(command: string[], filter?: string, action?: () => any, options: Omit<ShellOptions, 'cwd' | 'output'> = {}): Promise<ActionOutput> {
121121
return shellWithAction(command, filter, action, {
122-
output: this.output,
122+
outputs: [this.output],
123123
cwd: path.join(this.integTestDir, 'cdk.out').toString(),
124124
...options,
125125
});
@@ -182,7 +182,12 @@ export async function shellWithAction(
182182
throw new Error('Use either env or modEnv but not both');
183183
}
184184

185-
options.output?.write(`💻 ${command.join(' ')}\n`);
185+
const writeToOutputs = (x: string) => {
186+
for (const output of options.outputs ?? []) {
187+
output.write(x);
188+
}
189+
};
190+
writeToOutputs(`💻 ${command.join(' ')}\n`);
186191

187192
const env = options.env ?? (options.modEnv ? { ...process.env, ...options.modEnv } : undefined);
188193

@@ -206,17 +211,17 @@ export async function shellWithAction(
206211
out.push(Buffer.from(chunk));
207212
if (!actionExecuted && typeof filter === 'string' && Buffer.concat(out).toString('utf-8').includes(filter) && typeof action === 'function') {
208213
actionExecuted = true;
209-
options.output?.write('before executing action');
214+
writeToOutputs('before executing action');
210215
action().then((output) => {
211-
options.output?.write(`action output is ${output}`);
216+
writeToOutputs(`action output is ${output}`);
212217
actionOutput = output;
213218
actionSucceeded = true;
214219
}).catch((error) => {
215-
options.output?.write(`action error is ${error}`);
220+
writeToOutputs(`action error is ${error}`);
216221
actionSucceeded = false;
217222
actionOutput = error;
218223
}).finally(() => {
219-
options.output?.write('terminate sam sub process');
224+
writeToOutputs('terminate sam sub process');
220225
killSubProcess(child, command.join(' '));
221226
});
222227
}
@@ -235,13 +240,13 @@ export async function shellWithAction(
235240
}
236241

237242
child.stdout!.on('data', chunk => {
238-
options.output?.write(chunk);
243+
writeToOutputs(chunk);
239244
stdout.push(chunk);
240245
executeAction(chunk);
241246
});
242247

243248
child.stderr!.on('data', chunk => {
244-
options.output?.write(chunk);
249+
writeToOutputs(chunk);
245250
if (options.captureStderr ?? true) {
246251
stderr.push(chunk);
247252
}

packages/@aws-cdk-testing/cli-integ/tests/cli-integ-tests/cli.integtest.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1662,7 +1662,7 @@ integTest(
16621662
const targetName = template.replace(/.js$/, '');
16631663
await shell([process.execPath, template, '>', targetName], {
16641664
cwd: cxAsmDir,
1665-
output: fixture.output,
1665+
outputs: [fixture.output],
16661666
modEnv: {
16671667
TEST_ACCOUNT: await fixture.aws.account(),
16681668
TEST_REGION: fixture.aws.region,

0 commit comments

Comments
 (0)