Skip to content

Commit 7d53a6d

Browse files
authored
build: add test to ensure compatibility with ng-linker (#22351)
* build: add tests to ensure compatibility with ng-linker Adds tests to ensure that Angular components is compatible with Angular's patial compilation mode and the linker. A new job running every hour ensures that changes to the partial compilation or linker in Angular `master` are compatible with the components repository. Additionally, we add a job that runs for each commit/PR in order to insure that Angular components remains compatible with the linker of already-released Angular versions. * build: build and run e2e tests in partial compilation mode * fixup! build: build and run e2e tests in partial compilation mode Address feedback
1 parent 8ba7148 commit 7d53a6d

19 files changed

+714
-64
lines changed

.circleci/config.yml

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,8 @@ jobs:
197197

198198
# Exclude release and docs packages here as those will be built within
199199
# the "build_release_packages" and "publish_snapshots" jobs.
200-
- run: bazel build src/... --build_tag_filters=-docs-package,-release-package
200+
# TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available.
201+
- run: bazel build --build_tag_filters=-docs-package,-release-package -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF
201202
- *slack_notify_on_failure
202203

203204
# -----------------------------------
@@ -219,7 +220,8 @@ jobs:
219220

220221
# Exclude release and docs packages here as those will be built within
221222
# the "build_release_packages" and "publish_snapshots" jobs.
222-
- run: bazel build src/... --build_tag_filters=-docs-package,-release-package --config=view-engine
223+
# TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available.
224+
- run: bazel build --build_tag_filters=-docs-package,-release-package --config=view-engine -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF
223225
- *slack_notify_on_failure
224226

225227
# --------------------------------------------------------------------------------------------
@@ -281,7 +283,8 @@ jobs:
281283
- *yarn_install
282284
- *setup_bazel_binary
283285

284-
- run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only
286+
# TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available.
287+
- run: bazel test --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF
285288
- *slack_notify_on_failure
286289

287290
# ----------------------------------------------------------------------------
@@ -533,7 +536,8 @@ jobs:
533536
- *yarn_install_loose_lockfile
534537
- *setup_bazel_binary
535538

536-
- run: bazel test src/... --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only
539+
# TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available.
540+
- run: bazel test --build_tag_filters=-e2e --test_tag_filters=-e2e --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF
537541
- *slack_notify_on_failure
538542

539543
# ----------------------------------------------------------------------------
@@ -554,7 +558,8 @@ jobs:
554558
- *setup_bazel_binary
555559

556560
# Run project tests with NGC and View Engine.
557-
- run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only
561+
# TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available.
562+
- run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF
558563
- *slack_notify_on_failure
559564

560565
# ----------------------------------------------------------------------------
@@ -575,7 +580,8 @@ jobs:
575580
- *setup_bazel_binary
576581

577582
# Run project tests with NGC and View Engine.
578-
- run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only
583+
# TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available.
584+
- run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --config=view-engine --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF
579585
- *slack_notify_on_failure
580586

581587
# ----------------------------------------------------------------------------
@@ -593,6 +599,9 @@ jobs:
593599
- *setup_bazel_remote_execution
594600
- *yarn_install
595601
- *setup_bazel_binary
602+
603+
- run: yarn integration-tests:partial-ivy
604+
- run: yarn integration-tests:view-engine
596605
# TODO: Re-enable when there are integration tests that should run with Ivy.
597606
# Currently this command fails as there are no tests.
598607
# - run: yarn integration-tests
@@ -601,7 +610,26 @@ jobs:
601610
command: |
602611
# If the size integration tests fail, report the failure to a dedicated #components-ci-size-tracking Slack channel.
603612
yarn integration-tests:size-test || node ./scripts/circleci/notify-slack-job-failure.js components-ci-size-tracking
604-
- run: yarn integration-tests:view-engine
613+
- *slack_notify_on_failure
614+
615+
# ----------------------------------------------------------------------------
616+
# Job that runs all integration tests against Angular snapshot builds.
617+
# ----------------------------------------------------------------------------
618+
integration_tests_snapshot:
619+
<<: *job_defaults
620+
resource_class: xlarge
621+
environment:
622+
GCP_DECRYPT_TOKEN: *gcp_decrypt_token
623+
steps:
624+
- checkout_and_rebase
625+
- *restore_cache
626+
- *setup_bazel_ci_config
627+
- *setup_bazel_remote_execution
628+
- *setup_snapshot_builds
629+
- *yarn_install_loose_lockfile
630+
- *setup_bazel_binary
631+
632+
- run: yarn integration-tests:partial-ivy
605633
- *slack_notify_on_failure
606634

607635
# ----------------------------------------------------------------------------
@@ -626,7 +654,8 @@ jobs:
626654

627655
# Setup the components repository to use the MDC snapshot builds.
628656
# Run project tests with the MDC canary builds.
629-
- run: bazel test src/... --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --build_tests_only
657+
# TODO(devversion): remove target exclusion once https://github.com/bazelbuild/rules_nodejs/pull/2646 is available.
658+
- run: bazel test --build_tag_filters=-docs-package,-e2e --test_tag_filters=-e2e --build_tests_only -- src/... -//src/e2e-app:devserver_with_linked_declarations.MF
630659
- *slack_notify_on_failure
631660

632661
# ----------------------------------------------------------------------------------------
@@ -695,6 +724,8 @@ workflows:
695724
filters: *only_main_branch_filter
696725
- mdc_snapshot_test_cronjob:
697726
filters: *only_main_branch_filter
727+
- integration_tests_snapshot:
728+
filters: *only_main_branch_filter
698729

699730
triggers:
700731
- schedule:

WORKSPACE

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ http_archive(
2323
],
2424
)
2525

26+
# Add skylib which contains common Bazel utilities. Note that `rules_nodejs` would also
27+
# bring in the skylib repository but with an older version that does not support shorthands
28+
# for declaring Bazel build setting flags.
29+
http_archive(
30+
name = "bazel_skylib",
31+
sha256 = "ebdf850bfef28d923a2cc67ddca86355a449b5e4f38b0a70e584dc24e5984aa6",
32+
strip_prefix = "bazel-skylib-f80bc733d4b9f83d427ce3442be2e07427b2cc8d",
33+
urls = [
34+
"https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/archive/f80bc733d4b9f83d427ce3442be2e07427b2cc8d.tar.gz",
35+
"https://github.com/bazelbuild/bazel-skylib/archive/f80bc733d4b9f83d427ce3442be2e07427b2cc8d.tar.gz",
36+
],
37+
)
38+
39+
load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
40+
41+
bazel_skylib_workspace()
42+
2643
load("@build_bazel_rules_nodejs//:index.bzl", "check_bazel_version", "node_repositories", "yarn_install")
2744

2845
# The minimum bazel version to use with this repo is v3.1.0.

integration/BUILD.bazel

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
load("@build_bazel_rules_nodejs//:index.bzl", "js_library")
2+
3+
package(default_visibility = ["//visibility:public"])
4+
5+
# JavaScript library that exposes a script for retrieving all NPM packages
6+
# available in the runfiles of an action.
7+
js_library(
8+
name = "npm-packages-from-runfiles",
9+
srcs = ["npm-packages-from-runfiles.js"],
10+
)

integration/linker/BUILD.bazel

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test")
2+
3+
# Test which ensures that specified NPM packages can be transformed from their partial
4+
# declarations to definitions using the `@angular/compiler-cli` linker babel plugin.
5+
nodejs_test(
6+
name = "linker",
7+
data = [
8+
"//integration:npm-packages-from-runfiles",
9+
"//src/cdk:npm_package",
10+
"//src/cdk-experimental:npm_package",
11+
"//src/google-maps:npm_package",
12+
"//src/material:npm_package",
13+
"//src/material-experimental:npm_package",
14+
"//src/youtube-player:npm_package",
15+
"@npm//@angular/compiler-cli",
16+
"@npm//@babel/core",
17+
"@npm//@babel/traverse",
18+
"@npm//chalk",
19+
"@npm//glob",
20+
],
21+
entry_point = "link-packages-test.js",
22+
tags = ["partial-compilation-integration"],
23+
)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/**
2+
* Test that collects all partially built NPM packages and links their Angular
3+
* declarations to the corresponding definitions.
4+
*/
5+
6+
const {NodeJSFileSystem} = require('@angular/compiler-cli/src/ngtsc/file_system');
7+
const {ConsoleLogger, LogLevel} = require('@angular/compiler-cli/src/ngtsc/logging');
8+
const {createEs2015LinkerPlugin} = require('@angular/compiler-cli/linker/babel');
9+
const {getNpmPackagesFromRunfiles} = require('../npm-packages-from-runfiles');
10+
const {readFileSync} = require('fs');
11+
const {join} = require('path');
12+
const babel = require('@babel/core');
13+
const {default: traverse} = require('@babel/traverse');
14+
const glob = require('glob');
15+
const chalk = require('chalk');
16+
17+
/** File system used by the Angular linker plugin. */
18+
const fileSystem = new NodeJSFileSystem();
19+
/** Logger used by the Angular linker plugin. */
20+
const logger = new ConsoleLogger(LogLevel.info);
21+
/** List of NPM packages available in the Bazel runfiles. */
22+
const npmPackages = getNpmPackagesFromRunfiles();
23+
/** Whether any package could not be linked successfully. */
24+
let failedPackages = false;
25+
26+
// Iterate through all determined NPM packages and ensure that entry point
27+
// files can be processed successfully by the Angular linker.
28+
for (const pkg of npmPackages) {
29+
const {failures, passedFiles} = testPackage(pkg);
30+
31+
console.info(chalk.cyan(`------- Package: @angular/${pkg.name} -------`));
32+
console.info(`Passed files: ${passedFiles.length}`);
33+
console.info(`Failed files: ${failures.length}`);
34+
35+
if (failures.length > 0) {
36+
failures.forEach(({debugFileName, error}) => {
37+
console.error(` • ${chalk.yellow(debugFileName)}: ${error}`);
38+
});
39+
failedPackages = true;
40+
}
41+
42+
console.info('-------------------------------------');
43+
console.info();
44+
}
45+
46+
if (failedPackages) {
47+
console.error(chalk.red(`✘ Not all packages could be linked successfully. See errors above.`));
48+
// If there are failures, exit the process with a non-zero exit code. Bazel
49+
// uses exit code `3` to indicate non-fatal test failures.
50+
process.exitCode = 3;
51+
} else {
52+
console.info(chalk.green('✓ All packages have been successfully linked.'));
53+
}
54+
55+
/**
56+
* Tests the specified package against the Angular linker plugin.
57+
* @param pkg Package being tested.
58+
* @returns An object containing linker failures and passed files.
59+
*/
60+
function testPackage(pkg) {
61+
const entryPointFesmFiles = glob.sync(`fesm2015/**/*.js`, {cwd: pkg.pkgPath});
62+
const passedFiles = [];
63+
const failures = [];
64+
65+
// Iterate through each entry point and confirm that all partial declarations can be linked
66+
// to their corresponding Angular definitions without errors.
67+
for (const fesmFileName of entryPointFesmFiles) {
68+
const diskFilePath = join(pkg.pkgPath, fesmFileName);
69+
const debugFileName = join(pkg.name, fesmFileName);
70+
const fileContent = readFileSync(diskFilePath, 'utf8');
71+
const linkerPlugin = createEs2015LinkerPlugin({fileSystem, logger});
72+
73+
// Babel throws errors if the transformation fails. We catch these so that we
74+
// can print incompatible entry points with their errors at the end.
75+
try {
76+
const {ast} = babel.transformSync(fileContent, {
77+
ast: true,
78+
filename: diskFilePath,
79+
filenameRelative: debugFileName,
80+
plugins: [linkerPlugin]
81+
});
82+
83+
// Naively check if there are any Angular declarations left that haven't been linked.
84+
traverse(ast, {
85+
Identifier: (astPath) => {
86+
if (astPath.node.name.startsWith('ɵɵngDeclare')) {
87+
throw astPath.buildCodeFrameError(
88+
'Found Angular declaration that has not been linked.', Error);
89+
}
90+
}
91+
});
92+
93+
passedFiles.push(debugFileName);
94+
} catch (error) {
95+
failures.push({debugFileName, error});
96+
}
97+
}
98+
99+
return {passedFiles, failures}
100+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Collection of common logic for dealing with Bazel runfiles within
3+
* integration tests.
4+
*/
5+
6+
const {relative, sep, join} = require('path');
7+
const {readdirSync, readFileSync, existsSync} = require('fs');
8+
9+
/**
10+
* Gets all built Angular NPM package artifacts by querying the Bazel runfiles.
11+
* In case there is a runfiles manifest (e.g. on Windows), the packages are resolved
12+
* through the manifest because the runfiles are not symlinked and cannot be searched
13+
* within the real filesystem.
14+
* TODO: Simplify if Bazel on Windows uses runfile symlinking.
15+
*/
16+
exports.getNpmPackagesFromRunfiles = function() {
17+
// Path to the Bazel runfiles manifest if present. This file is present if runfiles are
18+
// not symlinked into the runfiles directory.
19+
const runfilesManifestPath = process.env.RUNFILES_MANIFEST_FILE;
20+
const workspacePath = 'angular_material/src';
21+
if (!runfilesManifestPath) {
22+
const packageRunfilesDir = join(process.env.RUNFILES, workspacePath);
23+
return readdirSync(packageRunfilesDir)
24+
.map(name => ({name, pkgPath: join(packageRunfilesDir, name, 'npm_package/')}))
25+
.filter(({pkgPath}) => existsSync(pkgPath));
26+
}
27+
const workspaceManifestPathRegex = new RegExp(`^${workspacePath}/[\\w-]+/npm_package$`);
28+
return readFileSync(runfilesManifestPath, 'utf8')
29+
.split('\n')
30+
.map(mapping => mapping.split(' '))
31+
.filter(([runfilePath]) => runfilePath.match(workspaceManifestPathRegex))
32+
.map(([runfilePath, realPath]) => ({
33+
name: relative(workspacePath, runfilePath).split(sep)[0],
34+
pkgPath: realPath,
35+
}));
36+
}

integration/ts-compat/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ typescript_version_packages = [
2525
"helpers.js",
2626
"test.js",
2727
":import-all-entry-points-file",
28+
"//integration:npm-packages-from-runfiles",
2829
"//src/cdk:npm_package",
2930
"//src/cdk-experimental:npm_package",
3031
"//src/google-maps:npm_package",

integration/ts-compat/helpers.js

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
const {relative, sep, join} = require('path');
2-
const {readdirSync, readFileSync, existsSync, unlinkSync} = require('fs');
1+
const {join} = require('path');
2+
const {unlinkSync} = require('fs');
33
const {set, ln, rm, mkdir} = require('shelljs');
44
const {fork} = require('child_process');
5+
const {getNpmPackagesFromRunfiles} = require('../npm-packages-from-runfiles');
56
const {runfiles} = require('@bazel/runfiles');
67

78
// Exit if any command fails.
@@ -57,30 +58,3 @@ exports.runTypeScriptCompatibilityTest = async (tscBinPath) => {
5758
});
5859
};
5960

60-
/**
61-
* Gets all built Angular NPM package artifacts by querying the Bazel runfiles.
62-
* In case there is a runfiles manifest (e.g. on Windows), the packages are resolved
63-
* through the manifest because the runfiles are not symlinked and cannot be searched
64-
* within the real filesystem. TODO: Remove if Bazel on Windows uses runfile symlinking.
65-
*/
66-
function getNpmPackagesFromRunfiles() {
67-
// Path to the Bazel runfiles manifest if present. This file is present if runfiles are
68-
// not symlinked into the runfiles directory.
69-
const runfilesManifestPath = process.env.RUNFILES_MANIFEST_FILE;
70-
const workspacePath = 'angular_material/src';
71-
if (!runfilesManifestPath) {
72-
const packageRunfilesDir = join(process.env.RUNFILES, workspacePath);
73-
return readdirSync(packageRunfilesDir)
74-
.map(name => ({name, pkgPath: join(packageRunfilesDir, name, 'npm_package/')}))
75-
.filter(({pkgPath}) => existsSync(pkgPath));
76-
}
77-
const workspaceManifestPathRegex = new RegExp(`^${workspacePath}/[\\w-]+/npm_package$`);
78-
return readFileSync(runfilesManifestPath, 'utf8')
79-
.split('\n')
80-
.map(mapping => mapping.split(' '))
81-
.filter(([runfilePath]) => runfilePath.match(workspaceManifestPathRegex))
82-
.map(([runfilePath, realPath]) => ({
83-
name: relative(workspacePath, runfilePath).split(sep)[0],
84-
pkgPath: realPath,
85-
}));
86-
}

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@
4343
"merge": "ng-dev pr merge",
4444
"approve-api": "node ./scripts/approve-api-golden.js",
4545
"approve-size-tests": "node ./scripts/approve-size-golden.js",
46-
"integration-tests": "bazel test --test_tag_filters=-view-engine-only --build_tests_only -- //integration/... -//integration/size-test/...",
46+
"integration-tests": "bazel test --test_tag_filters=-view-engine-only,-linker-integration-test --build_tests_only -- //integration/... -//integration/size-test/...",
4747
"integration-tests:view-engine": "bazel test --test_tag_filters=view-engine-only --build_tests_only -- //integration/... -//integration/size-test/...",
48+
"integration-tests:partial-ivy": "bazel test --//tools:partial_compilation=True --test_tag_filters=partial-compilation-integration --build_tests_only -- //integration/... //src/...",
4849
"integration-tests:size-test": "bazel test //integration/size-test/...",
4950
"check-mdc-tests": "ts-node --project scripts/tsconfig.json scripts/check-mdc-tests.ts",
5051
"check-mdc-exports": "ts-node --project scripts/tsconfig.json scripts/check-mdc-exports.ts",
@@ -80,6 +81,8 @@
8081
"@angular/platform-server": "^12.0.0",
8182
"@angular/router": "^12.0.0",
8283
"@axe-core/webdriverjs": "^4.1.0",
84+
"@babel/core": "^7.13.10",
85+
"@babel/traverse": "^7.13.0",
8386
"@bazel/bazelisk": "1.7.5",
8487
"@bazel/buildifier": "4.0.1",
8588
"@bazel/concatjs": "3.2.1",
@@ -139,6 +142,8 @@
139142
"@material/touch-target": "^12.0.0-canary.a23ecb682.0",
140143
"@material/typography": "^12.0.0-canary.a23ecb682.0",
141144
"@octokit/rest": "18.3.5",
145+
"@rollup/plugin-babel": "^5.3.0",
146+
"@rollup/plugin-commonjs": "^18.0.0",
142147
"@schematics/angular": "^12.0.0",
143148
"@types/autoprefixer": "^9.7.2",
144149
"@types/browser-sync": "^2.26.1",

0 commit comments

Comments
 (0)