Skip to content

Commit db24afe

Browse files
committed
feat(ng-dev): create workflow performance testing tooling (#2418)
Creates a piece of tooling within ng-dev that allows for a set of commands to be run that emulate an expected workflow within a repository. This is then measured for the time it takes to run these commands so they can be checked or tracked over time. PR Close #2418
1 parent 7898454 commit db24afe

File tree

17 files changed

+478
-22
lines changed

17 files changed

+478
-22
lines changed

.github/local-actions/branch-manager/main.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56692,7 +56692,7 @@ var supportsColor2 = {
5669256692
var supports_color_default2 = supportsColor2;
5669356693

5669456694
//
56695-
import { spawn as _spawn, spawnSync as _spawnSync } from "child_process";
56695+
import { spawn as _spawn, spawnSync as _spawnSync, exec as _exec } from "child_process";
5669656696
var ChildProcess = class {
5669756697
static spawnInteractive(command, args, options = {}) {
5669856698
return new Promise((resolve, reject) => {
@@ -56706,7 +56706,7 @@ var ChildProcess = class {
5670656706
return new Promise((resolve, reject) => {
5670756707
const commandText = `${command} ${args.join(" ")}`;
5670856708
const outputMode = options.mode;
56709-
const env3 = getEnvironmentForNonInteractiveSpawn(options.env);
56709+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
5671056710
Log.debug(`Executing command: ${commandText}`);
5671156711
const childProcess = _spawn(command, args, { ...options, env: env3, shell: true, stdio: "pipe" });
5671256712
let logOutput = "";
@@ -56747,7 +56747,7 @@ ${logOutput}`);
5674756747
}
5674856748
static spawnSync(command, args, options = {}) {
5674956749
const commandText = `${command} ${args.join(" ")}`;
56750-
const env3 = getEnvironmentForNonInteractiveSpawn(options.env);
56750+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
5675156751
Log.debug(`Executing command: ${commandText}`);
5675256752
const { status: exitCode, signal, stdout, stderr } = _spawnSync(command, args, { ...options, env: env3, encoding: "utf8", shell: true, stdio: "pipe" });
5675356753
const status = statusFromExitCodeAndSignal(exitCode, signal);
@@ -56756,11 +56756,50 @@ ${logOutput}`);
5675656756
}
5675756757
throw new Error(stderr);
5675856758
}
56759+
static exec(command, options = {}) {
56760+
return new Promise((resolve, reject) => {
56761+
var _a2, _b;
56762+
const outputMode = options.mode;
56763+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
56764+
Log.debug(`Executing command: ${command}`);
56765+
const childProcess = _exec(command, { ...options, env: env3 });
56766+
let logOutput = "";
56767+
let stdout = "";
56768+
let stderr = "";
56769+
(_a2 = childProcess.stderr) == null ? void 0 : _a2.on("data", (message) => {
56770+
stderr += message;
56771+
logOutput += message;
56772+
if (outputMode === void 0 || outputMode === "enabled") {
56773+
process.stderr.write(message);
56774+
}
56775+
});
56776+
(_b = childProcess.stdout) == null ? void 0 : _b.on("data", (message) => {
56777+
stdout += message;
56778+
logOutput += message;
56779+
if (outputMode === void 0 || outputMode === "enabled") {
56780+
process.stderr.write(message);
56781+
}
56782+
});
56783+
childProcess.on("close", (exitCode, signal) => {
56784+
const exitDescription = exitCode !== null ? `exit code "${exitCode}"` : `signal "${signal}"`;
56785+
const printFn = outputMode === "on-error" ? Log.error : Log.debug;
56786+
const status = statusFromExitCodeAndSignal(exitCode, signal);
56787+
printFn(`Command "${command}" completed with ${exitDescription}.`);
56788+
printFn(`Process output:
56789+
${logOutput}`);
56790+
if (status === 0 || options.suppressErrorOnFailingExitCode) {
56791+
resolve({ stdout, stderr, status });
56792+
} else {
56793+
reject(outputMode === "silent" ? logOutput : void 0);
56794+
}
56795+
});
56796+
});
56797+
}
5675956798
};
5676056799
function statusFromExitCodeAndSignal(exitCode, signal) {
5676156800
return exitCode ?? signal ?? -1;
5676256801
}
56763-
function getEnvironmentForNonInteractiveSpawn(userProvidedEnv) {
56802+
function getEnvironmentForNonInteractiveCommand(userProvidedEnv) {
5676456803
const forceColorValue = supports_color_default2.stdout !== false ? supports_color_default2.stdout.level.toString() : void 0;
5676556804
return { FORCE_COLOR: forceColorValue, ...userProvidedEnv ?? process.env };
5676656805
}

.github/local-actions/changelog/main.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56565,7 +56565,7 @@ var supportsColor2 = {
5656556565
var supports_color_default2 = supportsColor2;
5656656566

5656756567
//
56568-
import { spawn as _spawn, spawnSync as _spawnSync } from "child_process";
56568+
import { spawn as _spawn, spawnSync as _spawnSync, exec as _exec } from "child_process";
5656956569
var ChildProcess = class {
5657056570
static spawnInteractive(command, args, options = {}) {
5657156571
return new Promise((resolve, reject) => {
@@ -56579,7 +56579,7 @@ var ChildProcess = class {
5657956579
return new Promise((resolve, reject) => {
5658056580
const commandText = `${command} ${args.join(" ")}`;
5658156581
const outputMode = options.mode;
56582-
const env3 = getEnvironmentForNonInteractiveSpawn(options.env);
56582+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
5658356583
Log.debug(`Executing command: ${commandText}`);
5658456584
const childProcess = _spawn(command, args, { ...options, env: env3, shell: true, stdio: "pipe" });
5658556585
let logOutput = "";
@@ -56620,7 +56620,7 @@ ${logOutput}`);
5662056620
}
5662156621
static spawnSync(command, args, options = {}) {
5662256622
const commandText = `${command} ${args.join(" ")}`;
56623-
const env3 = getEnvironmentForNonInteractiveSpawn(options.env);
56623+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
5662456624
Log.debug(`Executing command: ${commandText}`);
5662556625
const { status: exitCode, signal, stdout, stderr } = _spawnSync(command, args, { ...options, env: env3, encoding: "utf8", shell: true, stdio: "pipe" });
5662656626
const status = statusFromExitCodeAndSignal(exitCode, signal);
@@ -56629,11 +56629,50 @@ ${logOutput}`);
5662956629
}
5663056630
throw new Error(stderr);
5663156631
}
56632+
static exec(command, options = {}) {
56633+
return new Promise((resolve, reject) => {
56634+
var _a2, _b;
56635+
const outputMode = options.mode;
56636+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
56637+
Log.debug(`Executing command: ${command}`);
56638+
const childProcess = _exec(command, { ...options, env: env3 });
56639+
let logOutput = "";
56640+
let stdout = "";
56641+
let stderr = "";
56642+
(_a2 = childProcess.stderr) == null ? void 0 : _a2.on("data", (message) => {
56643+
stderr += message;
56644+
logOutput += message;
56645+
if (outputMode === void 0 || outputMode === "enabled") {
56646+
process.stderr.write(message);
56647+
}
56648+
});
56649+
(_b = childProcess.stdout) == null ? void 0 : _b.on("data", (message) => {
56650+
stdout += message;
56651+
logOutput += message;
56652+
if (outputMode === void 0 || outputMode === "enabled") {
56653+
process.stderr.write(message);
56654+
}
56655+
});
56656+
childProcess.on("close", (exitCode, signal) => {
56657+
const exitDescription = exitCode !== null ? `exit code "${exitCode}"` : `signal "${signal}"`;
56658+
const printFn = outputMode === "on-error" ? Log.error : Log.debug;
56659+
const status = statusFromExitCodeAndSignal(exitCode, signal);
56660+
printFn(`Command "${command}" completed with ${exitDescription}.`);
56661+
printFn(`Process output:
56662+
${logOutput}`);
56663+
if (status === 0 || options.suppressErrorOnFailingExitCode) {
56664+
resolve({ stdout, stderr, status });
56665+
} else {
56666+
reject(outputMode === "silent" ? logOutput : void 0);
56667+
}
56668+
});
56669+
});
56670+
}
5663256671
};
5663356672
function statusFromExitCodeAndSignal(exitCode, signal) {
5663456673
return exitCode ?? signal ?? -1;
5663556674
}
56636-
function getEnvironmentForNonInteractiveSpawn(userProvidedEnv) {
56675+
function getEnvironmentForNonInteractiveCommand(userProvidedEnv) {
5663756676
const forceColorValue = supports_color_default2.stdout !== false ? supports_color_default2.stdout.level.toString() : void 0;
5663856677
return { FORCE_COLOR: forceColorValue, ...userProvidedEnv ?? process.env };
5663956678
}

.github/workflows/ci.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,19 @@ jobs:
7171
- uses: ./github-actions/bazel/setup
7272
- run: yarn install --immutable
7373
- run: yarn bazel test --sandbox_writable_path="$HOME/Library/Application Support" --test_tag_filters=macos --build_tests_only -- //...
74+
75+
workflow-perf:
76+
timeout-minutes: 30
77+
runs-on: ubuntu-latest
78+
steps:
79+
# Because the checkout and setup node action is contained in the dev-infra repo, we must
80+
# checkout the repo to be able to run the action we have created. Other repos will skip
81+
# this step.
82+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
83+
- uses: ./github-actions/npm/checkout-and-setup-node
84+
- uses: ./github-actions/bazel/setup
85+
- run: yarn install --immutable
86+
- run: yarn ng-dev perf workflows --json
87+
# Always run this step to ensure that the job always is successful
88+
- if: ${{ always() }}
89+
run: exit 0

.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/perf-tests/test-rerun.diff

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
diff --git a/ng-dev/utils/test/g3.spec.ts b/ng-dev/utils/test/g3.spec.ts
2+
index a82c1b7a..8e0b24f8 100644
3+
--- a/ng-dev/utils/test/g3.spec.ts
4+
+++ b/ng-dev/utils/test/g3.spec.ts
5+
@@ -29,9 +29,9 @@ describe('G3Stats', () => {
6+
});
7+
8+
function setupFakeSyncConfig(config: GoogleSyncConfig): string {
9+
- const configFileName = 'sync-test-conf.json';
10+
- fs.writeFileSync(path.join(git.baseDir, configFileName), JSON.stringify(config));
11+
- return configFileName;
12+
+ const somethingelse = 'sync-test-conf.json';
13+
+ fs.writeFileSync(path.join(git.baseDir, somethingelse), JSON.stringify(config));
14+
+ return somethingelse;
15+
}
16+
17+
describe('gathering stats', () => {

github-actions/create-pr-for-changes/main.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41501,7 +41501,7 @@ var supportsColor2 = {
4150141501
var supports_color_default2 = supportsColor2;
4150241502

4150341503
//
41504-
import { spawn as _spawn, spawnSync as _spawnSync } from "child_process";
41504+
import { spawn as _spawn, spawnSync as _spawnSync, exec as _exec } from "child_process";
4150541505
var ChildProcess = class {
4150641506
static spawnInteractive(command, args, options = {}) {
4150741507
return new Promise((resolve, reject) => {
@@ -41515,7 +41515,7 @@ var ChildProcess = class {
4151541515
return new Promise((resolve, reject) => {
4151641516
const commandText = `${command} ${args.join(" ")}`;
4151741517
const outputMode = options.mode;
41518-
const env3 = getEnvironmentForNonInteractiveSpawn(options.env);
41518+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
4151941519
Log.debug(`Executing command: ${commandText}`);
4152041520
const childProcess = _spawn(command, args, { ...options, env: env3, shell: true, stdio: "pipe" });
4152141521
let logOutput = "";
@@ -41556,7 +41556,7 @@ ${logOutput}`);
4155641556
}
4155741557
static spawnSync(command, args, options = {}) {
4155841558
const commandText = `${command} ${args.join(" ")}`;
41559-
const env3 = getEnvironmentForNonInteractiveSpawn(options.env);
41559+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
4156041560
Log.debug(`Executing command: ${commandText}`);
4156141561
const { status: exitCode, signal, stdout, stderr } = _spawnSync(command, args, { ...options, env: env3, encoding: "utf8", shell: true, stdio: "pipe" });
4156241562
const status = statusFromExitCodeAndSignal(exitCode, signal);
@@ -41565,11 +41565,50 @@ ${logOutput}`);
4156541565
}
4156641566
throw new Error(stderr);
4156741567
}
41568+
static exec(command, options = {}) {
41569+
return new Promise((resolve, reject) => {
41570+
var _a, _b;
41571+
const outputMode = options.mode;
41572+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
41573+
Log.debug(`Executing command: ${command}`);
41574+
const childProcess = _exec(command, { ...options, env: env3 });
41575+
let logOutput = "";
41576+
let stdout = "";
41577+
let stderr = "";
41578+
(_a = childProcess.stderr) == null ? void 0 : _a.on("data", (message) => {
41579+
stderr += message;
41580+
logOutput += message;
41581+
if (outputMode === void 0 || outputMode === "enabled") {
41582+
process.stderr.write(message);
41583+
}
41584+
});
41585+
(_b = childProcess.stdout) == null ? void 0 : _b.on("data", (message) => {
41586+
stdout += message;
41587+
logOutput += message;
41588+
if (outputMode === void 0 || outputMode === "enabled") {
41589+
process.stderr.write(message);
41590+
}
41591+
});
41592+
childProcess.on("close", (exitCode, signal) => {
41593+
const exitDescription = exitCode !== null ? `exit code "${exitCode}"` : `signal "${signal}"`;
41594+
const printFn = outputMode === "on-error" ? Log.error : Log.debug;
41595+
const status = statusFromExitCodeAndSignal(exitCode, signal);
41596+
printFn(`Command "${command}" completed with ${exitDescription}.`);
41597+
printFn(`Process output:
41598+
${logOutput}`);
41599+
if (status === 0 || options.suppressErrorOnFailingExitCode) {
41600+
resolve({ stdout, stderr, status });
41601+
} else {
41602+
reject(outputMode === "silent" ? logOutput : void 0);
41603+
}
41604+
});
41605+
});
41606+
}
4156841607
};
4156941608
function statusFromExitCodeAndSignal(exitCode, signal) {
4157041609
return exitCode ?? signal ?? -1;
4157141610
}
41572-
function getEnvironmentForNonInteractiveSpawn(userProvidedEnv) {
41611+
function getEnvironmentForNonInteractiveCommand(userProvidedEnv) {
4157341612
const forceColorValue = supports_color_default2.stdout !== false ? supports_color_default2.stdout.level.toString() : void 0;
4157441613
return { FORCE_COLOR: forceColorValue, ...userProvidedEnv ?? process.env };
4157541614
}

github-actions/slash-commands/main.js

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53425,7 +53425,7 @@ var supportsColor2 = {
5342553425
var supports_color_default2 = supportsColor2;
5342653426

5342753427
//
53428-
import { spawn as _spawn, spawnSync as _spawnSync } from "child_process";
53428+
import { spawn as _spawn, spawnSync as _spawnSync, exec as _exec } from "child_process";
5342953429
var ChildProcess = class {
5343053430
static spawnInteractive(command, args, options = {}) {
5343153431
return new Promise((resolve, reject) => {
@@ -53439,7 +53439,7 @@ var ChildProcess = class {
5343953439
return new Promise((resolve, reject) => {
5344053440
const commandText = `${command} ${args.join(" ")}`;
5344153441
const outputMode = options.mode;
53442-
const env3 = getEnvironmentForNonInteractiveSpawn(options.env);
53442+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
5344353443
Log.debug(`Executing command: ${commandText}`);
5344453444
const childProcess = _spawn(command, args, { ...options, env: env3, shell: true, stdio: "pipe" });
5344553445
let logOutput = "";
@@ -53480,7 +53480,7 @@ ${logOutput}`);
5348053480
}
5348153481
static spawnSync(command, args, options = {}) {
5348253482
const commandText = `${command} ${args.join(" ")}`;
53483-
const env3 = getEnvironmentForNonInteractiveSpawn(options.env);
53483+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
5348453484
Log.debug(`Executing command: ${commandText}`);
5348553485
const { status: exitCode, signal, stdout, stderr } = _spawnSync(command, args, { ...options, env: env3, encoding: "utf8", shell: true, stdio: "pipe" });
5348653486
const status = statusFromExitCodeAndSignal(exitCode, signal);
@@ -53489,11 +53489,50 @@ ${logOutput}`);
5348953489
}
5349053490
throw new Error(stderr);
5349153491
}
53492+
static exec(command, options = {}) {
53493+
return new Promise((resolve, reject) => {
53494+
var _a2, _b;
53495+
const outputMode = options.mode;
53496+
const env3 = getEnvironmentForNonInteractiveCommand(options.env);
53497+
Log.debug(`Executing command: ${command}`);
53498+
const childProcess = _exec(command, { ...options, env: env3 });
53499+
let logOutput = "";
53500+
let stdout = "";
53501+
let stderr = "";
53502+
(_a2 = childProcess.stderr) == null ? void 0 : _a2.on("data", (message) => {
53503+
stderr += message;
53504+
logOutput += message;
53505+
if (outputMode === void 0 || outputMode === "enabled") {
53506+
process.stderr.write(message);
53507+
}
53508+
});
53509+
(_b = childProcess.stdout) == null ? void 0 : _b.on("data", (message) => {
53510+
stdout += message;
53511+
logOutput += message;
53512+
if (outputMode === void 0 || outputMode === "enabled") {
53513+
process.stderr.write(message);
53514+
}
53515+
});
53516+
childProcess.on("close", (exitCode, signal) => {
53517+
const exitDescription = exitCode !== null ? `exit code "${exitCode}"` : `signal "${signal}"`;
53518+
const printFn = outputMode === "on-error" ? Log.error : Log.debug;
53519+
const status = statusFromExitCodeAndSignal(exitCode, signal);
53520+
printFn(`Command "${command}" completed with ${exitDescription}.`);
53521+
printFn(`Process output:
53522+
${logOutput}`);
53523+
if (status === 0 || options.suppressErrorOnFailingExitCode) {
53524+
resolve({ stdout, stderr, status });
53525+
} else {
53526+
reject(outputMode === "silent" ? logOutput : void 0);
53527+
}
53528+
});
53529+
});
53530+
}
5349253531
};
5349353532
function statusFromExitCodeAndSignal(exitCode, signal) {
5349453533
return exitCode ?? signal ?? -1;
5349553534
}
53496-
function getEnvironmentForNonInteractiveSpawn(userProvidedEnv) {
53535+
function getEnvironmentForNonInteractiveCommand(userProvidedEnv) {
5349753536
const forceColorValue = supports_color_default2.stdout !== false ? supports_color_default2.stdout.level.toString() : void 0;
5349853537
return { FORCE_COLOR: forceColorValue, ...userProvidedEnv ?? process.env };
5349953538
}

ng-dev/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ ts_library(
2727
"//ng-dev/format",
2828
"//ng-dev/misc",
2929
"//ng-dev/ngbot",
30+
"//ng-dev/perf",
3031
"//ng-dev/pr",
3132
"//ng-dev/pr/common/labels",
3233
"//ng-dev/pr/config",

ng-dev/cli.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {buildReleaseParser} from './release/cli.js';
2020
import {tsCircularDependenciesBuilder} from './ts-circular-dependencies/index.js';
2121
import {captureLogOutputForCommand} from './utils/logging.js';
2222
import {buildAuthParser} from './auth/cli.js';
23+
import {buildPerfParser} from './perf/cli.js';
2324
import {Argv} from 'yargs';
2425

2526
runParserWithCompletedFunctions((yargs: Argv) => {
@@ -38,6 +39,7 @@ runParserWithCompletedFunctions((yargs: Argv) => {
3839
.command('caretaker <command>', '', buildCaretakerParser)
3940
.command('misc <command>', '', buildMiscParser)
4041
.command('ngbot <command>', false, buildNgbotParser)
42+
.command('perf <command>', '', buildPerfParser)
4143
.wrap(120)
4244
.strict();
4345
});

0 commit comments

Comments
 (0)