[bazel] Show CanvasKit can be loaded in POC karma_test
In order to load CanvasKit, we need to add support for statically
served files. On the karma side, this is done by adding an
object [1] to the files list (example: [2]).
Then, we need to include canvaskit.js in with the karma test
files and use the callback to load canvaskit.wasm from the
correct file location.
[1] http://karma-runner.github.io/6.3/config/files.html
[2] https://github.com/google/skia/blob/4f7b6560123d89cac2a9ec6eb71a54b5bc2fe56c/modules/canvaskit/karma.conf.js#L13
Change-Id: I7482d6e949a5e8efd0ca882efe5afbe0dc16c0e4
Bug: skia:12541
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/510736
Reviewed-by: Leandro Lovisolo <lovisolo@google.com>
diff --git a/bazel/karma_test.bzl b/bazel/karma_test.bzl
index f5edc49..2388ae7 100644
--- a/bazel/karma_test.bzl
+++ b/bazel/karma_test.bzl
@@ -1,8 +1,10 @@
+"""This module defines rules for running JS tests in a browser."""
+
# 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):
+def karma_test(name, config_file, srcs, static_files = None, **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
@@ -20,12 +22,24 @@
situations nor bundle everything together.
Args:
- srcs: A list of JavaScript test files or helpers.
+ name: The name of the rule which actually runs the tests. generated dependent rules will use
+ this name plus an applicable suffix.
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.
+ srcs: A list of JavaScript test files or helpers.
+ static_files: Arbitrary files which are available to be loaded.
+ Files are served at:
+ - `/static/<WORKSPACE_NAME>/<path-to-file>` or
+ - `/static/<WORKSPACE_NAME>/<path-to-rule>/<file>`
+ Examples:
+ - `/static/skia/modules/canvaskit/tests/assets/color_wheel.gif`
+ - `/static/skia/modules/canvaskit/canvaskit_wasm/canvaskit.wasm`
+ **kwargs: Additional arguments are passed to @io_bazel_rules_webtesting/web_test.
"""
if len(srcs) == 0:
fail("Must pass at least one file into srcs or there will be no tests to run")
+ if not static_files:
+ static_files = []
wrapped_test_name = name + "_karma_test"
_karma_test(
@@ -38,6 +52,7 @@
"@npm//jasmine-core",
],
config_file = config_file,
+ static_files = static_files,
visibility = ["//visibility:private"],
)
@@ -59,6 +74,10 @@
# (e.g. the user ran `bazel run foo`).
_apply_bazel_settings_js_code = """
(function(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']);
+
// Apply the paths to any files that are coming from other Bazel rules (e.g. compiled JS).
function addFilePaths(cfg) {
if (!cfg.files) {
@@ -66,6 +85,24 @@
}
cfg.files = cfg.files.concat([_BAZEL_SRCS]);
cfg.basePath = "_BAZEL_BASE_PATH";
+
+ if (!cfg.proxies) {
+ cfg.proxies = {};
+ }
+ // The following is based off of the concatjs version
+ // https://github.com/bazelbuild/rules_nodejs/blob/700b7a3c5f97f2877320e6e699892ee706f85269/packages/concatjs/web_test/karma.conf.js#L276
+ const staticFiles = [_BAZEL_STATIC_FILES];
+ for (const file of staticFiles) {
+ // We need to find the actual path (symlinks can apparently cause issues on Windows).
+ const resolvedFile = runfiles.resolve(file);
+ cfg.files.push({pattern: resolvedFile, included: false});
+ // We want the file to be available on a path according to its location in the workspace
+ // (and not the path on disk), so we use a proxy to redirect.
+ // Prefixing the proxy path with '/absolute' allows karma to load files that are not
+ // underneath the basePath. This doesn't see to be an official API.
+ // https://github.com/karma-runner/karma/issues/2703
+ cfg.proxies['/static/' + file] = '/absolute' + resolvedFile;
+ }
}
// Returns true if invoked with bazel run, i.e. the user wants to see the results on a real
@@ -76,7 +113,7 @@
}
// Configures the settings to run chrome.
-function applyChromiumSettings(cfg, runfiles, chromiumPath) {
+function applyChromiumSettings(cfg, chromiumPath) {
if (isBazelRun()) {
cfg.browsers = ['Chrome'];
cfg.singleRun = false;
@@ -97,9 +134,6 @@
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.
@@ -109,7 +143,7 @@
const webTestFiles = webTestMetadata['webTestFiles'][0];
const path = webTestFiles['namedFiles']['CHROMIUM'];
if (path) {
- applyChromiumSettings(cfg, runfiles, path);
+ applyChromiumSettings(cfg, path);
} else {
throw new Error("not supported yet");
}
@@ -134,10 +168,14 @@
config_segments = len(ctx.outputs.configuration.short_path.split("/"))
base_path = "/".join([".."] * config_segments)
+ static_files = ['"{}"'.format(_absolute_path(ctx, f)) for f in ctx.files.static_files]
+ static_list = ", ".join(static_files)
+
# 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)
+ apply_bazel_settings = apply_bazel_settings.replace("_BAZEL_STATIC_FILES", static_list)
# 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
@@ -176,7 +214,8 @@
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.
+# start up, complaining about a read-only file system. This does not get cleaned up automatically
+# by Bazel, so we do so after Karma finishes.
export HOME=$(mktemp -d)
readonly COMMAND="${{KARMA}} "start" ${{CONF}}"
@@ -206,11 +245,13 @@
# 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
+ # - Any static files the user specified
# - The other dependencies from npm (e.g. jasmine-core)
runfiles = [
ctx.outputs.configuration,
]
runfiles += ctx.files.srcs
+ runfiles += ctx.files.static_files
runfiles += ctx.files.deps
# We need to add the sources for our Karma dependencies as transitive dependencies, otherwise
@@ -221,7 +262,7 @@
if ExternalNpmPackageInfo in dep:
node_modules_depsets.append(dep[ExternalNpmPackageInfo].sources)
else:
- print("Not an external npm file?", dep)
+ fail("Not an external npm file: " + dep)
node_modules = depset(transitive = node_modules_depsets)
# https://docs.bazel.build/versions/main/skylark/lib/DefaultInfo.html
@@ -250,7 +291,7 @@
),
"deps": attr.label_list(
doc = """Any karma plugins (aka peer deps) required. These are generally listed
- in the provided config_file.""",
+ in the provided config_file""",
allow_files = True,
aspects = [node_modules_aspect],
mandatory = True,
@@ -263,6 +304,10 @@
cfg = "exec",
allow_files = True,
),
+ "static_files": attr.label_list(
+ doc = "Additional files which are available to be loaded",
+ allow_files = True,
+ ),
},
outputs = {
"configuration": "%{name}.conf.js",
diff --git a/modules/canvaskit/BUILD.bazel b/modules/canvaskit/BUILD.bazel
index a40069f..b758fb8 100644
--- a/modules/canvaskit/BUILD.bazel
+++ b/modules/canvaskit/BUILD.bazel
@@ -434,7 +434,14 @@
karma_test(
name = "hello_world",
srcs = [
+ ":canvaskit_wasm/canvaskit.js",
+ # We want to make sure the CanvasKit JS is loaded before the loader script
+ "tests/bazel_canvaskitinit.js",
+ # which is loaded before the tests...
"tests/hello_world.js",
],
config_file = "karma.bazel.js",
+ static_files = [
+ ":canvaskit_wasm/canvaskit.wasm",
+ ],
)
diff --git a/modules/canvaskit/Makefile b/modules/canvaskit/Makefile
index a68d568..05aa85f 100644
--- a/modules/canvaskit/Makefile
+++ b/modules/canvaskit/Makefile
@@ -157,3 +157,6 @@
cp ../../bazel-bin/modules/canvaskit/canvaskit_wasm/canvaskit.js build/canvaskit.js
cp ../../bazel-bin/modules/canvaskit/canvaskit_wasm/canvaskit.wasm build/canvaskit.wasm
ls -l build
+
+bazel_test_canvaskit:
+ bazelisk test :hello_world --compilation_mode opt --spawn_strategy=local --test_output=all
diff --git a/modules/canvaskit/tests/bazel_canvaskitinit.js b/modules/canvaskit/tests/bazel_canvaskitinit.js
new file mode 100644
index 0000000..15ce829
--- /dev/null
+++ b/modules/canvaskit/tests/bazel_canvaskitinit.js
@@ -0,0 +1,18 @@
+// The increased timeout is especially needed with larger binaries
+// like in the debug/gpu build
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
+
+let CanvasKit = null;
+const LoadCanvasKit = new Promise((resolve, reject) => {
+ console.log('canvaskit loading', new Date());
+ CanvasKitInit({
+ locateFile: (file) => '/static/skia/modules/canvaskit/canvaskit_wasm/'+file,
+ }).then((loaded) => {
+ console.log('canvaskit loaded', new Date());
+ CanvasKit = loaded;
+ resolve();
+ }).catch((e) => {
+ console.error('canvaskit failed to load', new Date(), e);
+ reject();
+ });
+});
\ No newline at end of file
diff --git a/modules/canvaskit/tests/hello_world.js b/modules/canvaskit/tests/hello_world.js
index 361e992..8f19c83 100644
--- a/modules/canvaskit/tests/hello_world.js
+++ b/modules/canvaskit/tests/hello_world.js
@@ -5,4 +5,15 @@
it('runs the second test', () => {
expect(null).toBeFalsy();
});
+
+ describe('the CanvasKit loading promise', () => {
+ beforeEach(async () => {
+ await LoadCanvasKit;
+ });
+
+ it('has access to CanvasKit', () => {
+ const r = CanvasKit.LTRBRect(1, 2, 3, 4);
+ expect(r.constructor.name).toEqual('Float32Array');
+ });
+ });
})
\ No newline at end of file