ci: use new size tracking in integration tests (#62650)

Use the new size tracking within integration tests

PR Close #62650
This commit is contained in:
Joey Perrott
2025-07-15 19:07:35 +00:00
committed by Kirill Cherkashin
parent 8caaf5ed22
commit b0effa1cc3
27 changed files with 42 additions and 376 deletions
+1 -1
View File
@@ -9,7 +9,7 @@ export const format: FormatConfig = {
'**/*.{yaml,yml}',
'**/*.{js,ts,mjs,mts,cjs,cts,tsx}',
'devtools/**/*.{js,ts,mjs,mts,cjs,cts,html,scss}',
'integration/**/size.json',
// Do not format d.ts files as they are generated
'!**/*.d.ts',
// Both third_party and .yarn are directories containing copied code which should
+1 -1
View File
@@ -415,7 +415,7 @@ groups:
- *no-groups-above-this-rejected
- >
contains_any_globs(files, [
'goldens/size-tracking/**/{*,.*}'
'integration/**/size.json'
])
reviewers:
users:
-3
View File
@@ -11,9 +11,6 @@ exports_files([
"karma-js.conf.js",
"browser-providers.conf.js",
YARN_PATH,
"scripts/ci/bazel-payload-size.sh",
"scripts/ci/payload-size.sh",
"scripts/ci/payload-size.js",
"package.json",
"angular.json",
])
+1 -1
View File
@@ -271,7 +271,7 @@ yarn_install(
git_repository(
name = "devinfra",
commit = "6f54d143077baef582d70873722166fdc040066c",
commit = "ae6afbc8c8cd972146d1ac062ccd93b9e6587986",
remote = "https://github.com/angular/dev-infra.git",
)
-4
View File
@@ -1,9 +1,5 @@
package(default_visibility = ["//visibility:public"])
exports_files([
"size-tracking/integration-payloads.json",
])
filegroup(
name = "public-api",
srcs = glob([
@@ -1,62 +0,0 @@
{
"cli-hello-world": {
"uncompressed": {
"main": 137461,
"polyfills": 34579
}
},
"cli-hello-world-ivy-i18n": {
"uncompressed": {
"main": 135813,
"polyfills": 35883
}
},
"cli-hello-world-lazy": {
"uncompressed": {
"main": 108611,
"polyfills": 34169,
"lazy.routes": 361
}
},
"forms": {
"uncompressed": {
"main": 181773,
"polyfills": 33772
}
},
"animations": {
"uncompressed": {
"main": 143167,
"polyfills": 34023,
"open-close.component": 1190
}
},
"animations-async": {
"uncompressed": {
"main": 86540,
"polyfills": 33782,
"browser": 63949,
"open-close.component": 1218
}
},
"standalone-bootstrap": {
"uncompressed": {
"main": 94769,
"polyfills": 33802
}
},
"defer": {
"uncompressed": {
"main": 12709,
"polyfills": 33807,
"defer.component": 345
}
},
"platform-server-hydration/browser": {
"uncompressed": {
"main": 221976,
"polyfills": 34544,
"event-dispatch-contract.min": 476
}
}
}
@@ -3,5 +3,4 @@ load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
setup_chromium = True,
track_payload_size = "cli-hello-world-ivy-i18n",
)
@@ -0,0 +1,4 @@
{
"dist/main.js": 135813,
"dist/polyfills.js": 35883
}
@@ -2,5 +2,4 @@ load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
track_payload_size = "cli-hello-world-lazy",
)
+1 -1
View File
@@ -2,4 +2,4 @@
This test checks bundle sizes when there is a lazy module. It also checks if the `ngDevMode` global variable and string references in `packages/core/src/util/ng_dev_mode.ts` are correctly removed.
This test contains a lazy route to ensure `ngDevMode` removal happens even across chunks, and a payload size check in `/goldens/size-tracking/integration-payloads.json` to ensure extra code is not retained accidentally.
This test contains a lazy route to ensure `ngDevMode` removal happens even across chunks, and a payload size check to ensure extra code is not retained accidentally.
@@ -0,0 +1,5 @@
{
"dist/main.js": 108611,
"dist/polyfills.js": 34169,
"dist/lazy.routes-[hash].js": 361
}
-1
View File
@@ -2,5 +2,4 @@ load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
track_payload_size = "cli-hello-world",
)
+4
View File
@@ -0,0 +1,4 @@
{
"dist/main.js": 137461,
"dist/polyfills.js": 34579
}
-1
View File
@@ -2,5 +2,4 @@ load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
track_payload_size = "defer",
)
+5
View File
@@ -0,0 +1,5 @@
{
"dist/main.js": 12709,
"dist/polyfills.js": 33807,
"dist/defer.component-[hash].js": 345
}
-19
View File
@@ -14,8 +14,6 @@ def _ng_integration_test(name, setup_chromium = False, **kwargs):
pinned_npm_packages = kwargs.pop("pinned_npm_packages", [])
toolchains = kwargs.pop("toolchains", [])
environment = kwargs.pop("environment", {})
track_payload_size = kwargs.pop("track_payload_size", None)
track_payload_paths = kwargs.pop("track_payload_paths", [""])
data = kwargs.pop("data", [])
if setup_chromium:
@@ -33,23 +31,6 @@ def _ng_integration_test(name, setup_chromium = False, **kwargs):
"yarn test",
])
if track_payload_size:
commands += [
"yarn build",
]
for path in track_payload_paths:
commands += [
# TODO: Replace the track payload-size script with a RBE and Windows-compatible script.
"$(rootpath //:scripts/ci/bazel-payload-size.sh) {bundle}{path} 'dist{path}/*.js' true ${runfiles}/angular/$(rootpath //goldens:size-tracking/integration-payloads.json)".format(bundle = track_payload_size, path = path, runfiles = "${RUNFILES}"),
]
data += [
"//goldens:size-tracking/integration-payloads.json",
"//:scripts/ci/bazel-payload-size.sh",
"//:scripts/ci/payload-size.sh",
"//:scripts/ci/payload-size.js",
]
# Complete list of npm packages to override in the test's package.json file mapped to
# tgz archive to use for the replacement. This is the full list for all integration
# tests. Any given integration does not need to use all of these packages.
@@ -2,5 +2,4 @@ load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
track_payload_size = "animations-async",
)
@@ -0,0 +1,6 @@
{
"dist/main.js": 86540,
"dist/polyfills.js": 33782,
"dist/browser-[hash].js": 63949,
"dist/open-close.component-[hash].js": 1218
}
@@ -2,5 +2,4 @@ load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
track_payload_size = "animations",
)
+5
View File
@@ -0,0 +1,5 @@
{
"dist/main.js": 143167,
"dist/polyfills.js": 34023,
"dist/open-close.component-[hash].js": 1190
}
@@ -3,8 +3,4 @@ load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
setup_chromium = True,
track_payload_paths = [
"/browser",
],
track_payload_size = "platform-server-hydration",
)
@@ -0,0 +1,5 @@
{
"dist/browser/main-[hash].js": 221976,
"dist/browser/polyfills-[hash].js": 34544,
"dist/browser/event-dispatch-contract.min.js": 476
}
@@ -2,5 +2,4 @@ load("//integration:index.bzl", "ng_integration_test")
ng_integration_test(
name = "test",
track_payload_size = "standalone-bootstrap",
)
@@ -0,0 +1,4 @@
{
"dist/main.js": 94769,
"dist/polyfills.js": 33802
}
-24
View File
@@ -1,24 +0,0 @@
#!/usr/bin/env bash
set -eu -o pipefail
# Source optional CI environment variables which are sandboxed out
# of the environment when running integration tests under Bazel
readonly bazelVarEnv="/tmp/bazel-ci-env.sh"
if [[ -f "$bazelVarEnv" ]]; then
source $bazelVarEnv
fi
# If running locally, at a minimum set PROJECT_ROOT
if [[ -z "${PROJECT_ROOT:-}" ]]; then
PROJECT_ROOT=$(cd $(dirname $0)/../..; pwd)
fi
# Bazel payload size tracking should always be treated as if this runs as part of
# a pull request. i.e. the results are not uploaded. This is necessary as Bazel test
# targets do not necessarily run for every commit, and cached results might originate
# from RBE-built pull requests. We will overhaut size-tracking anyway..
export CI_PULL_REQUEST="true"
source ${PROJECT_ROOT}/scripts/ci/payload-size.sh
trackPayloadSize "$@"
-92
View File
@@ -1,92 +0,0 @@
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
'use strict';
// Imports
const fs = require('fs');
const path = require('path');
// Get limit file, project name and commit SHA from command line arguments.
const [, , limitFile, project, commit] = process.argv;
const THRESHOLD_BYTES = 5000;
const THRESHOLD_PERCENT = 5;
// Load sizes.
const currentSizes = JSON.parse(fs.readFileSync('/tmp/current.log', 'utf8'));
const allLimitSizes = JSON.parse(fs.readFileSync(limitFile, 'utf8'));
const limitSizes = allLimitSizes[project];
if (!limitSizes) {
throw new Error(`ERROR: Project '${project}' is missing from limit file ${limitFile}.`);
}
// Check current sizes against limits.
let failed = false;
const successMessages = [];
const failureMessages = [];
for (const compressionType in limitSizes) {
if (typeof limitSizes[compressionType] === 'object') {
const limitPerFile = limitSizes[compressionType];
for (const filename in limitPerFile) {
const expectedSize = limitPerFile[filename];
const actualSize = currentSizes[`${compressionType}/${filename}`];
if (actualSize === undefined) {
failed = true;
// An expected compression type/file combination is missing. Maybe the file was renamed or
// removed. Report it as an error, so the user updates the corresponding limit file.
console.error(
`ERROR: Commit ${commit} ${compressionType} ${filename} measurement is missing. ` +
'Maybe the file was renamed or removed.',
);
} else {
const absoluteSizeDiff = Math.abs(actualSize - expectedSize);
// If size diff is larger than THRESHOLD_BYTES or THRESHOLD_PERCENT...
if (
absoluteSizeDiff > THRESHOLD_BYTES ||
absoluteSizeDiff > (expectedSize * THRESHOLD_PERCENT) / 100
) {
failed = true;
// We must also catch when the size is significantly lower than the payload limit, so
// we are forced to update the expected payload number when the payload size reduces.
// Otherwise, we won't be able to catch future regressions that happen to be below
// the artificially inflated limit.
const operator = actualSize > expectedSize ? 'exceeded' : 'fell below';
failureMessages.push(
`FAIL: Commit ${commit} ${compressionType} ${filename} ${operator} expected size by ${THRESHOLD_BYTES} bytes or >${THRESHOLD_PERCENT}% ` +
`(expected: ${expectedSize}, actual: ${actualSize}).`,
);
} else {
successMessages.push(
`SUCCESS: Commit ${commit} ${compressionType} ${filename} did NOT cross size threshold of ${THRESHOLD_BYTES} bytes or >${THRESHOLD_PERCENT} ` +
`(expected: ${expectedSize}, actual: ${actualSize}).`,
);
}
}
}
}
}
// Group failure messages separately from success messages so they are easier to find.
successMessages.concat(failureMessages).forEach((message) => console.error(message));
if (failed) {
const projectRoot = path.resolve(__dirname, '../..');
const limitFileRelPath = path.relative(projectRoot, limitFile);
console.info(
`If this is a desired change, please update the size limits in file '${limitFileRelPath}'.`,
);
process.exit(1);
} else {
console.info(
`Payload size check passed. All diffs are less than ${THRESHOLD_PERCENT}% or ${THRESHOLD_BYTES} bytes.`,
);
}
-157
View File
@@ -1,157 +0,0 @@
#!/usr/bin/env bash
set -eu -o pipefail
# statc makes `stat -c` work on both Linux & OSX
function statc () {
case $(uname) in
Darwin*) format='-f%z' ;;
*) format='-c%s' ;;
esac
stat ${format} $@
}
# sedr makes `sed -r` work on both Linux & OSX
function sedr () {
case $(uname) in
Darwin*) flag='-E' ;;
*) flag='-r' ;;
esac
sed ${flag} "$@"
}
readonly PROJECT_NAME="angular-payload-size"
NODE_MODULES_BIN=$PROJECT_ROOT/node_modules/.bin/
# Get the gzip size of a file with the specified compression level.
# $1: string - The file path.
# $2: number - The level of compression.
getGzipSize() {
local filePath=$1
local compLevel=$2
local compPath="$(mktemp).gz"
local size=-1
gzip -c -$compLevel "$filePath" >> "$compPath"
size=$(statc "$compPath")
rm "$compPath"
echo $size
}
# Calculate the size of target file uncompressed size, gzip7 size, gzip9 size
# Write to global variable $payloadData, $filename
calculateSize() {
# Remove .js and -T74CPV26.js from the filename
label=$(echo "$filename" | sed "s/\(-[A-Z0-9]\{8\}\)\?\.js//" | sed "s/.*\///")
rawSize=$(statc $filename)
gzip7Size=$(getGzipSize "$filename" 7)
gzip9Size=$(getGzipSize "$filename" 9)
# Log the sizes (for information/debugging purposes).
printf "Size: %6d (gzip7: %6d, gzip9: %6d) %s\n" $rawSize $gzip7Size $gzip9Size $label
payloadData="$payloadData\"uncompressed/$label\": $rawSize, "
payloadData="$payloadData\"gzip7/$label\": $gzip7Size, "
payloadData="$payloadData\"gzip9/$label\": $gzip9Size, "
}
# Check whether the file size is under limit.
# Exit with an error if limit is exceeded.
# $1: string - The name in database.
# $2: string - The payload size limit file.
checkSize() {
name="$1"
limitFile="$2"
# PRs and non-PR pushes will always test against the size-limits of the current revision.
node ${PROJECT_ROOT}/scripts/ci/payload-size.js $limitFile $name ${CI_COMMIT:-}
}
# Write timestamp to global variable `$payloadData`.
addTimestamp() {
# Add Timestamp
timestamp=$(date +%s)
payloadData="$payloadData\"timestamp\": $timestamp, "
}
# Write the current CI build URL to global variable `$payloadData`.
# This allows mapping the data stored in the database to the CI build job that generated it, which
# might contain more info/context.
# $1: string - The CI build URL.
addBuildUrl() {
buildUrl="$1"
payloadData="$payloadData\"buildUrl\": \"$buildUrl\", "
}
# Write the commit message for the specified CI commit to global variable `$payloadData`.
# $1: string - The commit SHA for this build (in `<SHA-1>` format).
addMessage() {
message="${1}"
message=$(echo $message | sed 's/\r//g' | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
payloadData="$payloadData\"message\": \"$message\", "
}
# Convert the current `payloadData` value to a JSON string.
# (Basically remove trailing `,` and wrap in `{...}`.)
payloadToJson() {
echo "{$(sedr 's|, *$||' <<< $payloadData)}"
}
# Upload data to firebase database if it's commit, print out data for pull requests.
# $1: string - The name in database.
uploadData() {
name="$1"
readonly safeBranchName=$(echo $CI_BRANCH | sed -e 's/\./_/g')
readonly dbPath=/payload/$name/$safeBranchName/$CI_COMMIT
readonly jsonPayload=$(payloadToJson)
# WARNING: CI_SECRET_PAYLOAD_FIREBASE_TOKEN should NOT be printed.
set +x
$NODE_MODULES_BIN/firebase database:update --data "$jsonPayload" --project $PROJECT_NAME --force --token "$CI_SECRET_PAYLOAD_FIREBASE_TOKEN" $dbPath
}
# Track payload size.
# $1: string - The name in database.
# $2: string - The file path.
# $3: true | false - Whether to check the payload size and fail the test if it exceeds limit.
# $4: [string] - The payload size limit file. Only necessary if `$3` is `true`.
trackPayloadSize() {
name="$1"
path="$2"
checkSize="$3"
limitFile="${4:-}"
payloadData=""
# Calculate the file sizes.
echo "Calculating sizes for files in '$path'..."
for filename in $path; do
calculateSize
done
# Save the file sizes to be retrieved from `payload-size.js`.
echo "$(payloadToJson)" > /tmp/current.log
# If this is a non-PR build, upload the data to firebase.
if [[ "${CI_PULL_REQUEST:-}" == "false" ]]; then
echo "Uploading data for '$name'..."
addTimestamp
addBuildUrl $CI_BUILD_URL
addMessage $CI_COMMIT
uploadData $name
else
echo "Skipped uploading data for '$name', because this is a pull request."
fi
# Check the file sizes against the specified limits.
if [[ $checkSize = true ]]; then
echo "Verifying sizes against '$limitFile'..."
checkSize $name $limitFile
else
echo "Skipped verifying sizes (checkSize: false)."
fi
}