forked from google/skia
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Run the tests in headless mode and output the logs bazel test :hello_world --test_output=all Start up a visible web browser with the karma test driver (need to go to Debug tab to actually run tests) bazel run :hello_world Suggested review order - package.json to see the karma dependencies to run jasmine tests on chrome and firefox. - WORKSPACE.bazel to see how the packages listed in package.json and package-lock.json are downloaded into the Bazel sandbox/cache via the npm_install rule. As mentioned in the package.json comment, the version of build_bazel_rules_nodejs which emscripten uses [1] is 4.4.1 and if we tried to install it ourselves, that installation will be ignored. We also bring in hermetic browsers via io_bazel_rules_webtesting. - bazel/karma_test.bzl which defines a new rule _karma_test and a macro karma_test which joins the new rule with an existing web_test rule to run it on a hermetic browser which Bazel downloads. This rule takes heavy inspiration from @bazel/concatjs [2], but is much simpler and lets us configure more things (e.g. proxies, so we can work with test_on_env). - karma.bazel.js, which is a pretty ordinary looking karma configuration file [2] with effectively a JS macro BAZEL_APPLY_SETTINGS. JS doesn't have a preprocessor or actual macros, but this string will be replaced by the JS code in karma_test.bzl which will set correct filepaths for Bazel content. - All other files. [1] https://github.com/emscripten-core/emsdk/blob/c33c7be17f047355aa13a59f62a05100f9ff3257/bazel/deps.bzl#L10 [2] https://github.com/bazelbuild/rules_nodejs/blob/700b7a3c5f97f2877320e6e699892ee706f85269/packages/concatjs/web_test/karma_web_test.bzl#L318 [3] http://karma-runner.github.io/6.3/config/configuration-file.html Change-Id: Id64c0a86d6be37d627762cef0beaaf23ad390ac1 Reviewed-on: https://skia-review.googlesource.com/c/skia/+/509717 Reviewed-by: Leandro Lovisolo <[email protected]>
- Loading branch information
Showing
8 changed files
with
3,970 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
# https://github.com/bazelbuild/rules_webtesting/blob/master/web/web.bzl | ||
load("@io_bazel_rules_webtesting//web:web.bzl", "web_test") | ||
load("@build_bazel_rules_nodejs//:providers.bzl", "ExternalNpmPackageInfo", "node_modules_aspect") | ||
|
||
def karma_test(name, srcs, config_file, **kwargs): | ||
"""Tests the given JS files using Karma and a browser provided by Bazel (Chromium) | ||
This rule injects some JS code into the karma config file and produces both that modified | ||
configuration file and a bash script which invokes Karma. That script is then invoked | ||
in an environment that has the Bazel-downloaded browser available and the tests run using it. | ||
When invoked via `bazel test`, the test runs in headless mode. When invoked via `bazel run`, | ||
a visible web browser appears for the user to inspect and debug. | ||
This draws inspiration from the karma_web_test implementation in concatjs | ||
https://github.com/bazelbuild/rules_nodejs/blob/700b7a3c5f97f2877320e6e699892ee706f85269/packages/concatjs/web_test/karma_web_test.bzl | ||
but we were unable to use it because they prevented us from defining some proxies ourselves, | ||
which we need in order to communicate our test gms (PNG files) to a server that runs alongside | ||
the test. This implementation is simpler than concatjs's and does not try to work for all | ||
situations nor bundle everything together. | ||
Args: | ||
srcs: A list of JavaScript test files or helpers. | ||
config_file: A karma config file. The user is to expect a function called BAZEL_APPLY_SETTINGS | ||
is defined and should call it with the configuration object before passing it to config.set. | ||
""" | ||
if len(srcs) == 0: | ||
fail("Must pass at least one file into srcs or there will be no tests to run") | ||
|
||
wrapped_test_name = name + "_karma_test" | ||
_karma_test( | ||
name = wrapped_test_name, | ||
srcs = srcs, | ||
deps = [ | ||
"@npm//karma-chrome-launcher", | ||
"@npm//karma-firefox-launcher", | ||
"@npm//karma-jasmine", | ||
"@npm//jasmine-core", | ||
], | ||
config_file = config_file, | ||
visibility = ["//visibility:private"], | ||
) | ||
|
||
# See the following link for the options. | ||
# https://github.com/bazelbuild/rules_webtesting/blob/e9cf17123068b1123c68219edf9b274bf057b9cc/web/internal/web_test.bzl#L164 | ||
# TODO(kjlubick) consider using web_test_suite to test on Firefox as well. | ||
web_test( | ||
name = name, | ||
launcher = ":" + wrapped_test_name, | ||
browser = "@io_bazel_rules_webtesting//browsers:chromium-local", | ||
test = wrapped_test_name, | ||
**kwargs | ||
) | ||
|
||
# This JS code is injected into the the provided karma configuration file. It contains | ||
# Bazel-specific logic that could be re-used across different configuration files. | ||
# Concretely, it sets up the browser configuration and whether we want to just run the tests | ||
# and exit (e.g. the user ran `bazel test foo`) or if we want to have an interactive session | ||
# (e.g. the user ran `bazel run foo`). | ||
_apply_bazel_settings_js_code = """ | ||
(function(cfg) { | ||
// Apply the paths to any files that are coming from other Bazel rules (e.g. compiled JS). | ||
function addFilePaths(cfg) { | ||
if (!cfg.files) { | ||
cfg.files = []; | ||
} | ||
cfg.files = cfg.files.concat([_BAZEL_SRCS]); | ||
cfg.basePath = "_BAZEL_BASE_PATH"; | ||
} | ||
// Returns true if invoked with bazel run, i.e. the user wants to see the results on a real | ||
// browser. | ||
function isBazelRun() { | ||
// This env var seems to be a good indicator on Linux, at least. | ||
return !!process.env['DISPLAY']; | ||
} | ||
// Configures the settings to run chrome. | ||
function applyChromiumSettings(cfg, runfiles, chromiumPath) { | ||
if (isBazelRun()) { | ||
cfg.browsers = ['Chrome']; | ||
cfg.singleRun = false; | ||
} else { | ||
// Invoked via bazel test, so run the tests once in a headless browser and be done | ||
cfg.browsers = ['ChromeHeadless']; | ||
cfg.singleRun = true; | ||
} | ||
try { | ||
// Setting the CHROME_BIN environment variable tells Karma which chrome to use. | ||
// We want it to use the Chrome brought via Bazel. | ||
process.env.CHROME_BIN = runfiles.resolve(chromiumPath); | ||
} catch { | ||
throw new Error(`Failed to resolve Chromium binary '${chromiumPath}' in runfiles`); | ||
} | ||
} | ||
function applyBazelSettings(cfg) { | ||
addFilePaths(cfg) | ||
// This is is a JS function provided via environment variables to let us resolve files | ||
// https://bazelbuild.github.io/rules_nodejs/Built-ins.html#nodejs_binary-templated_args | ||
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']); | ||
// This is a JSON file that contains this metadata, mixed in with some other data, e.g. | ||
// the link to the correct executable for the given platform. | ||
// https://github.com/bazelbuild/rules_webtesting/blob/e9cf17123068b1123c68219edf9b274bf057b9cc/browsers/chromium-local.json | ||
const webTestMetadata = require(runfiles.resolve(process.env['WEB_TEST_METADATA'])); | ||
const webTestFiles = webTestMetadata['webTestFiles'][0]; | ||
const path = webTestFiles['namedFiles']['CHROMIUM']; | ||
if (path) { | ||
applyChromiumSettings(cfg, runfiles, path); | ||
} else { | ||
throw new Error("not supported yet"); | ||
} | ||
} | ||
applyBazelSettings(cfg) | ||
// The user is expected to treat the BAZEL_APPLY_SETTINGS as a function name and pass in | ||
// the configuration as a parameter. Thus, we need to end such that our IIFE will be followed | ||
// by the parameter in parentheses and get passed in as cfg. | ||
})""" | ||
|
||
def _expand_templates_in_karma_config(ctx): | ||
# Wrap the absolute paths of our files in quotes and make them comma seperated so they | ||
# can go in the Karma files list. | ||
srcs = ['"{}"'.format(_absolute_path(ctx, f)) for f in ctx.files.srcs] | ||
src_list = ", ".join(srcs) | ||
|
||
# Set our base path to that which contains the karma configuration file. | ||
# This requires going up a few directory segments. This allows our absolute paths to | ||
# all be compatible with each other. | ||
config_segments = len(ctx.outputs.configuration.short_path.split("/")) | ||
base_path = "/".join([".."] * config_segments) | ||
|
||
# Replace the placeholders in the embedded JS with those files. We cannot use .format() because | ||
# the curly braces from the JS code throw it off. | ||
apply_bazel_settings = _apply_bazel_settings_js_code.replace("_BAZEL_SRCS", src_list) | ||
apply_bazel_settings = apply_bazel_settings.replace("_BAZEL_BASE_PATH", base_path) | ||
|
||
# Add in the JS fragment that applies the Bazel-specific settings to the provided config. | ||
# https://docs.bazel.build/versions/main/skylark/lib/actions.html#expand_template | ||
ctx.actions.expand_template( | ||
output = ctx.outputs.configuration, | ||
template = ctx.file.config_file, | ||
substitutions = { | ||
"BAZEL_APPLY_SETTINGS": apply_bazel_settings, | ||
}, | ||
) | ||
|
||
def _absolute_path(ctx, file): | ||
# Referencing things in @npm yields a short_path that starts with ../ | ||
# For those cases, we can just remove the ../ | ||
if file.short_path.startswith("../"): | ||
return file.short_path[3:] | ||
|
||
# Otherwise, we have a local file, so we need to include the workspace path to make it | ||
# an absolute path | ||
return ctx.workspace_name + "/" + file.short_path | ||
|
||
_invoke_karma_bash_script = """#!/usr/bin/env bash | ||
# --- begin runfiles.bash initialization v2 --- | ||
# Copy-pasted from the Bazel Bash runfiles library v2. | ||
# https://github.com/bazelbuild/bazel/blob/master/tools/bash/runfiles/runfiles.bash | ||
set -uo pipefail; f=build_bazel_rules_nodejs/third_party/github.com/bazelbuild/bazel/tools/bash/runfiles/runfiles.bash | ||
source "${{RUNFILES_DIR:-/dev/null}}/$f" 2>/dev/null || \ | ||
source "$(grep -sm1 "^$f " "${{RUNFILES_MANIFEST_FILE:-/dev/null}}" | cut -f2- -d' ')" 2>/dev/null || \ | ||
source "$0.runfiles/$f" 2>/dev/null || \ | ||
source "$(grep -sm1 "^$f " "$0.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ | ||
source "$(grep -sm1 "^$f " "$0.exe.runfiles_manifest" | cut -f2- -d' ')" 2>/dev/null || \ | ||
{{ echo>&2 "ERROR: cannot find $f"; exit 1; }}; f=; set -e | ||
# --- end runfiles.bash initialization v2 --- | ||
readonly KARMA=$(rlocation "{_KARMA_EXECUTABLE_SCRIPT}") | ||
readonly CONF=$(rlocation "{_KARMA_CONFIGURATION_FILE}") | ||
# set a temporary directory as the home directory, because otherwise Chrome fails to | ||
# start up, complaining about a read-only file system. This does not get cleaned up automatically. | ||
export HOME=$(mktemp -d) | ||
readonly COMMAND="${{KARMA}} "start" ${{CONF}}" | ||
${{COMMAND}} | ||
KARMA_EXIT_CODE=$? | ||
echo "Karma returned ${{KARMA_EXIT_CODE}}" | ||
# Attempt to clean up the temporary home directory. If this fails, that's not a big deal because | ||
# the contents are small and will be cleaned up by the OS on reboot. | ||
rm -rf $HOME || true | ||
exit $KARMA_EXIT_CODE | ||
""" | ||
|
||
def _create_bash_script_to_invoke_karma(ctx): | ||
ctx.actions.write( | ||
output = ctx.outputs.executable, | ||
is_executable = True, | ||
content = _invoke_karma_bash_script.format( | ||
_KARMA_EXECUTABLE_SCRIPT = _absolute_path(ctx, ctx.executable.karma), | ||
_KARMA_CONFIGURATION_FILE = _absolute_path(ctx, ctx.outputs.configuration), | ||
), | ||
) | ||
|
||
def _karma_test_impl(ctx): | ||
_expand_templates_in_karma_config(ctx) | ||
_create_bash_script_to_invoke_karma(ctx) | ||
|
||
# The files that need to be included when we run the bash script that invokes Karma are: | ||
# - The templated configuration file | ||
# - Any JS test files the user provided | ||
# - The other dependencies from npm (e.g. jasmine-core) | ||
runfiles = [ | ||
ctx.outputs.configuration, | ||
] | ||
runfiles += ctx.files.srcs | ||
runfiles += ctx.files.deps | ||
|
||
# We need to add the sources for our Karma dependencies as transitive dependencies, otherwise | ||
# things like the karma-chrome-launcher will not be available for Karma to load. | ||
# https://docs.bazel.build/versions/main/skylark/lib/depset.html | ||
node_modules_depsets = [] | ||
for dep in ctx.attr.deps: | ||
if ExternalNpmPackageInfo in dep: | ||
node_modules_depsets.append(dep[ExternalNpmPackageInfo].sources) | ||
else: | ||
print("Not an external npm file?", dep) | ||
node_modules = depset(transitive = node_modules_depsets) | ||
|
||
# https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html | ||
return [DefaultInfo( | ||
runfiles = ctx.runfiles( | ||
files = runfiles, | ||
transitive_files = node_modules, | ||
).merge(ctx.attr.karma[DefaultInfo].data_runfiles), | ||
executable = ctx.outputs.executable, | ||
)] | ||
|
||
_karma_test = rule( | ||
implementation = _karma_test_impl, | ||
test = True, | ||
executable = True, | ||
attrs = { | ||
"config_file": attr.label( | ||
doc = "The karma config file", | ||
mandatory = True, | ||
allow_single_file = [".js"], | ||
), | ||
"srcs": attr.label_list( | ||
doc = "A list of JavaScript test files", | ||
allow_files = [".js"], | ||
mandatory = True, | ||
), | ||
"deps": attr.label_list( | ||
doc = """Any karma plugins (aka peer deps) required. These are generally listed | ||
in the provided config_file.""", | ||
allow_files = True, | ||
aspects = [node_modules_aspect], | ||
mandatory = True, | ||
), | ||
"karma": attr.label( | ||
doc = "karma binary label", | ||
# By default, we use the karma pulled in via Bazel running npm install | ||
default = "@npm//karma/bin:karma", | ||
executable = True, | ||
cfg = "exec", | ||
allow_files = True, | ||
), | ||
}, | ||
outputs = { | ||
"configuration": "%{name}.conf.js", | ||
}, | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
module.exports = function(config) { | ||
// http://karma-runner.github.io/6.3/config/configuration-file.html | ||
let cfg = { | ||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter | ||
frameworks: ['jasmine'], | ||
|
||
// possible values: 'dots', 'progress' | ||
// available reporters: https://npmjs.org/browse/keyword/karma-reporter | ||
reporters: ['progress'], | ||
colors: true, | ||
logLevel: config.LOG_INFO, | ||
|
||
browserDisconnectTimeout: 20000, | ||
browserNoActivityTimeout: 20000, | ||
|
||
// How many browsers should be started simultaneous | ||
concurrency: Infinity, | ||
}; | ||
|
||
// Bazel will inject some code here to add/change the following items: | ||
// - files | ||
// - proxies | ||
// - browsers | ||
// - basePath | ||
// - singleRun | ||
BAZEL_APPLY_SETTINGS(cfg) | ||
|
||
config.set(cfg); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
describe('The test harness', () => { | ||
it('runs the first test', () => { | ||
expect(2+3).toBe(5); | ||
}); | ||
it('runs the second test', () => { | ||
expect(null).toBeFalsy(); | ||
}); | ||
}) |
Oops, something went wrong.