From f30edbaafe6b3a88bd4e44a20713eb8c4c864cec Mon Sep 17 00:00:00 2001 From: Paul Gschwendtner Date: Tue, 31 Dec 2019 13:51:10 +0100 Subject: [PATCH] build: create script that simplifies bazel testing workflow Instead of remembering what individual browser targets are called, and what the convention for Bazel labels is, we should just provide a script that simplifies the Bazel testing workflow. e.g. ```bash yarn test button [--local] [--firefox] [--no-watch] yarn test src/material/button src/cdk/bidi yarn test button slider coercion ``` Eventually we should document this script better in one of the contributing markdown files, but these docs need a general cleanup since they still mention Gulp. --- package.json | 5 +- scripts/run-component-tests.js | 107 +++++++++++++++++++++++++++++++++ tools/defaults.bzl | 2 + 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 scripts/run-component-tests.js diff --git a/package.json b/package.json index 76cba09fc997..8a6259889978 100644 --- a/package.json +++ b/package.json @@ -18,8 +18,9 @@ "bazel:buildifier": "find . -type f \\( -name \"*.bzl\" -or -name WORKSPACE -or -name BUILD -or -name BUILD.bazel \\) ! -path \"*/node_modules/*\" | xargs buildifier -v --warnings=attr-cfg,attr-license,attr-non-empty,attr-output-default,attr-single-file,constant-glob,ctx-args,depset-iteration,depset-union,dict-concatenation,duplicated-name,filetype,git-repository,http-archive,integer-division,load,load-on-top,native-build,native-package,output-group,package-name,package-on-top,redefined-variable,repository-name,same-origin-load,string-iteration,unused-variable,unsorted-dict-items,out-of-order-load", "bazel:format-lint": "yarn -s bazel:buildifier --lint=warn --mode=check", "dev-app": "ibazel run //src/dev-app:devserver", - "test": "bazel test //src/... --test_tag_filters=-e2e,-browser:firefox-local --build_tag_filters=-browser:firefox-local --build_tests_only", - "test-firefox": "bazel test //src/... --test_tag_filters=-e2e,-browser:chromium-local --build_tag_filters=-browser:chromium-local --build_tests_only", + "test": "node ./scripts/run-component-tests.js", + "test-local": "yarn -s test --local", + "test-firefox": "yarn -s test --firefox", "lint": "yarn -s tslint && yarn -s bazel:format-lint && yarn -s ownerslint", "e2e": "bazel test //src/... --test_tag_filters=e2e", "deploy-dev-app": "node ./scripts/deploy-dev-app.js", diff --git a/scripts/run-component-tests.js b/scripts/run-component-tests.js new file mode 100644 index 000000000000..85cf9ff0536b --- /dev/null +++ b/scripts/run-component-tests.js @@ -0,0 +1,107 @@ +#!/usr/bin/env node + +/** + * Script that simplifies the workflow of running unit tests for a component + * using Bazel. Here are a few examples: + * + * node ./scripts/run-component-tests button | Runs Material button tests + * node ./scripts/run-component-tests overlay | Runs CDK overlay tests + * node ./scripts/run-component-tests src/cdk/a11y | Runs CDK a11y tests + * node ./scripts/run-component-tests a11y overlay | Runs CDK a11y and overlay tests + * + * Supported command line flags: + * + * --local | If specified, no browser will be launched. + * --firefox | Instead of Chrome being used for tests, Firefox will be used. + * --no-watch | Watch mode is enabled by default. This flag opts-out to standard Bazel. + */ + +const minimist = require('minimist'); +const shelljs = require('shelljs'); +const chalk = require('chalk'); +const path = require('path'); +const args = process.argv.slice(2); + +// Path to the project directory. +const projectDir = path.join(__dirname, '../'); + +// Path to the directory that contains all packages. +const packagesDir = path.join(projectDir, 'src/'); + +// List of packages where the specified component could be defined in. The script uses the +// first package that contains the component (if no package is specified explicitly). +// e.g. "button" will become "material/button", and "overlay" becomes "cdk/overlay". +const orderedGuessPackages = ['material', 'cdk', 'material-experimental', 'cdk-experimental']; + +// ShellJS should exit if any command fails. +shelljs.set('-e'); +shelljs.cd(projectDir); + +// Extracts the supported command line options. +const {_: components, local, firefox, watch} = minimist(args, { + boolean: ['local', 'firefox', 'watch'], + default: {watch: true}, +}); + +// Exit if no component has been specified. +if (!components.length) { + console.error(chalk.red( + 'No component specified. Specify a component name, or pass a ' + + 'path to the component directory.')); + process.exit(1); +} + +// We can only run a single target with "--local". Running multiple targets within the +// same Karma server is not possible since each test target runs isolated from the others. +if (local && components.length > 1) { + console.error(chalk.red( + 'Unable to run multiple components tests in local mode. ' + + 'Only one component at a time can be run with "--local"')); + process.exit(1); +} + +const bazelBinary = watch ? 'ibazel' : 'bazel'; +const bazelAction = local ? 'run' : 'test'; +const testTargetName = + `unit_tests_${local ? 'local' : firefox ? 'firefox-local' : 'chromium-local'}`; +const testLabels = components.map(t => `${getBazelPackageOfComponentName(t)}:${testTargetName}`); + +// Runs Bazel for the determined test labels. +shelljs.exec(`yarn -s ${bazelBinary} ${bazelAction} ${testLabels.join(' ')}`); + +/** + * Gets the Bazel package label for the specified component name. Throws if + * the component could not be resolved to a Bazel package. + */ +function getBazelPackageOfComponentName(name) { + // Before guessing any Bazel package, we test if the name contains the + // package name already. If so, we just use that for Bazel package. + const targetName = convertPathToBazelLabel(name); + if (targetName !== null) { + return targetName; + } + // If the name does not contain an explicit package name, we try guessing the + // package name by walking through an ordered list of possible packages and checking + // if a package contains a component with the given name. The first match will be used. + for (let guessPackage of orderedGuessPackages) { + const guessTargetName = convertPathToBazelLabel(path.join(packagesDir, guessPackage, name)); + if (guessTargetName !== null) { + return guessTargetName; + } + } + throw Error(chalk.red(`Could not find test target for specified component: ` + + `${chalk.yellow(name)}. Looked in packages: ${orderedGuessPackages.join(', ')}`)); +} + +/** Converts a path to a Bazel label. */ +function convertPathToBazelLabel(name) { + if (shelljs.test('-d', name)) { + return `//${convertPathToPosix(path.relative(projectDir, name))}`; + } + return null; +} + +/** Converts an arbitrary path to a Posix path. */ +function convertPathToPosix(pathName) { + return pathName.replace(/\\/g, '/'); +} diff --git a/tools/defaults.bzl b/tools/defaults.bzl index ef32f6fa94e1..72cc28d44dce 100644 --- a/tools/defaults.bzl +++ b/tools/defaults.bzl @@ -243,6 +243,8 @@ def ng_web_test_suite(deps = [], static_css = [], bootstrap = [], tags = [], **k "//test:angular_test_init", ] + deps, browsers = [ + # Note: when changing the browser names here, also update the "yarn test" + # script to reflect the new browser names. "@io_bazel_rules_webtesting//browsers:chromium-local", "@io_bazel_rules_webtesting//browsers:firefox-local", ],