Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bazel/integration/test_runner/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ ts_project(
tsconfig = "//bazel:tsconfig",
deps = [
"//bazel:node_modules/@types/node",
"//bazel:node_modules/chalk",
"//bazel:node_modules/true-case-path",
],
)
108 changes: 108 additions & 0 deletions bazel/integration/test_runner/runner.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* found in the LICENSE file at https://angular.io/license
*/

import {existsSync} from 'node:fs';
import fs from 'node:fs/promises';
import path from 'node:path';
import os from 'node:os';
Expand All @@ -30,6 +31,14 @@ import {
} from './process_utils.mjs';
import {ENVIRONMENT_TMP_PLACEHOLDER} from './constants.mjs';
import {debug} from './debug.mjs';
import chalk from 'chalk';

// Convience access to chalk colors.
const {red, green} = chalk;
/** The size discrepancy we allow in bytes. */
const THRESHOLD_BYTES = 5000;
/** The size discrepancy as a percentage. */
const THRESHOLD_PERCENT = 5;

/** Error class that is used when an integration command fails. */
class IntegrationTestCommandError extends Error {}
Expand Down Expand Up @@ -270,6 +279,11 @@ export class TestRunner {
console.info(
`Successfully ran all commands in test directory: ${path.normalize(testWorkingDir)}`,
);

// If the integration test provides a size.json file we use it as a size tracking marker.
if (existsSync(path.join(testWorkingDir, 'size.json'))) {
await this._runSizeTracking(testWorkingDir, commandEnv);
}
}

/**
Expand Down Expand Up @@ -299,4 +313,98 @@ export class TestRunner {

return {...defaults, ...baseEnv};
}

/**
* Runs the size tracking scripting.
*
* Builds the integration test application and then checks if the size of the generated files varies too
* far from our known file sizes.
*/
private async _runSizeTracking(
testWorkingDir: string,
commandEnv: NodeJS.ProcessEnv,
): Promise<void> {
const success = await runCommandInChildProcess('yarn', ['build'], testWorkingDir, commandEnv);
if (!success) {
throw Error('Failed to build for size tracking.');
}

const sizes: {[key: string]: SizeCheckResult} = {};

const expectedSizes = JSON.parse(
await fs.readFile(path.join(testWorkingDir, 'size.json'), 'utf-8'),
) as {[key: string]: number};

for (let [filename, expectedSize] of Object.entries(expectedSizes)) {
const generedFilePath = path.join(testWorkingDir, 'dist', filename);
if (!existsSync(generedFilePath)) {
sizes[filename] = {
actual: undefined,
failing: false,
expected: expectedSize,
details: 'missing',
};
} else {
const {size: actualSize} = await fs.stat(path.join(testWorkingDir, 'dist', filename));
const absoluteSizeDiff = Math.abs(actualSize - expectedSize);
const percentSizeDiff = (absoluteSizeDiff / expectedSize) * 100;
const direction = actualSize === expectedSize ? '' : actualSize > expectedSize ? '+' : '-';
sizes[filename] = {
actual: actualSize,
expected: expectedSize,
failing: absoluteSizeDiff > THRESHOLD_BYTES || percentSizeDiff > THRESHOLD_PERCENT,
details: {
raw: `${direction}${absoluteSizeDiff.toFixed(0)}`,
percent: `${direction}${percentSizeDiff.toFixed(3)}`,
},
};
}
}

console.info(Array(80).fill('=').join(''));
console.info(
`${Array(28).fill('=').join('')} SIZE TRACKING RESULTS ${Array(29).fill('=').join('')}`,
);
console.info(Array(80).fill('=').join(''));
let failed = false;
for (let [filename, {actual, expected, failing, details}] of Object.entries(sizes)) {
failed = failed || failing;
const bullet = failing ? green('✔') : red('✘');
console.info(` ${bullet} ${filename}`);
if (details === 'missing') {
console.info(
` File not found in generated integration test application, either ensure the file is created or remove it from the size tracking json file.`,
);
} else {
console.info(
` Actual Size: ${actual} | Expected Size: ${expected} | ${details.raw} bytes (${details.raw}%)`,
);
}
}
console.info();
if (failed) {
const sizeFileLocation = path.join(this.testPackage, 'size.json');
console.info(
`If this is a desired change, please update the size limits in: ${sizeFileLocation}`,
);
process.exitCode = 1;
} else {
console.info(
`Payload size check passed. All diffs are less than ${THRESHOLD_PERCENT}% or ${THRESHOLD_BYTES} bytes.`,
);
}
console.info(Array(80).fill('=').join(''));
}
}

interface SizeCheckResult {
expected: number;
actual: number | undefined;
failing: boolean;
details:
| 'missing'
| {
raw: string;
percent: string;
};
}
1 change: 1 addition & 0 deletions bazel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"@types/wait-on": "^5.3.4",
"@types/yargs": "17.0.33",
"browser-sync": "3.0.4",
"chalk": "5.4.1",
"piscina": "^5.0.0",
"send": "1.2.0",
"true-case-path": "2.2.1",
Expand Down
9 changes: 9 additions & 0 deletions bazel/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading