"""Npm integration testing """ load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") # Returns the manifest path of a file: `workspace/path/to/file` def _to_manifest_path(ctx, file): if file.short_path.startswith("../"): # Strip the ../ from short_path to external repository return file.short_path[3:] else: # Add the repository name for short_path to local repository return ctx.workspace_name + "/" + file.short_path def _npm_integration_test_config_impl(ctx): if len(ctx.files.test_files) == 0: fail("No files were found to run under integration testing.") if ctx.attr.debug: for f in ctx.files.test_files: if f.is_directory: fail("In debug mode, directory test_files labels not supported.") commands = [] for c in ctx.attr.commands: commands.append(ctx.expand_location(c, targets = ctx.attr.data)) # pass --define vars to test; these are added to the environment using process.env(). env_vars = {} for k in ctx.attr.configuration_env_vars: if k in ctx.var.keys(): env_vars[k] = ctx.var[k] # Serialize configuration file for test runner ctx.actions.write( output = ctx.outputs.config, content = """// npm_integration_test runner config generated by npm_integration_test rule module.exports = {{ testFiles: [ {TMPL_test_files} ], commands: [ {TMPL_commands} ], npmPackages: {{ {TMPL_npm_packages} }}, checkNpmPackages: [ {TMPL_check_npm_packages} ], envVars: {{ {TMPL_env_vars} }}, debug: {TMPL_debug}, }}; """.format( TMPL_test_files = ", ".join(["'%s'" % f.short_path for f in ctx.files.test_files]), TMPL_commands = ", ".join(["'%s'" % s for s in commands]), TMPL_npm_packages = ", ".join(["'%s': '%s'" % (ctx.attr.npm_packages[n], n.files.to_list()[0].short_path) for n in ctx.attr.npm_packages]), TMPL_check_npm_packages = ", ".join(["'%s'" % s for s in ctx.attr.check_npm_packages]), TMPL_env_vars = ", ".join(["'%s': '%s'" % (k, env_vars[k]) for k in env_vars]), TMPL_debug = "true" if ctx.attr.debug else "false", ), ) runfiles = [ctx.outputs.config] + ctx.files.test_files + ctx.files.npm_packages return [DefaultInfo(runfiles = ctx.runfiles(files = runfiles))] _NPM_INTEGRATION_TEST_CONFIG_ATTRS = { "commands": attr.string_list( default = [], mandatory = True, doc = """The list of test commands to run. Defaults to `[]`.""", ), "configuration_env_vars": attr.string_list( doc = """Pass these configuration environment variables to the resulting test. Chooses a subset of the configuration environment variables (taken from `ctx.var`), which also includes anything specified via the --define flag. Note, this can lead to different results for the test.""", default = [], ), "check_npm_packages": attr.string_list( doc = """A list of npm packages that should be replaced in this test. This attribute checks that none of the npm packages lists is found in the workspace-under-test's package.json file unlinked to a generated npm package. This can be used to verify that all npm package artifacts that need to be tested against are indeed replaced in all integration tests. For example, ``` check_npm_packages = [ "@angular/common", "@angular/compiler", "@angular/compiler-cli", "@angular/core", ], ``` If an `npm_packages` replacement on any package listed is missed then the test will fail. Since listing all npm packages in `npm_packages` is expensive as any change will result in all integration tests re-running, this attribute allows a fine grained `npm_packages` per integration test with the added safety that none are missed for any one test. """, ), "data": attr.label_list( doc = """Data dependencies for test.""", allow_files = True, ), "debug": attr.bool( doc = """Setup the test for debugging. If set to true then the package.json replacement are done in-place instead of a tmp folder and the test is not run. This is used to configure the test folder for local testing and debugging. """, default = False, ), "npm_packages": attr.label_keyed_string_dict( doc = """A label keyed string dictionary of npm package replacements to make in the workspace-under-test's package.json with npm package targets. The targets should be pkg_tar tar.gz archives. For example, ``` npm_packages = { "//packages/common:npm_package_archive": "@angular/common", "//packages/compiler:npm_package_archive": "@angular/compiler", "//packages/compiler-cli:npm_package_archive": "@angular/compiler-cli", "//packages/core:npm_package_archive": "@angular/core", } ```""", allow_files = True, ), "test_files": attr.label( doc = """A filegroup of all files necessary to run the test.""", allow_files = True, ), } _npm_integration_test_config = rule( implementation = _npm_integration_test_config_impl, doc = """Generates an npm_integration_test config.""", attrs = _NPM_INTEGRATION_TEST_CONFIG_ATTRS, outputs = { "config": "%{name}.js", }, ) def npm_integration_test(name, **kwargs): """Runs an npm integration test. See _NPM_INTEGRATION_TEST_CONFIG_ATTRS above for configuration arguments. """ commands = kwargs.pop("commands", []) configuration_env_vars = kwargs.pop("configuration_env_vars", []) check_npm_packages = kwargs.pop("check_npm_packages", []) npm_packages = kwargs.pop("npm_packages", {}) test_files = kwargs.pop("test_files", []) data = kwargs.pop("data", []) _npm_integration_test_config( name = name + ".config", commands = commands, configuration_env_vars = configuration_env_vars, check_npm_packages = check_npm_packages, data = data, npm_packages = npm_packages, test_files = test_files, visibility = ["//visibility:private"], tags = ["manual"], testonly = True, ) # Config for debug target below _npm_integration_test_config( name = name + ".debug.config", commands = commands, configuration_env_vars = configuration_env_vars, check_npm_packages = check_npm_packages, data = data, npm_packages = npm_packages, test_files = test_files, debug = True, visibility = ["//visibility:private"], tags = ["manual"], testonly = True, ) tags = kwargs.pop("tags", []) npm_deps = ["@npm//tmp", "@npm//@bazel/runfiles"] nodejs_test( name = name, data = data + npm_deps + [":%s.config" % name, ":%s.config.js" % name], tags = tags, templated_args = ["$(rootpath :%s.config.js)" % name], entry_point = "//tools/npm_integration_test:test_runner.js", **kwargs ) # Setup a .debug target that sets the debug attribute to True. # This target must be run with `bazel run` so it is tagged manual. nodejs_test( name = name + ".debug", data = data + npm_deps + [":%s.debug.config" % name, ":%s.debug.config.js" % name], tags = tags + ["manual", "local"], templated_args = ["$(rootpath :%s.debug.config.js)" % name], entry_point = "//tools/npm_integration_test:test_runner.js", **kwargs )