Skip to content

Commit d7678af

Browse files
committed
fixup! feat(ng-dev): create workflow performance testing tooling
1 parent 8aeb5a3 commit d7678af

File tree

6 files changed

+97
-36
lines changed

6 files changed

+97
-36
lines changed

.ng-dev/dx-perf-workflows.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
workflows:
2+
- name: Rerun a test
3+
prepare:
4+
- bazel clean;
5+
- bazel build //ng-dev/utils/test;
6+
workflow:
7+
- bazel test //ng-dev/utils/test;
8+
- git apply .ng-dev/perf-tests/test-rerun.diff;
9+
- bazel test //ng-dev/utils/test;
10+
cleanup:
11+
- git apply -R .ng-dev/perf-tests/test-rerun.diff;
12+
13+
- name: Build Everything
14+
prepare:
15+
- bazel clean;
16+
workflow:
17+
- bazel build //...;

.ng-dev/workflows.yml

Lines changed: 0 additions & 17 deletions
This file was deleted.

ng-dev/perf/workflow/cli.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ interface WorkflowsParams {
2121
function builder(yargs: Argv) {
2222
return yargs
2323
.option('config-file' as 'configFile', {
24-
default: '.ng-dev/workflows.yml',
24+
default: '.ng-dev/dx-perf-workflows.yml',
2525
type: 'string',
2626
description: 'The path to the workflow definitions in a yml file',
2727
})
2828
.option('json', {
2929
default: false,
3030
type: 'boolean',
31-
description: 'Whether to ouput the results as a json object',
31+
description: 'Whether to output the results as a json object',
3232
});
3333
}
3434

@@ -46,7 +46,7 @@ async function handler({configFile, json}: WorkflowsParams) {
4646
}
4747
}
4848

49-
/** yargs command module for checking out a PR */
49+
/** yargs command module for checking out a PR. */
5050
export const WorkflowsModule: CommandModule<{}, WorkflowsParams> = {
5151
handler,
5252
builder,

ng-dev/perf/workflow/loader.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import {parse} from 'yaml';
33

44
export interface Workflow {
55
name: string;
6-
workflow: string;
7-
prepare?: string;
8-
cleanup?: string;
6+
workflow: string[];
7+
prepare?: string[];
8+
cleanup?: string[];
99
}
1010

1111
export async function loadWorkflows(src: string) {

ng-dev/perf/workflow/workflow.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,11 @@ export async function measureWorkflow({name, workflow, prepare, cleanup}: Workfl
4444
* Run a set of commands provided as a multiline text block. Commands are assumed to always be
4545
* provided on a single line.
4646
*/
47-
async function runCommands(cmds?: string) {
48-
cmds = cmds?.trim();
49-
if (!cmds) {
47+
async function runCommands(commands?: string[]) {
48+
if (!commands || commands.length === 0) {
5049
return;
5150
}
52-
let commands = cmds
53-
.split('\n')
54-
.filter((_) => !!_)
55-
.map((cmdStr: string) => cmdStr.trim().split(' '));
56-
57-
for (let [cmd, ...args] of commands) {
58-
await ChildProcess.spawn(cmd, args, {mode: 'silent'});
51+
for (let cmd of commands) {
52+
await ChildProcess.exec(cmd, {mode: 'silent'});
5953
}
6054
}

ng-dev/utils/child-process.ts

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
SpawnOptions as _SpawnOptions,
1313
spawnSync as _spawnSync,
1414
SpawnSyncOptions as _SpawnSyncOptions,
15+
ExecOptions as _ExecOptions,
16+
exec as _exec,
1517
} from 'child_process';
1618
import {Log} from './logging.js';
1719

@@ -32,6 +34,14 @@ export interface SpawnOptions extends Omit<_SpawnOptions, 'shell' | 'stdio'> {
3234
input?: string;
3335
}
3436

37+
/** Interface describing the options for exec-ing a process. */
38+
export interface ExecOptions extends Omit<_ExecOptions, 'shell' | 'stdio'> {
39+
/** Console output mode. Defaults to "enabled". */
40+
mode?: 'enabled' | 'silent' | 'on-error';
41+
/** Whether to prevent exit codes being treated as failures. */
42+
suppressErrorOnFailingExitCode?: boolean;
43+
}
44+
3545
/** Interface describing the options for spawning an interactive process. */
3646
export type SpawnInteractiveCommandOptions = Omit<_SpawnOptions, 'shell' | 'stdio'>;
3747

@@ -81,7 +91,7 @@ export abstract class ChildProcess {
8191
return new Promise((resolve, reject) => {
8292
const commandText = `${command} ${args.join(' ')}`;
8393
const outputMode = options.mode;
84-
const env = getEnvironmentForNonInteractiveSpawn(options.env);
94+
const env = getEnvironmentForNonInteractiveCommand(options.env);
8595

8696
Log.debug(`Executing command: ${commandText}`);
8797

@@ -148,7 +158,7 @@ export abstract class ChildProcess {
148158
*/
149159
static spawnSync(command: string, args: string[], options: SpawnSyncOptions = {}): SpawnResult {
150160
const commandText = `${command} ${args.join(' ')}`;
151-
const env = getEnvironmentForNonInteractiveSpawn(options.env);
161+
const env = getEnvironmentForNonInteractiveCommand(options.env);
152162

153163
Log.debug(`Executing command: ${commandText}`);
154164

@@ -168,6 +178,63 @@ export abstract class ChildProcess {
168178

169179
throw new Error(stderr);
170180
}
181+
182+
static exec(command: string, options: ExecOptions = {}) {
183+
return new Promise((resolve, reject) => {
184+
const outputMode = options.mode;
185+
const env = getEnvironmentForNonInteractiveCommand(options.env);
186+
187+
Log.debug(`Executing command: ${command}`);
188+
189+
const childProcess = _exec(command, {...options, env});
190+
let logOutput = '';
191+
let stdout = '';
192+
let stderr = '';
193+
194+
// Capture the stdout separately so that it can be passed as resolve value.
195+
// This is useful if commands return parsable stdout.
196+
childProcess.stderr?.on('data', (message) => {
197+
stderr += message;
198+
logOutput += message;
199+
// If console output is enabled, print the message directly to the stderr. Note that
200+
// we intentionally print all output to stderr as stdout should not be polluted.
201+
if (outputMode === undefined || outputMode === 'enabled') {
202+
process.stderr.write(message);
203+
}
204+
});
205+
206+
childProcess.stdout?.on('data', (message) => {
207+
stdout += message;
208+
logOutput += message;
209+
// If console output is enabled, print the message directly to the stderr. Note that
210+
// we intentionally print all output to stderr as stdout should not be polluted.
211+
if (outputMode === undefined || outputMode === 'enabled') {
212+
process.stderr.write(message);
213+
}
214+
});
215+
216+
// The `close` event is used because the process is guaranteed to have completed writing to
217+
// stdout and stderr, using the `exit` event can cause inconsistent information in stdout and
218+
// stderr due to a race condition around exiting.
219+
childProcess.on('close', (exitCode, signal) => {
220+
const exitDescription =
221+
exitCode !== null ? `exit code "${exitCode}"` : `signal "${signal}"`;
222+
const printFn = outputMode === 'on-error' ? Log.error : Log.debug;
223+
const status = statusFromExitCodeAndSignal(exitCode, signal);
224+
225+
printFn(`Command "${command}" completed with ${exitDescription}.`);
226+
printFn(`Process output: \n${logOutput}`);
227+
228+
// On success, resolve the promise. Otherwise reject with the captured stderr
229+
// and stdout log output if the output mode was set to `silent`.
230+
if (status === 0 || options.suppressErrorOnFailingExitCode) {
231+
resolve({stdout, stderr, status});
232+
} else {
233+
reject(outputMode === 'silent' ? logOutput : undefined);
234+
}
235+
});
236+
});
237+
}
171238
}
172239
/**
173240
* Convert the provided exitCode and signal to a single status code.
@@ -188,7 +255,7 @@ function statusFromExitCodeAndSignal(exitCode: number | null, signal: NodeJS.Sig
188255
* Currently we enable `FORCE_COLOR` since non-interactive spawn's with
189256
* non-inherited `stdio` will not have colors enabled due to a missing TTY.
190257
*/
191-
function getEnvironmentForNonInteractiveSpawn(
258+
function getEnvironmentForNonInteractiveCommand(
192259
userProvidedEnv?: NodeJS.ProcessEnv,
193260
): NodeJS.ProcessEnv {
194261
// Pass through the color level from the TTY/process performing the `spawn` call.

0 commit comments

Comments
 (0)