Skip to content

Commit dab0288

Browse files
committed
build: add infrastructure for performing size golden tests
1 parent fb814bb commit dab0288

16 files changed

+301
-28
lines changed

.circleci/config.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -514,8 +514,8 @@ jobs:
514514
- *setup_bazel_remote_execution
515515
- *yarn_install
516516
- *setup_bazel_binary
517-
# Integration tests run with --config=view-engine because we release with View Engine.
518-
- run: bazel test integration/... --build_tests_only --config=view-engine
517+
- run: yarn integration-tests
518+
- run: yarn integration-tests:view-engine
519519

520520
# ----------------------------------------------------------------------------
521521
# Job that runs all Bazel tests against material-components-web@canary

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,3 +319,5 @@
319319
/.vscode/** @angular/dev-infra-components @mmalerba
320320
/src/* @jelbourn
321321
/goldens/ts-circular-deps.json @angular/dev-infra-components
322+
/goldens/size-test.json @jelbourn @mmalerba @crisbeto
323+
/goldens/* @angular/dev-infra-components

WORKSPACE

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
88
# Add NodeJS rules
99
http_archive(
1010
name = "build_bazel_rules_nodejs",
11-
sha256 = "d14076339deb08e5460c221fae5c5e9605d2ef4848eee1f0c81c9ffdc1ab31c1",
12-
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.6.1/rules_nodejs-1.6.1.tar.gz"],
11+
sha256 = "84abf7ac4234a70924628baa9a73a5a5cbad944c4358cf9abdb4aab29c9a5b77",
12+
urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/1.7.0/rules_nodejs-1.7.0.tar.gz"],
1313
)
1414

1515
# Add sass rules

goldens/BUILD.bazel

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
exports_files([
2+
"size-test.json",
3+
])

goldens/size-test.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"material/list": 224424
3+
}

integration/size-test/BUILD.bazel

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
load("//tools:defaults.bzl", "ts_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
exports_files([
6+
"rollup.config.js",
7+
"terser-config.json",
8+
])
9+
10+
ts_library(
11+
name = "check-size",
12+
srcs = ["check-size.ts"],
13+
deps = [
14+
"@npm//@types/node",
15+
"@npm//chalk",
16+
],
17+
)

integration/size-test/check-size.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Script that measures the size of a given test file and compares it
3+
* with an entry in a file size golden. If the size deviates by certain
4+
* amount, the script will fail with a non-zero exit code.
5+
*/
6+
7+
import chalk from 'chalk';
8+
import {readFileSync, statSync, writeFileSync} from 'fs';
9+
10+
/**
11+
* Absolute byte deviation from the expected value that is allowed. If the
12+
* size deviates by 500 bytes of the expected value, the script will fail.
13+
*/
14+
const ABSOLUTE_BYTE_THRESHOLD = 500;
15+
/**
16+
* Percentage deviation from the expected value that is allowed. If the
17+
* size deviates by 1% of the expected value, the script will fail.
18+
*/
19+
const PERCENTAGE_DEVIATION_THRESHOLD = 1;
20+
21+
/**
22+
* Extracted command line arguments specified by the Bazel NodeJS binaries:
23+
* - `testId`: Unique id for the given test file that is used as key in the golden.
24+
* - `testFileRootpath`: Bazel rootpath that resolves to the test file that should be measured.
25+
* - `isApprove`: Whether the script runs in approve mode, and the golden should be updated
26+
* with the actual measured size.
27+
*/
28+
const [testId, testFileRootpath, isApprove] = process.argv.slice(2);
29+
const testFilePath = require.resolve(`angular_material/${testFileRootpath}`);
30+
const goldenFilePath = require.resolve('../../goldens/size-test.json');
31+
32+
const golden = JSON.parse(readFileSync(goldenFilePath, 'utf8'));
33+
const fileStat = statSync(testFilePath);
34+
const actualSize = fileStat.size;
35+
36+
// If in approve mode, update the golden to reflect the new size.
37+
if (isApprove) {
38+
golden[testId] = actualSize;
39+
writeFileSync(goldenFilePath, JSON.stringify(golden, null, 2));
40+
console.info(chalk.green(`Approved golden size for "${testId}"`));
41+
process.exit(0);
42+
}
43+
44+
// If no size has been collected for the test id, report an error.
45+
if (golden[testId] === undefined) {
46+
console.error(`No golden size for "${testId}". Please create a new entry.`);
47+
printApproveCommand();
48+
process.exit(1);
49+
}
50+
51+
const expectedSize = Number(golden[testId]);
52+
const absoluteSizeDiff = Math.abs(actualSize - expectedSize);
53+
const deviatedByPercentage =
54+
absoluteSizeDiff > (expectedSize * PERCENTAGE_DEVIATION_THRESHOLD / 100);
55+
const deviatedByAbsoluteDiff = absoluteSizeDiff > ABSOLUTE_BYTE_THRESHOLD;
56+
57+
// Always print the expected and actual size so that it's easier to find culprit
58+
// commits when the size slowly moves toward the threshold boundaries.
59+
console.info(chalk.yellow(`Expected: ${expectedSize}, but got: ${actualSize}`));
60+
61+
if (deviatedByPercentage) {
62+
console.error(`Actual size deviates by more than 1% of the expected size. `);
63+
printApproveCommand();
64+
process.exit(1);
65+
} else if (deviatedByAbsoluteDiff) {
66+
console.error(`Actual size deviates by more than 500 bytes from the expected.`);
67+
printApproveCommand();
68+
process.exit(1);
69+
}
70+
71+
/** Prints the command for approving the current test. */
72+
function printApproveCommand() {
73+
console.info(chalk.yellow('You can approve the golden by running the following command:'));
74+
console.info(chalk.yellow(` bazel run ${process.env.BAZEL_TARGET}.approve`));
75+
}

integration/size-test/index.bzl

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary", "nodejs_test")
2+
load("@npm_bazel_rollup//:index.bzl", "rollup_bundle")
3+
load("@npm_bazel_terser//:index.bzl", "terser_minified")
4+
load("//tools:defaults.bzl", "ng_module")
5+
6+
"""
7+
Performs size measurements for the specified file. The file will be built as part
8+
of a `ng_module` and then will be optimized with build-optimizer, rollup and Terser.
9+
10+
The resulting size will be validated against a golden file to ensure that we don't
11+
regress in payload size, or that we can improvements to payload size.
12+
"""
13+
14+
def size_test(name, file, deps):
15+
# Generates an unique id for the given size test. e.g. if the macro is called
16+
# within the `integration/size-test/material` package with `name = list`, then
17+
# the unique id will be set to `material/list`.
18+
test_id = "%s/%s" % (native.package_name()[len("integration/size-test/"):], name)
19+
20+
ng_module(
21+
name = "%s_lib" % name,
22+
srcs = ["%s.ts" % name],
23+
testonly = True,
24+
deps = [
25+
"@npm//@angular/core",
26+
"@npm//@angular/platform-browser-dynamic",
27+
] + deps,
28+
)
29+
30+
rollup_bundle(
31+
name = "%s_bundle" % name,
32+
config_file = "//integration/size-test:rollup.config.js",
33+
testonly = True,
34+
entry_points = {
35+
(file): "%s_bundled" % name,
36+
},
37+
deps = [
38+
":%s_lib" % name,
39+
"@npm//rollup-plugin-node-resolve",
40+
"@npm//@angular-devkit/build-optimizer",
41+
],
42+
sourcemap = "false",
43+
)
44+
45+
terser_minified(
46+
testonly = True,
47+
name = "%s_bundle_min" % name,
48+
src = ":%s_bundle" % name,
49+
config_file = "//integration/size-test:terser-config.json",
50+
sourcemap = False,
51+
)
52+
53+
nodejs_test(
54+
name = name,
55+
data = [
56+
"//goldens:size-test.json",
57+
"//integration/size-test:check-size",
58+
":%s_bundle_min" % name,
59+
],
60+
entry_point = "//integration/size-test:check-size.ts",
61+
args = [test_id, "$(rootpath :%s_bundle_min)" % name],
62+
)
63+
64+
nodejs_binary(
65+
name = "%s.approve" % name,
66+
testonly = True,
67+
data = [
68+
"//goldens:size-test.json",
69+
"//integration/size-test:check-size",
70+
":%s_bundle_min" % name,
71+
],
72+
entry_point = "//integration/size-test:check-size.ts",
73+
args = [test_id, "$(rootpath :%s_bundle_min)" % name, "true"],
74+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
load("//integration/size-test:index.bzl", "size_test")
2+
3+
size_test(
4+
name = "list",
5+
file = "list.ts",
6+
deps = ["//src/material/list"],
7+
)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import {Component, NgModule} from '@angular/core';
2+
import {platformBrowser} from '@angular/platform-browser';
3+
import {MatListModule} from '@angular/material/list';
4+
5+
/**
6+
* Basic component using `MatNavList` and `MatListItem`. Other parts of the list
7+
* module such as `MatList`, `MatSelectionList` or `MatListOption` are not used
8+
* and should be tree-shaken away.
9+
*/
10+
@Component({
11+
template: `
12+
<mat-nav-list>
13+
<mat-list-item>
14+
hello
15+
</mat-list-item>
16+
</mat-nav-list>
17+
`,
18+
})
19+
export class TestComponent {}
20+
21+
@NgModule({
22+
imports: [MatListModule],
23+
declarations: [TestComponent],
24+
bootstrap: [TestComponent],
25+
})
26+
export class AppModule {}
27+
28+
platformBrowser().bootstrapModule(AppModule);
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
const {buildOptimizer} = require('@angular-devkit/build-optimizer');
2+
const node = require('rollup-plugin-node-resolve');
3+
4+
const buildOptimizerPlugin = {
5+
name: 'build-optimizer',
6+
transform: (content, id) => {
7+
const {content: code, sourceMap: map} = buildOptimizer({
8+
content,
9+
inputFilePath: id,
10+
emitSourceMap: true,
11+
isSideEffectFree: true,
12+
isAngularCoreFile: false,
13+
});
14+
if (!code) {
15+
return null;
16+
}
17+
if (!map) {
18+
throw new Error('No sourcemap produced by build optimizer');
19+
}
20+
return {code, map};
21+
},
22+
};
23+
24+
module.exports = {
25+
plugins: [
26+
buildOptimizerPlugin,
27+
node({
28+
mainFields: ['es2015_ivy_ngcc', 'module_ivy_ngcc','es2015', 'module'],
29+
}),
30+
],
31+
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"output": {
3+
"ecma": "es2015",
4+
"comments": false
5+
},
6+
"compress": {
7+
"global_defs": {
8+
"ngDevMode": false,
9+
"ngI18nClosureMode": false,
10+
"ngJitMode": false
11+
},
12+
"passes": 3,
13+
"pure_getters": true
14+
},
15+
"mangle": true
16+
}

integration/size-test/tsconfig.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"experimentalDecorators": true,
4+
"lib": ["es2015"],
5+
"types": ["node"]
6+
}
7+
}

integration/ts-compat/BUILD.bazel

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ typescript_version_packages = [
4040
],
4141
entry_point = "test.js",
4242
tags = [
43-
"integration",
43+
# These tests run in view engine only as the release packages are
44+
# built with View Engine and we want to reproduce this here.
45+
"view-engine-only",
4446
],
4547
)
4648
for ts_pkg_name in typescript_version_packages

package.json

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,9 @@
4444
"ts-circular-deps:check": "yarn -s ts-circular-deps check --config ./src/circular-deps-test.conf.js",
4545
"ts-circular-deps:approve": "yarn -s ts-circular-deps approve --config ./src/circular-deps-test.conf.js",
4646
"merge": "ng-dev pr merge",
47-
"approve-api": "node ./scripts/approve-api-golden.js"
47+
"approve-api": "node ./scripts/approve-api-golden.js",
48+
"integration-tests": "bazel test //integration/... --test_tag_filters=-view-engine-only --build_tests_only",
49+
"integration-tests:view-engine": "bazel test //integration/... --test_tag_filters=view-engine-only --build_tests_only --config=view-engine"
4850
},
4951
"version": "10.0.0-rc.1",
5052
"dependencies": {
@@ -79,11 +81,12 @@
7981
"@bazel/bazelisk": "^1.4.0",
8082
"@bazel/buildifier": "^2.2.1",
8183
"@bazel/ibazel": "^0.13.0",
82-
"@bazel/jasmine": "^1.6.0",
83-
"@bazel/karma": "^1.6.0",
84-
"@bazel/protractor": "^1.6.0",
85-
"@bazel/terser": "^1.4.1",
86-
"@bazel/typescript": "^1.6.0",
84+
"@bazel/jasmine": "^1.7.0",
85+
"@bazel/karma": "^1.7.0",
86+
"@bazel/protractor": "^1.7.0",
87+
"@bazel/rollup": "^1.7.0",
88+
"@bazel/terser": "^1.7.0",
89+
"@bazel/typescript": "^1.7.0",
8790
"@firebase/app-types": "^0.3.2",
8891
"@octokit/rest": "16.28.7",
8992
"@schematics/angular": "^10.0.0-rc.2",

yarn.lock

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -400,37 +400,42 @@
400400
resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.13.0.tgz#2307bdf9ba0ad9835ee603b8fb521822bf3b8a11"
401401
integrity sha512-SFyS6oiibp8KW9BgAU+Tk0DhrH/sJx9om/OlB/Mg4hDEn8F8dj2QRqJE6CVhBCtLDcZDeD7WIAZKQDxKRYIShw==
402402

403-
"@bazel/jasmine@^1.6.0":
404-
version "1.6.0"
405-
resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-1.6.0.tgz#c469ab8725d9a2e48c0c3c965861ff8add9272ac"
406-
integrity sha512-WtOQDtIMHKTxlp0+FcdrADV6LMrpJV7eEGZippSNFPL5YhwwrPfCSOs5WkooatsrjL5YEszswzqQXFjvC7EZKQ==
403+
"@bazel/jasmine@^1.7.0":
404+
version "1.7.0"
405+
resolved "https://registry.yarnpkg.com/@bazel/jasmine/-/jasmine-1.7.0.tgz#429df76e6628aa139176340434729cc091e371d7"
406+
integrity sha512-LXq6nfBBEczjsDLwFW9kesGdewRrnFiAOZzXAAivCV3xtq516xK4QnVWA9tQGq+R1DnY50IaODpCJhh8PDezdg==
407407
dependencies:
408408
jasmine "~3.5.0"
409409
jasmine-core "~3.5.0"
410410
jasmine-reporters "~2.3.2"
411411
v8-coverage "1.0.9"
412412

413-
"@bazel/karma@^1.6.0":
414-
version "1.6.0"
415-
resolved "https://registry.yarnpkg.com/@bazel/karma/-/karma-1.6.0.tgz#98950b71114dd9ec169e6778a35d31ae1f578655"
416-
integrity sha512-9cX0E1SiMWwA70ZMFnMzeqSRn3biduGx03bGV77FSUYKocZpyfU2cOEygYGfxAqHnyM7x4cS8nflRv3+ZE0Aqg==
413+
"@bazel/karma@^1.7.0":
414+
version "1.7.0"
415+
resolved "https://registry.yarnpkg.com/@bazel/karma/-/karma-1.7.0.tgz#ec7e97a2629f5af0b2abe9a99ae30363a34af97d"
416+
integrity sha512-mGYVD9DldB3v/DjxJpS39X1vUD6M32Al96DMoilwW3TSAazcRWwUAC6HY9z5Wtyeqwxyk8BY1Mg1/berWpoTxg==
417417
dependencies:
418418
tmp "0.1.0"
419419

420-
"@bazel/protractor@^1.6.0":
421-
version "1.6.0"
422-
resolved "https://registry.yarnpkg.com/@bazel/protractor/-/protractor-1.6.0.tgz#cf095a1dbc038def7031c513a3b87f4e79bedb00"
423-
integrity sha512-gPiRv0oUJbVPpQ9nrwe5vjkffAc8VsYJhpTGgG+8aPdOaTLWgmBP/sy4BdfijU9O1Z/mNYojQCZgMzQz6kAvdg==
420+
"@bazel/protractor@^1.7.0":
421+
version "1.7.0"
422+
resolved "https://registry.yarnpkg.com/@bazel/protractor/-/protractor-1.7.0.tgz#1ced325a64d77bccca4bf881e62982d017d6b639"
423+
integrity sha512-sLbejWwmwTupCS3JKdBeiZMUbylLpJxJdlrz8sZ9t4KV6YiFAXNOloCScrrdOkeiJz5QQZRG3p3rqHbIszUAwQ==
424+
425+
"@bazel/rollup@^1.7.0":
426+
version "1.7.0"
427+
resolved "https://registry.yarnpkg.com/@bazel/rollup/-/rollup-1.7.0.tgz#5c0f0d51d2f3f14e78781a4b9e6a9ffba87f1579"
428+
integrity sha512-Pp5aCJw3gwu77zn6/fQgZ39ArrWEI5O3dja5wKadBnfOQ66PImIEr+bf7JgROoWvACH1kGxaS423rq51fiuCsA==
424429

425-
"@bazel/terser@^1.4.1":
430+
"@bazel/terser@^1.7.0":
426431
version "1.7.0"
427432
resolved "https://registry.yarnpkg.com/@bazel/terser/-/terser-1.7.0.tgz#c43e711e13b9a71c7abd3ade04fb4650d547ad01"
428433
integrity sha512-u/UXk0WUinvkk1g5xxfqGieBz3r12Bj2y2m25lC5GjHBgCpGk7DyeGGi9H3QQNO1Wmpw51QSE9gaPzKzjUVGug==
429434

430-
"@bazel/typescript@^1.6.0":
431-
version "1.6.0"
432-
resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-1.6.0.tgz#8dfd29e71bcf917d5f9cb67f19ac4dcfc9082439"
433-
integrity sha512-vAKuwy1Hgl+t3M3sH/G0oqHRYN35TdENj+0lsCI3x7EbSzyI6cbA3YQrLrlyvdScksqOpZa3PZ3UBGqfJJq2DA==
435+
"@bazel/typescript@^1.7.0":
436+
version "1.7.0"
437+
resolved "https://registry.yarnpkg.com/@bazel/typescript/-/typescript-1.7.0.tgz#8dc02b8a161f4fff3285186066b5f73666793452"
438+
integrity sha512-M6JPXJZ+W6457QZfPHmGg/Mejnp7//YTnffGmnmeK9vDqybXeCCRWW1/iEOwopLJYQViBHfaoulde0VXelx9sA==
434439
dependencies:
435440
protobufjs "6.8.8"
436441
semver "5.6.0"

0 commit comments

Comments
 (0)