From 794e33ae72c15a628f308260020bc4da608c4996 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Wed, 7 Sep 2022 17:56:13 -0700 Subject: [PATCH] test: run legacy-cli e2e tests via bazel --- .bazelrc | 4 + .circleci/dynamic_config.yml | 189 ++++++++++++++++-- .circleci/env.sh | 5 + .circleci/win-ram-disk.ps1 | 3 +- BUILD.bazel | 9 + WORKSPACE | 11 + docs/DEVELOPER.md | 2 +- packages/angular/cli/src/utilities/version.ts | 2 +- tests/legacy-cli/BUILD.bazel | 12 +- tests/legacy-cli/e2e.bzl | 158 +++++++++++++++ tests/legacy-cli/e2e/assets/BUILD.bazel | 8 +- .../e2e/initialize/500-create-project.ts | 13 +- tests/legacy-cli/e2e/initialize/BUILD.bazel | 2 +- tests/legacy-cli/e2e/ng-snapshot/BUILD.bazel | 7 +- .../e2e/setup/001-create-tmp-dir.ts | 2 +- tests/legacy-cli/e2e/setup/002-npm-sandbox.ts | 4 + tests/legacy-cli/e2e/tests/BUILD.bazel | 4 +- tests/legacy-cli/e2e/tests/basic/test.ts | 13 +- .../e2e/tests/build/esbuild-unsupported.ts | 6 + tests/legacy-cli/e2e/utils/BUILD.bazel | 5 +- tests/legacy-cli/e2e/utils/bazel.ts | 3 + tests/legacy-cli/e2e/utils/process.ts | 55 ++++- tests/legacy-cli/e2e/utils/project.ts | 153 +++++++++----- tests/legacy-cli/e2e/utils/utils.ts | 4 +- tests/legacy-cli/e2e_runner.ts | 95 +++++++-- tools/defaults.bzl | 2 +- 26 files changed, 652 insertions(+), 119 deletions(-) create mode 100644 tests/legacy-cli/e2e.bzl create mode 100644 tests/legacy-cli/e2e/utils/bazel.ts diff --git a/.bazelrc b/.bazelrc index e3fb14bdabf7..339e2d7fb22b 100644 --- a/.bazelrc +++ b/.bazelrc @@ -90,6 +90,10 @@ build:snapshot --workspace_status_command="yarn -s ng-dev release build-env-stam build:snapshot --stamp build:snapshot --//:enable_snapshot_repo_deps +build:e2e --workspace_status_command="yarn -s ng-dev release build-env-stamp --mode=release" +build:e2e --stamp +test:e2e --test_timeout=3600 + build:local --//:enable_package_json_tar_deps ############################### diff --git a/.circleci/dynamic_config.yml b/.circleci/dynamic_config.yml index 5dbea5ebedca..8074cad85793 100644 --- a/.circleci/dynamic_config.yml +++ b/.circleci/dynamic_config.yml @@ -48,7 +48,9 @@ var_6: &only_pull_requests only: - /pull\/\d+/ +# All e2e test suites var_7: &all_e2e_subsets ['npm', 'esbuild', 'yarn'] +var_8: &all_e2e_build_types ['e2e', 'snapshot'] # Executor Definitions # https://circleci.com/docs/2.0/reusing-config/#authoring-reusable-executors @@ -63,10 +65,20 @@ executors: working_directory: ~/ng resource_class: small + bazel-executor: + parameters: + nodeversion: + type: string + default: *default_nodeversion + docker: + - image: cimg/node:<< parameters.nodeversion >>-browsers + working_directory: ~/ng + resource_class: xlarge + windows-executor: # Same as https://circleci.com/orbs/registry/orb/circleci/windows, but named. working_directory: ~/ng - resource_class: windows.medium + resource_class: windows.large shell: powershell.exe -ExecutionPolicy Bypass machine: # Contents of this image: @@ -116,7 +128,7 @@ commands: - initialize_env - run: nvm install 16.13 - run: nvm use 16.13 - - run: npm install -g yarn@1.22.10 + - run: npm install -g yarn@1.22.10 @bazel/bazelisk@${BAZELISK_VERSION} - run: node --version - run: yarn --version @@ -126,6 +138,7 @@ commands: type: env_var_name default: CIRCLE_PROJECT_REPONAME steps: + - run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc - devinfra/setup-bazel-remote-exec: bazelrc: ./.bazelrc.user @@ -269,23 +282,24 @@ jobs: paths: - dist/_*.tgz - build-bazel-e2e: - executor: action-executor - resource_class: medium + bazel-build: + executor: bazel-executor steps: - custom_attach_workspace - - run: yarn bazel build //tests/legacy-cli/... + - setup_bazel_rbe + - run: + name: Bazel Build Packages + command: yarn bazel build //... + - fail_fast - unit-test: - executor: action-executor - resource_class: xlarge + bazel-test: + executor: bazel-executor parameters: nodeversion: type: string default: *default_nodeversion_major steps: - custom_attach_workspace - - browser-tools/install-chrome - setup_bazel_rbe - run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc - when: @@ -311,6 +325,59 @@ jobs: no_output_timeout: 40m - fail_fast + bazel-e2e-tests: + executor: bazel-executor + parallelism: 8 + parameters: + build_type: + type: enum + enum: *all_e2e_build_types + default: 'e2e' + subset: + type: enum + enum: *all_e2e_subsets + default: 'npm' + steps: + - custom_attach_workspace + - initialize_env + - setup_bazel_rbe + - run: mkdir /mnt/ramdisk/e2e + - run: + name: Test << parameters.build_type >> << parameters.subset >> + command: yarn bazel test --define=E2E_TEMP=/mnt/ramdisk/e2e --define=E2E_SHARD_TOTAL=${CIRCLE_NODE_TOTAL} --define=E2E_SHARD_INDEX=${CIRCLE_NODE_INDEX} --config=<< parameters.build_type >> //tests/legacy-cli:e2e.<< parameters.subset >> + no_output_timeout: 40m + - store_artifacts: + path: dist/testlogs/tests/legacy-cli/e2e.<< parameters.subset >> + - store_test_results: + path: dist/testlogs/tests/legacy-cli/e2e.<< parameters.subset >> + - fail_fast + + bazel-test-browsers: + executor: bazel-executor + steps: + - custom_attach_workspace + - initialize_env + - setup_bazel_rbe + - run: + name: Initialize Saucelabs + command: setSecretVar SAUCE_ACCESS_KEY $(echo $SAUCE_ACCESS_KEY | rev) + - run: + name: Start Saucelabs Tunnel + command: ./scripts/saucelabs/start-tunnel.sh + background: true + # Waits for the Saucelabs tunnel to be ready. This ensures that we don't run tests + # too early without Saucelabs not being ready. + - run: ./scripts/saucelabs/wait-for-tunnel.sh + - run: + name: E2E Saucelabs Tests + command: yarn bazel test --config=saucelabs //tests/legacy-cli:e2e.saucelabs + - run: ./scripts/saucelabs/stop-tunnel.sh + - store_artifacts: + path: dist/testlogs/tests/legacy-cli/e2e.saucelabs + - store_test_results: + path: dist/testlogs/tests/legacy-cli/e2e.saucelabs + - fail_fast + snapshot_publish: executor: action-executor resource_class: medium @@ -382,6 +449,48 @@ jobs: node tests\legacy-cli\run_e2e.js --nb-shards=$env:CIRCLE_NODE_TOTAL --shard=$env:CIRCLE_NODE_INDEX --tmpdir=X:/ramdisk/e2e-main --ignore="tests/misc/browsers.ts" - fail_fast + bazel-e2e-cli-win: + executor: windows-executor + parallelism: 12 + steps: + - checkout + - rebase_pr_win + - setup_windows + - restore_cache: + keys: + - *cache_key_win + - run: + # We use Arsenal Image Mounter (AIM) instead of ImDisk because of: https://github.com/nodejs/node/issues/6861 + # Useful resources for AIM: http://reboot.pro/index.php?showtopic=22068 + name: 'Arsenal Image Mounter (RAM Disk)' + command: | + pwsh ./.circleci/win-ram-disk.ps1 + - run: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn + - save_cache: + key: *cache_key_win + paths: + - ~/.cache/yarn + # Path where Arsenal Image Mounter files are downloaded. + # Must match path in .circleci/win-ram-disk.ps1 + - ./aim + - run: + name: Execute E2E Tests + environment: + # Required by `yarn ng-dev` + # See https://github.com/angular/angular/issues/46858 + PWD: . + command: | + mkdir X:/ramdisk/e2e + bazel test --define=E2E_TEMP=X:/ramdisk/e2e --define=E2E_SHARD_TOTAL=$env:CIRCLE_NODE_TOTAL --define=E2E_SHARD_INDEX=$env:CIRCLE_NODE_INDEX --config=e2e //tests/legacy-cli:e2e.npm + # This timeout provides time for the actual tests to timeout and report status + # instead of CircleCI stopping the job without test failure information. + no_output_timeout: 40m + - fail_fast + - store_artifacts: + path: dist/testlogs/tests/legacy-cli/e2e.npm + - store_test_results: + path: dist/testlogs/tests/legacy-cli/e2e.npm + workflows: version: 2 default_workflow: @@ -457,23 +566,67 @@ workflows: # These jobs only really depend on Setup, but the build job is very quick to run (~35s) and # will catch any build errors before proceeding to the more lengthy and resource intensive # Bazel jobs. - - unit-test: + - bazel-test: name: test-node<< matrix.nodeversion >> matrix: parameters: nodeversion: *all_nodeversion_major requires: - - build - - # Compile the e2e tests with bazel to ensure the non-runtime typescript - # compilation completes succesfully. - - build-bazel-e2e: - requires: - - build + - bazel-build # Windows jobs - e2e-cli-win + - bazel-e2e-cli-win + + # Bazel jobs + - bazel-build: + requires: + - setup + + - bazel-e2e-tests: + name: bazel-e2e-cli-<< matrix.subset >> + matrix: + parameters: + subset: *all_e2e_subsets + build_type: 'e2e' + filters: + branches: + ignore: + - main + - /\d+\.\d+\.x/ + requires: + - bazel-build + + - bazel-e2e-tests: + name: bazel-e2e-snapshots-<< matrix.subset >> + matrix: + parameters: + subset: *all_e2e_subsets + build_type: 'snapshot' + pre-steps: + - when: + condition: + and: + - not: + equal: [main, << pipeline.git.branch >>] + - not: << pipeline.parameters.snapshot_changed >> + steps: + # Don't run snapshot E2E's unless it's on the main branch or the snapshots file has been updated. + - run: circleci-agent step halt + requires: + - bazel-build + filters: + branches: + only: + - main + # This is needed to run this steps on Renovate PRs that amend the snapshots package.json + - /^pull\/.*/ + + - bazel-test-browsers: + requires: + - bazel-build + # Publish jobs - snapshot_publish: <<: *only_release_branches diff --git a/.circleci/env.sh b/.circleci/env.sh index 6ec09ef85153..e6ae354a6a7c 100755 --- a/.circleci/env.sh +++ b/.circleci/env.sh @@ -36,3 +36,8 @@ source $BASH_ENV; # Disable husky. setPublicVar HUSKY 0 + +# Expose the Bazelisk version. We need to run Bazelisk globally since Windows has problems launching +# Bazel from a node modules directoy that might be modified by the Bazel Yarn install then. +setPublicVar BAZELISK_VERSION \ + "$(cd ${PROJECT_ROOT}; node -p 'require("./package.json").devDependencies["@bazel/bazelisk"]')" \ No newline at end of file diff --git a/.circleci/win-ram-disk.ps1 b/.circleci/win-ram-disk.ps1 index 5d16d8b8a11d..a73bdcdb06b7 100644 --- a/.circleci/win-ram-disk.ps1 +++ b/.circleci/win-ram-disk.ps1 @@ -26,5 +26,6 @@ if (-not (Test-Path -Path $aimContents)) { ./aim/cli/x64/aim_ll.exe --install ./aim/drivers # Setup RAM disk mount. Same parameters as ImDisk +# Ensure size is large enough to support the bazel 'shard_count's such as for e2e tests. # See: https://support.circleci.com/hc/en-us/articles/4411520952091-Create-a-windows-RAM-disk -./aim/cli/x64/aim_ll.exe -a -s 5G -m X: -p "/fs:ntfs /q /y" +./aim/cli/x64/aim_ll.exe -a -s 12G -m X: -p "/fs:ntfs /q /y" diff --git a/BUILD.bazel b/BUILD.bazel index 3fc46c3f3b32..b498ed4a3e94 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,6 +3,7 @@ # Use of this source code is governed by an MIT-style license that can be # found in the LICENSE file at https://angular.io/license load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") +load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") package(default_visibility = ["//visibility:public"]) @@ -16,6 +17,14 @@ exports_files([ "package.json", ]) +# Files required by e2e tests +copy_to_bin( + name = "config-files", + srcs = [ + "package.json", + ], +) + # Detect if the build is running under --stamp config_setting( name = "stamp", diff --git a/WORKSPACE b/WORKSPACE index af01de409336..fcb463c04996 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -98,3 +98,14 @@ nodejs_register_toolchains( name = "node16", node_version = "16.13.1", ) + +register_toolchains( + "@npm//@angular/build-tooling/bazel/git-toolchain:git_linux_toolchain", + "@npm//@angular/build-tooling/bazel/git-toolchain:git_macos_x86_toolchain", + "@npm//@angular/build-tooling/bazel/git-toolchain:git_macos_arm64_toolchain", + "@npm//@angular/build-tooling/bazel/git-toolchain:git_windows_toolchain", +) + +load("@npm//@angular/build-tooling/bazel/browsers:browser_repositories.bzl", "browser_repositories") + +browser_repositories() diff --git a/docs/DEVELOPER.md b/docs/DEVELOPER.md index cc06b685550c..fef7faa06eae 100644 --- a/docs/DEVELOPER.md +++ b/docs/DEVELOPER.md @@ -85,7 +85,7 @@ You can find more info about debugging [tests with Bazel in the docs.](https://g - Compile the packages being tested: `yarn build` - Run all tests: `node tests/legacy-cli/run_e2e.js` - Run a subset of the tests: `node tests/legacy-cli/run_e2e.js tests/legacy-cli/e2e/tests/i18n/ivy-localize-*` -- Run on a custom set of npm packages (tar files): `node tests/legacy-cli/run_e2e.js --package _angular_cli.tgz _angular_create.tgz dist/*.tgz ...` +- Run on a custom set of npm packages (tar files): `node tests/legacy-cli/run_e2e.js --package _angular_cli.tgz _angular_create.tgz dist/*.tgz tests/legacy-cli/e2e/tests/i18n/ivy-localize-*` When running the debug commands, Node will stop and wait for a debugger to attach. You can attach your IDE to the debugger to stop on breakpoints and step through the code. Also, see [IDE Specific Usage](#ide-specific-usage) for a diff --git a/packages/angular/cli/src/utilities/version.ts b/packages/angular/cli/src/utilities/version.ts index 2c9db37d69a9..777c3de165f6 100644 --- a/packages/angular/cli/src/utilities/version.ts +++ b/packages/angular/cli/src/utilities/version.ts @@ -23,7 +23,7 @@ class Version { } } -// TODO: Convert this to use build-time version stamping after flipping the build script to use bazel +// TODO(bazel): Convert this to use build-time version stamping after flipping the build script to use bazel // export const VERSION = new Version('0.0.0-PLACEHOLDER'); export const VERSION = new Version( ( diff --git a/tests/legacy-cli/BUILD.bazel b/tests/legacy-cli/BUILD.bazel index cbd1ea4fc4ca..00be22b4442e 100644 --- a/tests/legacy-cli/BUILD.bazel +++ b/tests/legacy-cli/BUILD.bazel @@ -1,4 +1,5 @@ load("//tools:defaults.bzl", "ts_library") +load(":e2e.bzl", "e2e_suites") ts_library( name = "runner", @@ -11,16 +12,25 @@ ts_library( deps = [ "//packages/angular_devkit/core", "//packages/angular_devkit/core/node", - "//tests/legacy-cli/e2e/assets", "//tests/legacy-cli/e2e/utils", "@npm//@types/glob", "@npm//@types/yargs-parser", "@npm//ansi-colors", "@npm//yargs-parser", + ], +) +e2e_suites( + name = "e2e", + data = [ + ":runner", + + # Tests + setup # Loaded dynamically at runtime, not compiletime deps + "//tests/legacy-cli/e2e/assets", "//tests/legacy-cli/e2e/setup", "//tests/legacy-cli/e2e/initialize", "//tests/legacy-cli/e2e/tests", ], + runner = ":e2e_runner.ts", ) diff --git a/tests/legacy-cli/e2e.bzl b/tests/legacy-cli/e2e.bzl new file mode 100644 index 000000000000..9df4beca88f0 --- /dev/null +++ b/tests/legacy-cli/e2e.bzl @@ -0,0 +1,158 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") + +# bazel query --output=label "kind('pkg_tar', //packages/...)" +TESTED_PACKAGES = [ + "//packages/angular/cli:npm_package_archive.tgz", + "//packages/angular/create:npm_package_archive.tgz", + "//packages/angular/pwa:npm_package_archive.tgz", + "//packages/angular_devkit/architect:npm_package_archive.tgz", + "//packages/angular_devkit/architect_cli:npm_package_archive.tgz", + # this is private so don't use here + # "//packages/angular_devkit/benchmark:npm_package_archive.tgz", + "//packages/angular_devkit/build_angular:npm_package_archive.tgz", + "//packages/angular_devkit/build_webpack:npm_package_archive.tgz", + "//packages/angular_devkit/core:npm_package_archive.tgz", + "//packages/angular_devkit/schematics:npm_package_archive.tgz", + "//packages/angular_devkit/schematics_cli:npm_package_archive.tgz", + "//packages/ngtools/webpack:npm_package_archive.tgz", + "//packages/schematics/angular:npm_package_archive.tgz", +] + +# Number of bazel shards per test target +TEST_SHARD_COUNT = 4 + +# NB: does not run on rbe because webdriver manager uses an absolute path to chromedriver +# Requires network to fetch npm packages. +TEST_TAGS = ["no-remote-exec", "requires-network"] + +# Subset of tests for yarn/esbuild +BROWSER_TESTS = ["tests/misc/browsers.js"] +YARN_TESTS = ["tests/basic/**", "tests/update/**", "tests/commands/add/**"] +ESBUILD_TESTS = ["tests/basic/**", "tests/build/prod-build.js"] + +# Tests excluded for esbuild +ESBUILD_IGNORE_TESTS = [ + "tests/basic/environment.js", + "tests/basic/rebuild.js", + "tests/basic/serve.js", + "tests/basic/scripts-array.js", +] + +def _to_glob(patterns): + if len(patterns) == 1: + return patterns[0] + + return "\"{%s}\"" % ",".join(patterns) + +def e2e_suites(name, runner, data): + """ + Construct all e2e test suite targets + + Args: + name: the prefix to all rules + runner: the e2e test runner entry point + data: runtime deps such as tests and test data + """ + + # Default target meant to be run manually for debugging, customizing test cli via bazel + _e2e_tests(name, runner = runner, data = data, tags = ["manual"]) + + # Pre-configured test suites + # TODO: add node 14 + 16 + _e2e_suite(name, runner, "npm", data) + _e2e_suite(name, runner, "yarn", data) + _e2e_suite(name, runner, "esbuild", data) + _e2e_suite(name, runner, "saucelabs", data) + +def _e2e_tests(name, runner, **kwargs): + # Always specify all the npm packages + args = kwargs.pop("templated_args", []) + ["--package"] + [ + "$(rootpath %s)" % p + for p in TESTED_PACKAGES + ] + + # Always add all the npm packages as data + data = kwargs.pop("data", []) + TESTED_PACKAGES + + # Tags that must always be applied + tags = kwargs.pop("tags", []) + TEST_TAGS + + # Passthru E2E variables in case it is customized by CI etc + configuration_env_vars = kwargs.pop("configuration_env_vars", []) + ["E2E_TEMP", "E2E_SHARD_INDEX", "E2E_SHARD_TOTAL"] + + env = kwargs.pop("env", {}) + toolchains = kwargs.pop("toolchains", []) + + # The git toolchain + env + env.update({"GIT_BIN": "$(GIT_BIN_PATH)"}) + toolchains = toolchains + ["@npm//@angular/build-tooling/bazel/git-toolchain:current_git_toolchain"] + + # Chromium browser toolchain + env.update({ + "CHROME_BIN": "$(CHROMIUM)", + "CHROMEDRIVER_BIN": "$(CHROMEDRIVER)", + }) + toolchains = toolchains + ["@npm//@angular/build-tooling/bazel/browsers/chromium:toolchain_alias"] + data = data + ["@npm//@angular/build-tooling/bazel/browsers/chromium"] + + nodejs_test( + name = name, + templated_args = args, + data = data, + entry_point = runner, + env = env, + configuration_env_vars = configuration_env_vars, + tags = tags, + toolchains = toolchains, + **kwargs + ) + +def _e2e_suite(name, runner, type, data): + """ + Setup a predefined test suite (yarn|esbuild|saucelabs|npm). + """ + args = [] + tests = None + ignore = None + + if type == "yarn": + args.append("--yarn") + tests = YARN_TESTS + ignore = BROWSER_TESTS + elif type == "esbuild": + args.append("--esbuild") + tests = ESBUILD_TESTS + ignore = BROWSER_TESTS + ESBUILD_IGNORE_TESTS + elif type == "saucelabs": + tests = BROWSER_TESTS + ignore = None + elif type == "npm": + tests = None + ignore = BROWSER_TESTS + + # Standard e2e tests + _e2e_tests( + name = "%s.%s" % (name, type), + runner = runner, + size = "enormous", + data = data, + shard_count = TEST_SHARD_COUNT, + templated_args = [ + "--glob=%s" % _to_glob(tests) if tests else "", + "--ignore=%s" % _to_glob(ignore) if ignore else "", + ], + ) + + # e2e tests of snapshot builds + _e2e_tests( + name = "%s.snapshot.%s" % (name, type), + runner = runner, + size = "enormous", + data = data, + shard_count = TEST_SHARD_COUNT, + templated_args = [ + "--ng-snapshots", + "--glob=%s" % _to_glob(tests) if tests else "", + "--ignore=%s" % _to_glob(ignore) if ignore else "", + ], + ) diff --git a/tests/legacy-cli/e2e/assets/BUILD.bazel b/tests/legacy-cli/e2e/assets/BUILD.bazel index c5067a937896..a2889b56559f 100644 --- a/tests/legacy-cli/e2e/assets/BUILD.bazel +++ b/tests/legacy-cli/e2e/assets/BUILD.bazel @@ -1,11 +1,7 @@ -load("//tools:defaults.bzl", "js_library") +load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") -js_library( +copy_to_bin( name = "assets", srcs = glob(["**"]), visibility = ["//visibility:public"], - deps = [ - "@npm//jasmine-spec-reporter", - "@npm//ts-node", - ], ) diff --git a/tests/legacy-cli/e2e/initialize/500-create-project.ts b/tests/legacy-cli/e2e/initialize/500-create-project.ts index d53bae11acea..b5ce6bd074d0 100644 --- a/tests/legacy-cli/e2e/initialize/500-create-project.ts +++ b/tests/legacy-cli/e2e/initialize/500-create-project.ts @@ -1,5 +1,6 @@ import { join } from 'path'; import yargsParser from 'yargs-parser'; +import { IS_BAZEL } from '../utils/bazel'; import { getGlobalVariable } from '../utils/env'; import { expectFileToExist } from '../utils/fs'; import { gitClean } from '../utils/git'; @@ -23,11 +24,13 @@ export default async function () { // Install puppeteer in the parent directory for use by the CLI within any test project. // Align the version with the primary project package.json. - const puppeteerVersion = require('../../../../package.json').devDependencies.puppeteer.replace( - /^[\^~]/, - '', - ); - await installPackage(`puppeteer@${puppeteerVersion}`); + // Bazel has own browser toolchains + // TODO(bazel): remove non-bazel + if (!IS_BAZEL) { + const puppeteerVersion = + require('../../../../package.json').devDependencies.puppeteer.replace(/^[\^~]/, ''); + await installPackage(`puppeteer@${puppeteerVersion}`); + } await ng('new', 'test-project', '--skip-install'); await expectFileToExist(join(process.cwd(), 'test-project')); diff --git a/tests/legacy-cli/e2e/initialize/BUILD.bazel b/tests/legacy-cli/e2e/initialize/BUILD.bazel index 00735969e9ab..5beccd6620af 100644 --- a/tests/legacy-cli/e2e/initialize/BUILD.bazel +++ b/tests/legacy-cli/e2e/initialize/BUILD.bazel @@ -5,7 +5,7 @@ ts_library( testonly = True, srcs = glob(["**/*.ts"]), data = [ - "//:package.json", + "//:config-files", ], visibility = ["//visibility:public"], deps = [ diff --git a/tests/legacy-cli/e2e/ng-snapshot/BUILD.bazel b/tests/legacy-cli/e2e/ng-snapshot/BUILD.bazel index 5a929766ca6f..079ffeb1accc 100644 --- a/tests/legacy-cli/e2e/ng-snapshot/BUILD.bazel +++ b/tests/legacy-cli/e2e/ng-snapshot/BUILD.bazel @@ -1,8 +1,7 @@ -load("//tools:defaults.bzl", "ts_library") +load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin") -ts_library( +copy_to_bin( name = "ng-snapshot", - srcs = [], - data = ["package.json"], + srcs = ["package.json"], visibility = ["//visibility:public"], ) diff --git a/tests/legacy-cli/e2e/setup/001-create-tmp-dir.ts b/tests/legacy-cli/e2e/setup/001-create-tmp-dir.ts index 07c5855a8394..29c403be85de 100644 --- a/tests/legacy-cli/e2e/setup/001-create-tmp-dir.ts +++ b/tests/legacy-cli/e2e/setup/001-create-tmp-dir.ts @@ -12,7 +12,7 @@ export default async function () { } else if (argv.tmpdir) { tempRoot = argv.tmpdir; } else { - tempRoot = await mktempd('angular-cli-e2e-'); + tempRoot = await mktempd('angular-cli-e2e-', process.env.E2E_TEMP); } console.log(` Using "${tempRoot}" as temporary directory for a new project.`); setGlobalVariable('tmp-root', tempRoot); diff --git a/tests/legacy-cli/e2e/setup/002-npm-sandbox.ts b/tests/legacy-cli/e2e/setup/002-npm-sandbox.ts index 295a89ec1df0..98d7f04dda21 100644 --- a/tests/legacy-cli/e2e/setup/002-npm-sandbox.ts +++ b/tests/legacy-cli/e2e/setup/002-npm-sandbox.ts @@ -24,6 +24,10 @@ export default async function () { process.env.NPM_CONFIG_PREFIX = npmModulesPrefix; process.env.YARN_CONFIG_PREFIX = yarnModulesPrefix; + // Put the npm+yarn caches in the temp dir + process.env.NPM_CONFIG_CACHE = join(tempRoot, 'npm-cache'); + process.env.YARN_CACHE_FOLDER = join(tempRoot, 'yarn-cache'); + // Snapshot builds may contain versions that are not yet released (e.g., RC phase main branch). // In this case peer dependency ranges may not resolve causing npm 7+ to fail during tests. // To support this case, legacy peer dependency mode is enabled for snapshot builds. diff --git a/tests/legacy-cli/e2e/tests/BUILD.bazel b/tests/legacy-cli/e2e/tests/BUILD.bazel index 4f01d7fc3887..308544663f00 100644 --- a/tests/legacy-cli/e2e/tests/BUILD.bazel +++ b/tests/legacy-cli/e2e/tests/BUILD.bazel @@ -4,9 +4,11 @@ ts_library( name = "tests", testonly = True, srcs = glob(["**/*.ts"]), + data = [ + "//tests/legacy-cli/e2e/ng-snapshot", + ], visibility = ["//visibility:public"], deps = [ - "//tests/legacy-cli/e2e/ng-snapshot", "//tests/legacy-cli/e2e/utils", "@npm//@types/express", "@npm//@types/glob", diff --git a/tests/legacy-cli/e2e/tests/basic/test.ts b/tests/legacy-cli/e2e/tests/basic/test.ts index 3c0c2d99ee68..d9066946ae8e 100644 --- a/tests/legacy-cli/e2e/tests/basic/test.ts +++ b/tests/legacy-cli/e2e/tests/basic/test.ts @@ -29,7 +29,18 @@ export default async function () { colors: true, logLevel: config.LOG_INFO, autoWatch: true, - browsers: ['ChromeHeadless'], + browsers: ['ChromeHeadlessNoSandbox'], + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: 'ChromeHeadless', + flags: [ + '--no-sandbox', + '--headless', + '--disable-gpu', + '--disable-dev-shm-usage', + ], + } + }, singleRun: false, restartOnFileChange: true }); diff --git a/tests/legacy-cli/e2e/tests/build/esbuild-unsupported.ts b/tests/legacy-cli/e2e/tests/build/esbuild-unsupported.ts index 0a3681549d3d..b2dab3bbac07 100644 --- a/tests/legacy-cli/e2e/tests/build/esbuild-unsupported.ts +++ b/tests/legacy-cli/e2e/tests/build/esbuild-unsupported.ts @@ -1,7 +1,13 @@ import { join } from 'path'; +import { IS_BAZEL } from '../../utils/bazel'; import { execWithEnv } from '../../utils/process'; export default async function () { + // TODO(bazel): fails with bazel on windows + if (IS_BAZEL && process.platform.startsWith('win')) { + return; + } + // Set the esbuild native binary path to a non-existent file to simulate a spawn error. // The build should still succeed by falling back to the WASM variant of esbuild. await execWithEnv('ng', ['build'], { diff --git a/tests/legacy-cli/e2e/utils/BUILD.bazel b/tests/legacy-cli/e2e/utils/BUILD.bazel index 7a242b4bd137..68301d92bb69 100644 --- a/tests/legacy-cli/e2e/utils/BUILD.bazel +++ b/tests/legacy-cli/e2e/utils/BUILD.bazel @@ -4,9 +4,11 @@ ts_library( name = "utils", testonly = True, srcs = glob(["**/*.ts"]), + data = [ + "//tests/legacy-cli/e2e/ng-snapshot", + ], visibility = ["//visibility:public"], deps = [ - "//tests/legacy-cli/e2e/ng-snapshot", "@npm//@types/glob", "@npm//@types/node-fetch", "@npm//@types/semver", @@ -16,7 +18,6 @@ ts_library( "@npm//glob", "@npm//npm", "@npm//protractor", - "@npm//puppeteer", "@npm//rxjs", "@npm//semver", "@npm//tar", diff --git a/tests/legacy-cli/e2e/utils/bazel.ts b/tests/legacy-cli/e2e/utils/bazel.ts new file mode 100644 index 000000000000..06a99f9023d5 --- /dev/null +++ b/tests/legacy-cli/e2e/utils/bazel.ts @@ -0,0 +1,3 @@ +// TODO(bazel): remove this along with any non-bazel specific logic using it. + +export const IS_BAZEL = !!process.env.BAZEL_TARGET; diff --git a/tests/legacy-cli/e2e/utils/process.ts b/tests/legacy-cli/e2e/utils/process.ts index 9ce14fb939f2..fa5f8afc3e2c 100644 --- a/tests/legacy-cli/e2e/utils/process.ts +++ b/tests/legacy-cli/e2e/utils/process.ts @@ -7,6 +7,7 @@ import { getGlobalVariable, getGlobalVariablesEnv } from './env'; import { catchError } from 'rxjs/operators'; import treeKill from 'tree-kill'; import { delimiter, join, resolve } from 'path'; +import { IS_BAZEL } from './bazel'; interface ExecOptions { silent?: boolean; @@ -167,7 +168,23 @@ export function extractNpmEnv() { function extractCIEnv(): NodeJS.ProcessEnv { return Object.keys(process.env) - .filter((v) => v.startsWith('SAUCE_') || v === 'CI' || v === 'CIRCLECI' || v === 'CHROME_BIN') + .filter( + (v) => + v.startsWith('SAUCE_') || + v === 'CI' || + v === 'CIRCLECI' || + v === 'CHROME_BIN' || + v === 'CHROMEDRIVER_BIN', + ) + .reduce((vars, n) => { + vars[n] = process.env[n]; + return vars; + }, {}); +} + +function extractNgEnv() { + return Object.keys(process.env) + .filter((v) => v.startsWith('NG_')) .reduce((vars, n) => { vars[n] = process.env[n]; return vars; @@ -357,11 +374,11 @@ export function node(...args: string[]) { } export function git(...args: string[]) { - return _exec({}, 'git', args); + return _exec({}, process.env.GIT_BIN || 'git', args); } export function silentGit(...args: string[]) { - return _exec({ silent: true }, 'git', args); + return _exec({ silent: true }, process.env.GIT_BIN || 'git', args); } /** @@ -372,24 +389,42 @@ export function silentGit(...args: string[]) { * registry (not the test runner or standard global node_modules). */ export async function launchTestProcess(entry: string, ...args: any[]): Promise { + // NOTE: do NOT use the bazel TEST_TMPDIR. When sandboxing is not enabled the + // TEST_TMPDIR is not sandboxed and has symlinks into the src dir in a + // parent directory. Symlinks into the src dir will include package.json, + // .git and other files/folders that may effect e2e tests. + const tempRoot: string = getGlobalVariable('tmp-root'); + const TEMP = process.env.TEMP ?? process.env.TMPDIR ?? tempRoot; // Extract explicit environment variables for the test process. const env: NodeJS.ProcessEnv = { + TEMP, + TMPDIR: TEMP, + HOME: TEMP, + + // Use BAZEL_TARGET as a metadata variable to show it is a + // process managed by bazel + BAZEL_TARGET: process.env.BAZEL_TARGET, + ...extractNpmEnv(), ...extractCIEnv(), + ...extractNgEnv(), ...getGlobalVariablesEnv(), }; - // Modify the PATH environment variable... - env.PATH = (env.PATH || process.env.PATH) - ?.split(delimiter) - // Only include paths within the sandboxed test environment or external - // non angular-cli paths such as /usr/bin for generic commands. - .filter((p) => p.startsWith(tempRoot) || !p.includes('angular-cli')) + // Only include paths within the sandboxed test environment or external + // non angular-cli paths such as /usr/bin for generic commands. + env.PATH = process.env + .PATH!.split(delimiter) + .filter((p) => p.startsWith(tempRoot) || p.startsWith(TEMP) || !p.includes('angular-cli')) .join(delimiter); - const testProcessArgs = [resolve(__dirname, 'run_test_process'), entry, ...args]; + const testProcessArgs = [ + resolve(__dirname, IS_BAZEL ? 'test_process' : 'run_test_process'), + entry, + ...args, + ]; return new Promise((resolve, reject) => { spawn(process.execPath, testProcessArgs, { diff --git a/tests/legacy-cli/e2e/utils/project.ts b/tests/legacy-cli/e2e/utils/project.ts index 1f25eb78105f..791bb3e07f6a 100644 --- a/tests/legacy-cli/e2e/utils/project.ts +++ b/tests/legacy-cli/e2e/utils/project.ts @@ -2,8 +2,9 @@ import * as fs from 'fs'; import * as path from 'path'; import { prerelease, SemVer } from 'semver'; import yargsParser from 'yargs-parser'; +import { IS_BAZEL } from './bazel'; import { getGlobalVariable } from './env'; -import { prependToFile, readFile, replaceInFile, writeFile } from './fs'; +import { readFile, replaceInFile, writeFile } from './fs'; import { gitCommit } from './git'; import { findFreePort } from './network'; import { installWorkspacePackages, PkgInfo } from './packages'; @@ -50,37 +51,41 @@ export async function prepareProjectForE2e(name: string) { await installWorkspacePackages(); await ng('generate', 'e2e', '--related-app-name', name); - const protractorPath = require.resolve('protractor'); - const webdriverUpdatePath = require.resolve('webdriver-manager/selenium/update-config.json', { - paths: [protractorPath], - }); - const webdriverUpdate = JSON.parse(await readFile(webdriverUpdatePath)) as { - chrome: { last: string }; - }; + // bazel will use its own sandboxed browser + webdriver + // TODO(bazel): remove non-bazel + if (!IS_BAZEL) { + const protractorPath = require.resolve('protractor'); + const webdriverUpdatePath = require.resolve('webdriver-manager/selenium/update-config.json', { + paths: [protractorPath], + }); + const webdriverUpdate = JSON.parse(await readFile(webdriverUpdatePath)) as { + chrome: { last: string }; + }; - const chromeDriverVersion = webdriverUpdate.chrome.last.match(/chromedriver_([\d|\.]+)/)?.[1]; - if (!chromeDriverVersion) { - throw new Error('Could not extract chrome webdriver version.'); - } + const chromeDriverVersion = webdriverUpdate.chrome.last.match(/chromedriver_([\d|\.]+)/)?.[1]; + if (!chromeDriverVersion) { + throw new Error('Could not extract chrome webdriver version.'); + } - // Initialize selenium webdriver. - // Often fails the first time so attempt twice if necessary. - const runWebdriverUpdate = () => - exec( - process.execPath, - 'node_modules/protractor/bin/webdriver-manager', - 'update', - '--standalone', - 'false', - '--gecko', - 'false', - '--versions.chrome', - chromeDriverVersion, - ); - try { - await runWebdriverUpdate(); - } catch { - await runWebdriverUpdate(); + // Initialize selenium webdriver. + // Often fails the first time so attempt twice if necessary. + const runWebdriverUpdate = () => + exec( + process.execPath, + 'node_modules/protractor/bin/webdriver-manager', + 'update', + '--standalone', + 'false', + '--gecko', + 'false', + '--versions.chrome', + chromeDriverVersion, + ); + try { + await runWebdriverUpdate(); + } catch { + await runWebdriverUpdate(); + } } await useCIChrome(name, 'e2e'); @@ -182,42 +187,96 @@ export function useCIDefaults(projectName = 'test-project'): Promise { }); } +const KARMA_CONF_DEFAULT = ` + module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma') + ], + client: { + jasmine: {}, + clearContext: false // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage/$PROJECT_NAME$'), + subdir: '.', + reporters: [ + { type: 'html' }, + { type: 'text-summary' } + ] + }, + reporters: ['progress', 'kjhtml'], + port: 9876, + colors: true, + logLevel: config.LOG_INFO, + autoWatch: true, + browsers: ['Chrome'], + singleRun: false, + restartOnFileChange: true + }); + }; +`; + export async function useCIChrome(projectName: string, projectDir = ''): Promise { const protractorConf = path.join(projectDir, 'protractor.conf.js'); - const chromePath = require('puppeteer').executablePath(); - - // Use Puppeteer in protractor if a config is found on the project. if (fs.existsSync(protractorConf)) { - const protractorPath = require.resolve('protractor'); - const webdriverUpdatePath = require.resolve('webdriver-manager/selenium/update-config.json', { - paths: [protractorPath], - }); - const webdriverUpdate = JSON.parse(await readFile(webdriverUpdatePath)) as { - chrome: { last: string }; - }; - const chromeDriverPath = webdriverUpdate.chrome.last; - await replaceInFile( protractorConf, `browserName: 'chrome'`, `browserName: 'chrome', chromeOptions: { - args: ['--headless'], - binary: String.raw\`${chromePath}\`, + args: ['--headless', '--no-sandbox', '--disable-gpu', '--disable-dev-shm-usage'], + binary: String.raw\`${process.env.CHROME_BIN}\`, }`, ); await replaceInFile( protractorConf, 'directConnect: true,', - `directConnect: true, chromeDriver: String.raw\`${chromeDriverPath}\`,`, + `directConnect: true, chromeDriver: String.raw\`${process.env.CHROMEDRIVER_BIN}\`,`, ); } - // Use ChromeHeadless. + const karmaConf = path.join(projectDir, 'karma.conf.js'); + + // Create one with default config if it doesn't exist + if (!fs.existsSync(karmaConf)) { + await writeFile(karmaConf, KARMA_CONF_DEFAULT.replace('$PROJECT_NAME$', projectName)); + } + + // Update to use the headless sandboxed chrome + await replaceInFile( + karmaConf, + /browsers:.*\]\s*,/, + ` + browsers: ['ChromeHeadlessNoSandbox'], + customLaunchers: { + ChromeHeadlessNoSandbox: { + base: 'ChromeHeadless', + flags: [ + '--no-sandbox', + '--headless', + '--disable-gpu', + '--disable-dev-shm-usage', + ], + } + }, + `, + ); + return updateJsonFile('angular.json', (workspaceJson) => { const project = workspaceJson.projects[projectName]; const appTargets = project.targets || project.architect; - appTargets.test.options.browsers = 'ChromeHeadless'; + appTargets.test.options.browsers = 'ChromeHeadlessNoSandbox'; + appTargets.test.options.karmaConfig = karmaConf; }); } diff --git a/tests/legacy-cli/e2e/utils/utils.ts b/tests/legacy-cli/e2e/utils/utils.ts index 6e9c8da3e756..b5a9901b448d 100644 --- a/tests/legacy-cli/e2e/utils/utils.ts +++ b/tests/legacy-cli/e2e/utils/utils.ts @@ -24,8 +24,8 @@ export function wait(msecs: number): Promise { }); } -export async function mktempd(prefix: string): Promise { - return realpath(await mkdtemp(path.join(tmpdir(), prefix))); +export async function mktempd(prefix: string, tempRoot?: string): Promise { + return realpath(await mkdtemp(path.join(tempRoot ?? tmpdir(), prefix))); } export async function mockHome(cb: (home: string) => Promise): Promise { diff --git a/tests/legacy-cli/e2e_runner.ts b/tests/legacy-cli/e2e_runner.ts index fa5ecd74bb23..ef28132e58c9 100644 --- a/tests/legacy-cli/e2e_runner.ts +++ b/tests/legacy-cli/e2e_runner.ts @@ -8,10 +8,11 @@ import { getGlobalVariable, setGlobalVariable } from './e2e/utils/env'; import { gitClean } from './e2e/utils/git'; import { createNpmRegistry } from './e2e/utils/registry'; import { launchTestProcess } from './e2e/utils/process'; -import { join } from 'path'; +import { delimiter, dirname, join } from 'path'; +import { IS_BAZEL } from './e2e/utils/bazel'; import { findFreePort } from './e2e/utils/network'; import { extractFile } from './e2e/utils/tar'; -import { realpathSync } from 'fs'; +import { readFileSync, realpathSync } from 'fs'; import { PkgInfo } from './e2e/utils/packages'; Error.stackTraceLimit = Infinity; @@ -61,6 +62,16 @@ const argv = yargsParser(process.argv.slice(2), { }, default: { 'package': ['./dist/_*.tgz'], + + 'debug': !!process.env.BUILD_WORKSPACE_DIRECTORY, + 'glob': process.env.TESTBRIDGE_TEST_ONLY, + 'nb-shards': + Number(process.env.E2E_SHARD_TOTAL ?? 1) * Number(process.env.TEST_TOTAL_SHARDS ?? 1) || 1, + 'shard': + process.env.E2E_SHARD_INDEX === undefined && process.env.TEST_SHARD_INDEX === undefined + ? undefined + : Number(process.env.E2E_SHARD_INDEX ?? 0) * Number(process.env.TEST_TOTAL_SHARDS ?? 1) + + Number(process.env.TEST_SHARD_INDEX ?? 0), }, }); @@ -80,6 +91,20 @@ process.exitCode = 255; */ process.env.LEGACY_CLI_RUNNER = '1'; +/** + * Add external git toolchain onto PATH + */ +if (process.env.GIT_BIN) { + process.env.PATH = process.env.PATH! + delimiter + dirname(process.env.GIT_BIN!); +} + +/** + * Add external browser toolchains onto PATH + */ +if (process.env.CHROME_BIN) { + process.env.PATH = process.env.PATH! + delimiter + dirname(process.env.CHROME_BIN!); +} + const logger = createConsoleLogger(argv.verbose, process.stdout, process.stderr, { info: (s) => s, debug: (s) => s, @@ -93,17 +118,27 @@ function lastLogger() { return logStack[logStack.length - 1]; } -const testGlob = argv.glob || 'tests/**/*.ts'; +// Under bazel the compiled file (.js) and types (.d.ts) are available. +// Outside bazel the source .ts files are available. +const SRC_FILE_EXT = IS_BAZEL ? 'js' : 'ts'; +const SRC_FILE_EXT_RE = new RegExp(`\.${SRC_FILE_EXT}$`); + +const testGlob = argv.glob || `tests/**/*.${SRC_FILE_EXT}`; const e2eRoot = path.join(__dirname, 'e2e'); -const allSetups = glob.sync('setup/**/*.ts', { nodir: true, cwd: e2eRoot }).sort(); -const allInitializers = glob.sync('initialize/**/*.ts', { nodir: true, cwd: e2eRoot }).sort(); +const allSetups = glob.sync(`setup/**/*.${SRC_FILE_EXT}`, { nodir: true, cwd: e2eRoot }).sort(); +const allInitializers = glob + .sync(`initialize/**/*.${SRC_FILE_EXT}`, { nodir: true, cwd: e2eRoot }) + .sort(); const allTests = glob .sync(testGlob, { nodir: true, cwd: e2eRoot, ignore: argv.ignore }) // Replace windows slashes. .map((name) => name.replace(/\\/g, '/')) .filter((name) => { - if (name.endsWith('/setup.ts')) { + if (name.endsWith(`/setup.${SRC_FILE_EXT}`)) { + return false; + } + if (!SRC_FILE_EXT_RE.test(name)) { return false; } @@ -122,8 +157,8 @@ const allTests = glob }) .sort(); -const shardId = 'shard' in argv ? argv['shard'] : null; -const nbShards = (shardId === null ? 1 : argv['nb-shards']) || 2; +const shardId = argv['shard'] !== undefined ? Number(argv['shard']) : null; +const nbShards = shardId === null ? 1 : Number(argv['nb-shards']); const tests = allTests.filter((name) => { // Check for naming tests on command line. if (argv._.length == 0) { @@ -134,7 +169,7 @@ const tests = allTests.filter((name) => { return ( path.join(process.cwd(), argName + '') == path.join(__dirname, 'e2e', name) || argName == name || - argName == name.replace(/\.ts$/, '') + argName == name.replace(SRC_FILE_EXT_RE, '') ); }); }); @@ -143,7 +178,7 @@ const tests = allTests.filter((name) => { const testsToRun = tests.filter((name, i) => shardId === null || i % nbShards == shardId); if (testsToRun.length === 0) { - if (shardId !== null && tests.length >= shardId ? 1 : 0) { + if (shardId !== null && tests.length <= shardId) { console.log(`No tests to run on shard ${shardId}, exiting.`); process.exit(0); } else { @@ -170,9 +205,28 @@ console.log(['Tests:', ...testsToRun].join('\n ')); setGlobalVariable('argv', argv); setGlobalVariable('package-manager', argv.yarn ? 'yarn' : 'npm'); -// This is needed by karma-chrome-launcher + +// Use the chrome supplied by bazel or the puppeteer chrome and webdriver-manager driver outside. +// This is needed by karma-chrome-launcher, protractor etc. // https://github.com/karma-runner/karma-chrome-launcher#headless-chromium-with-puppeteer -process.env['CHROME_BIN'] = require('puppeteer').executablePath(); +// +// Resolve from relative paths to absolute paths within the bazel runfiles tree +// so subprocesses spawned in a different working directory can still find them. +process.env.CHROME_BIN = IS_BAZEL + ? path.resolve(process.env.CHROME_BIN!) + : require('puppeteer').executablePath(); +process.env.CHROMEDRIVER_BIN = IS_BAZEL + ? path.resolve(process.env.CHROMEDRIVER_BIN!) + : (function () { + const protractorPath = require.resolve('protractor'); + const webdriverUpdatePath = require.resolve('webdriver-manager/selenium/update-config.json', { + paths: [protractorPath], + }); + const webdriverUpdate = JSON.parse(readFileSync(webdriverUpdatePath).toString()) as { + chrome: { last: string }; + }; + return webdriverUpdate.chrome.last; + })(); Promise.all([findFreePort(), findFreePort(), findPackageTars()]) .then(async ([httpPort, httpsPort, packageTars]) => { @@ -237,12 +291,12 @@ async function runSteps( for (const [stepIndex, relativeName] of steps.entries()) { // Make sure this is a windows compatible path. - let absoluteName = path.join(e2eRoot, relativeName).replace(/\.ts$/, ''); + let absoluteName = path.join(e2eRoot, relativeName).replace(SRC_FILE_EXT_RE, ''); if (/^win/.test(process.platform)) { absoluteName = absoluteName.replace(/\\/g, path.posix.sep); } - const name = relativeName.replace(/\.ts$/, ''); + const name = relativeName.replace(SRC_FILE_EXT_RE, ''); const start = Date.now(); printHeader(relativeName, stepIndex, steps.length, type); @@ -297,7 +351,7 @@ function printHeader( type: 'setup' | 'initializer' | 'test', ) { const text = `${testIndex + 1} of ${count}`; - const fullIndex = testIndex * nbShards + shardId + 1; + const fullIndex = testIndex * nbShards + (shardId ?? 0) + 1; const shard = shardId === null || type !== 'test' ? '' @@ -328,7 +382,16 @@ async function findPackageTars(): Promise<{ [pkg: string]: PkgInfo }> { glob.sync(p, { realpath: true }), ); - const pkgJsons = await Promise.all(pkgs.map((pkg) => extractFile(pkg, './package/package.json'))); + const pkgJsons = await Promise.all( + pkgs.map(async (pkg) => { + try { + return await extractFile(pkg, './package/package.json'); + } catch (e) { + // TODO(bazel): currently the bazel npm packaging does not contain the standard npm ./package directory + return await extractFile(pkg, './package.json'); + } + }), + ); return pkgs.reduce((all, pkg, i) => { const json = pkgJsons[i].toString('utf8'); diff --git a/tools/defaults.bzl b/tools/defaults.bzl index 2b7f8bea67f0..e73154d3a2a2 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -1,6 +1,6 @@ """Re-export of some bazel rules with repository-wide defaults.""" -load("@npm//@bazel/concatjs/internal:build_defs.bzl", _ts_library = "ts_library_macro") +load("@npm//@bazel/concatjs:index.bzl", _ts_library = "ts_library") load("@build_bazel_rules_nodejs//:index.bzl", "copy_to_bin", _js_library = "js_library", _pkg_npm = "pkg_npm") load("@rules_pkg//:pkg.bzl", "pkg_tar") load("@npm//@angular/build-tooling/bazel:extract_js_module_output.bzl", "extract_js_module_output")