Skip to content

Commit fd9135a

Browse files
committed
feat(bazel/integration): support tracking sizes within the integration test rule directly
Add size tracking support into the integration rule instead of adding it on downstream.
1 parent 6f54d14 commit fd9135a

File tree

4 files changed

+119
-0
lines changed

4 files changed

+119
-0
lines changed

bazel/integration/test_runner/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ ts_project(
88
tsconfig = "//bazel:tsconfig",
99
deps = [
1010
"//bazel:node_modules/@types/node",
11+
"//bazel:node_modules/chalk",
1112
"//bazel:node_modules/true-case-path",
1213
],
1314
)

bazel/integration/test_runner/runner.mts

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9+
import {existsSync} from 'node:fs';
910
import fs from 'node:fs/promises';
1011
import path from 'node:path';
1112
import os from 'node:os';
@@ -30,6 +31,14 @@ import {
3031
} from './process_utils.mjs';
3132
import {ENVIRONMENT_TMP_PLACEHOLDER} from './constants.mjs';
3233
import {debug} from './debug.mjs';
34+
import chalk from 'chalk';
35+
36+
// Convience access to chalk colors.
37+
const {red, green} = chalk;
38+
/** The size discrepancy we allow in bytes. */
39+
const THRESHOLD_BYTES = 5000;
40+
/** The size discrepancy as a percentage. */
41+
const THRESHOLD_PERCENT = 5;
3342

3443
/** Error class that is used when an integration command fails. */
3544
class IntegrationTestCommandError extends Error {}
@@ -270,6 +279,11 @@ export class TestRunner {
270279
console.info(
271280
`Successfully ran all commands in test directory: ${path.normalize(testWorkingDir)}`,
272281
);
282+
283+
// If the integration test provides a size.json file we use it as a size tracking marker.
284+
if (existsSync(path.join(testWorkingDir, 'size.json'))) {
285+
await this._runSizeTracking(testWorkingDir, commandEnv);
286+
}
273287
}
274288

275289
/**
@@ -299,4 +313,98 @@ export class TestRunner {
299313

300314
return {...defaults, ...baseEnv};
301315
}
316+
317+
/**
318+
* Runs the size tracking scripting.
319+
*
320+
* Builds the integration test application and then checks if the size of the generated files varies too
321+
* far from our known file sizes.
322+
*/
323+
private async _runSizeTracking(
324+
testWorkingDir: string,
325+
commandEnv: NodeJS.ProcessEnv,
326+
): Promise<void> {
327+
const success = await runCommandInChildProcess('yarn', ['build'], testWorkingDir, commandEnv);
328+
if (!success) {
329+
throw Error('Failed to build for size tracking.');
330+
}
331+
332+
const sizes: {[key: string]: SizeCheckResult} = {};
333+
334+
const expectedSizes = JSON.parse(
335+
await fs.readFile(path.join(testWorkingDir, 'size.json'), 'utf-8'),
336+
) as {[key: string]: number};
337+
338+
for (let [filename, expectedSize] of Object.entries(expectedSizes)) {
339+
const generedFilePath = path.join(testWorkingDir, 'dist', filename);
340+
if (!existsSync(generedFilePath)) {
341+
sizes[filename] = {
342+
actual: undefined,
343+
failing: false,
344+
expected: expectedSize,
345+
details: 'missing',
346+
};
347+
} else {
348+
const {size: actualSize} = await fs.stat(path.join(testWorkingDir, 'dist', filename));
349+
const absoluteSizeDiff = Math.abs(actualSize - expectedSize);
350+
const percentSizeDiff = (absoluteSizeDiff / expectedSize) * 100;
351+
const direction = actualSize === expectedSize ? '' : actualSize > expectedSize ? '+' : '-';
352+
sizes[filename] = {
353+
actual: actualSize,
354+
expected: expectedSize,
355+
failing: absoluteSizeDiff > THRESHOLD_BYTES || percentSizeDiff > THRESHOLD_PERCENT,
356+
details: {
357+
raw: `${direction}${absoluteSizeDiff.toFixed(0)}`,
358+
percent: `${direction}${percentSizeDiff.toFixed(3)}`,
359+
},
360+
};
361+
}
362+
}
363+
364+
console.info(Array(80).fill('=').join(''));
365+
console.info(
366+
`${Array(28).fill('=').join('')} SIZE TRACKING RESULTS ${Array(29).fill('=').join('')}`,
367+
);
368+
console.info(Array(80).fill('=').join(''));
369+
let failed = false;
370+
for (let [filename, {actual, expected, failing, details}] of Object.entries(sizes)) {
371+
failed = failed || failing;
372+
const bullet = failing ? green('✔') : red('✘');
373+
console.info(` ${bullet} ${filename}`);
374+
if (details === 'missing') {
375+
console.info(
376+
` File not found in generated integration test application, either ensure the file is created or remove it from the size tracking json file.`,
377+
);
378+
} else {
379+
console.info(
380+
` Actual Size: ${actual} | Expected Size: ${expected} | ${details.raw} bytes (${details.raw}%)`,
381+
);
382+
}
383+
}
384+
console.info();
385+
if (failed) {
386+
const sizeFileLocation = path.join(this.testPackage, 'size.json');
387+
console.info(
388+
`If this is a desired change, please update the size limits in: ${sizeFileLocation}`,
389+
);
390+
process.exitCode = 1;
391+
} else {
392+
console.info(
393+
`Payload size check passed. All diffs are less than ${THRESHOLD_PERCENT}% or ${THRESHOLD_BYTES} bytes.`,
394+
);
395+
}
396+
console.info(Array(80).fill('=').join(''));
397+
}
398+
}
399+
400+
interface SizeCheckResult {
401+
expected: number;
402+
actual: number | undefined;
403+
failing: boolean;
404+
details:
405+
| 'missing'
406+
| {
407+
raw: string;
408+
percent: string;
409+
};
302410
}

bazel/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"@types/wait-on": "^5.3.4",
1010
"@types/yargs": "17.0.33",
1111
"browser-sync": "3.0.4",
12+
"chalk": "5.4.1",
1213
"piscina": "^5.0.0",
1314
"send": "1.2.0",
1415
"true-case-path": "2.2.1",

bazel/pnpm-lock.yaml

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)