Skip to content

Commit

Permalink
feat(bazel): allow for working directory to be configured in integrat…
Browse files Browse the repository at this point in the history
…ion tests

Allows for the working directory of an integration test to be
configured. This is useful when a test cannot be declared directly
in the integration project. e.g. when the test directory is a Bazel
workspace itself, then we cannot declare the test within the
`BUILD.bazel` file as that file does not belong to the workspace we are
intending to run the test within.

To achieve what we want the test is declared in a directory which has
a sub-directory for the nested workspace. Then the test target can
set the working directory to the sub-directory so that commands
can be executed within it. e.g. consider a Bazel workspace test like
this:

```
integration/bazel-tests/BUILD.bazel    # <- Defines the test
  | -----------------> /ng_module_test/WORKSPACE
  | -----------------> /ng_module_test/package.json
  | -----------------> /ng_module_test/BUILD.bazel
```

As seen in the snippet above, the Bazel-nested tests need to be in
a parent folder with a `BUILD.bazel` that is part of the main workspace.
The nested workspace has its own set of files and is ignored by Bazel
(due to it being a nested workspace).. inherently the test running the
nested workspace needs to be declared outside of the nested space!
  • Loading branch information
devversion committed Nov 16, 2021
1 parent aaa90c5 commit 8b007f3
Show file tree
Hide file tree
Showing 11 changed files with 103 additions and 14 deletions.
6 changes: 6 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ build --nolegacy_external_runfiles
run --nolegacy_external_runfiles
test --nolegacy_external_runfiles

# The nested Bazel integration test workspace should not be analysed as part of
# Skyframe, but should also not be ignored in order to be able to glob for its files.
# TODO: Simplify this once https://github.com/bazelbuild/bazel/issues/12034 is fixed
build --deleted_packages=bazel/integration/tests/nested_bazel_workspaces/basic
query --deleted_packages=bazel/integration/tests/nested_bazel_workspaces/basic

###############################
# Filesystem interactions #
###############################
Expand Down
13 changes: 13 additions & 0 deletions bazel/integration/index.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ def _integration_test_config_impl(ctx):
config_file = ctx.actions.declare_file("%s.json" % ctx.attr.name)
config = struct(
testPackage = ctx.label.package,
testPackageRelativeWorkingDir = ctx.attr.working_dir,
testFiles = [_serialize_file(f) for f in ctx.files.srcs],
commands = [_expand_and_split_command(ctx, c) for c in ctx.attr.commands],
environment = _serialize_and_expand_environment(ctx, ctx.attr.environment),
Expand Down Expand Up @@ -150,6 +151,16 @@ _integration_test_config = rule(
implementation = _integration_test_config_impl,
doc = """Rule which controls the integration test runner by writing a configuration file.""",
attrs = {
"working_dir": attr.string(
default = "",
doc = """
Relative path that points to the working directory in which the integration
test commands are executed.
The working directory is also used as base directory for finding a `package.json`
file that will be updated to reflect the integration test NPM package mappings.
""",
),
"srcs": attr.label_list(
allow_files = True,
mandatory = True,
Expand Down Expand Up @@ -204,6 +215,7 @@ def integration_test(
tool_mappings = {},
environment = {},
toolchains = [],
working_dir = None,
data = [],
tags = [],
**kwargs):
Expand All @@ -221,6 +233,7 @@ def integration_test(
npm_packages = npm_packages,
tool_mappings = tool_mappings,
environment = environment,
working_dir = working_dir,
tags = tags,
toolchains = toolchains,
)
Expand Down
2 changes: 2 additions & 0 deletions bazel/integration/test_runner/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {debug} from './debug';
*/
interface TestConfig {
testPackage: string;
testPackageRelativeWorkingDir: string;
testFiles: BazelFileInfo[];
npmPackageMappings: Record<string, BazelFileInfo>;
toolMappings: Record<string, BazelFileInfo>;
Expand All @@ -38,6 +39,7 @@ async function main(): Promise<void> {
const runner = new TestRunner(
config.testFiles,
config.testPackage,
config.testPackageRelativeWorkingDir,
config.toolMappings,
config.npmPackageMappings,
config.commands,
Expand Down
31 changes: 17 additions & 14 deletions bazel/integration/test_runner/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,22 +42,25 @@ export class TestRunner {
constructor(
private readonly testFiles: BazelFileInfo[],
private readonly testPackage: string,
private readonly testPackageRelativeWorkingDir: string,
private readonly toolMappings: Record<string, BazelFileInfo>,
private readonly npmPackageMappings: Record<string, BazelFileInfo>,
private readonly commands: [[binary: BazelExpandedValue, ...args: string[]]],
private readonly environment: Record<string, BazelExpandedValue>,
) {}

async run() {
const testDir = await this._getTestTmpDirectoryPath();
const toolMappings = await this._setupToolMappingsForTest(testDir);
const testEnv = await this._buildTestProcessEnvironment(testDir, toolMappings.binDir);
const testTmpDir = await this._getTestTmpDirectoryPath();
const testWorkingDir = path.join(testTmpDir, this.testPackageRelativeWorkingDir);
const toolMappings = await this._setupToolMappingsForTest(testTmpDir);
const testEnv = await this._buildTestProcessEnvironment(testTmpDir, toolMappings.binDir);

console.info(`Running test in: ${path.normalize(testDir)}`);
debug(`Copying test fixtures into: ${path.normalize(testTmpDir)}`);
console.info(`Running test in directory: ${path.normalize(testWorkingDir)}`);

await this._copyTestFilesToDirectory(testDir);
await this._patchPackageJsonIfNeeded(testDir);
await this._runTestCommands(testDir, testEnv);
await this._copyTestFilesToDirectory(testTmpDir);
await this._patchPackageJsonIfNeeded(testWorkingDir);
await this._runTestCommands(testWorkingDir, testEnv);
}

/**
Expand Down Expand Up @@ -147,12 +150,12 @@ export class TestRunner {
}

/**
* Patches the top-level `package.json` in the given test directory by updating
* all dependency entries with their mapped files. This allows users to override
* first-party built packages with their locally-built NPM package output.
* Patches the top-level `package.json` in the given test working directory by
* updating all dependency entries with their mapped files. This allows users to
* override first-party built packages with their locally-built NPM package output.
*/
private async _patchPackageJsonIfNeeded(testDir: string) {
const pkgJsonPath = path.join(testDir, 'package.json');
private async _patchPackageJsonIfNeeded(testWorkingDir: string) {
const pkgJsonPath = path.join(testWorkingDir, 'package.json');
const pkgJson = await readPackageJsonContents(pkgJsonPath);
const mappedPackages = Object.keys(this.npmPackageMappings);

Expand Down Expand Up @@ -182,7 +185,7 @@ export class TestRunner {
* as a special placeholder for acquiring a temporary directory for the test.
*/
private async _buildTestProcessEnvironment(
testDir: string,
testTmpDir: string,
toolsBinDir: string,
): Promise<NodeJS.ProcessEnv> {
const testEnv: NodeJS.ProcessEnv = {...process.env};
Expand All @@ -194,7 +197,7 @@ export class TestRunner {
if (value.containsExpansion) {
envValue = await resolveBinaryWithRunfilesGracefully(envValue);
} else if (envValue === ENVIRONMENT_TMP_PLACEHOLDER) {
envValue = path.join(testDir, `.tmp-env-${i++}`);
envValue = path.join(testTmpDir, `.tmp-env-${i++}`);
await fs.promises.mkdir(envValue);
}

Expand Down
33 changes: 33 additions & 0 deletions bazel/integration/tests/nested_bazel_workspaces/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
load("//bazel/integration:index.bzl", "integration_test")

# Note: Since the Bazel nested workspace is fully isolated we cannot simply wire up
# the integration test with a `BUILD` file in the nested workspace. Instead we need
# the test to be defined at a higher-level, part of the actual `dev-infra` workspace.
integration_test(
name = "basic",
srcs = glob(["basic/**/*"]),
commands = [
"yarn",
"yarn test",
],
data = [
# The Yarn files also need to be part of the integration test as runfiles
# because the `yarn_bin` target is not a self-contained standalone binary.
"@nodejs//:yarn_files",
],
environment = {
# Setup a HOME directory so that Bazelisk can work, both Linux/macOS and Windows variants
# are configured to provide a fake home directory so that Bazelisk can download Bazel.
"HOME": "<TMP>",
"LOCALAPPDATA": "<TMP>",
},
tags = [
# This test relies on `yarn` so there needs to be internet access.
"requires-network",
],
tool_mappings = {
"@nodejs//:yarn_bin": "yarn",
"@nodejs//:node_bin": "node",
},
working_dir = "basic/",
)
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build --symlink_prefix=/
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
sh_test(
name = "test",
srcs = ["some_test.sh"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
workspace(
name = "nested_bazel_workspace",
)
12 changes: 12 additions & 0 deletions bazel/integration/tests/nested_bazel_workspaces/basic/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "basic",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"test": "bazel test ..."
},
"devDependencies": {
"@bazel/bazelisk": "^1.10.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/usr/bin/env bash

echo "Passes"
exit 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


"@bazel/bazelisk@^1.10.1":
version "1.10.1"
resolved "https://registry.yarnpkg.com/@bazel/bazelisk/-/bazelisk-1.10.1.tgz#46236a43ad58e310c55247f866da0dc6083c3d8b"
integrity sha512-IHszNzBO2UrUy6YtsSAsZtnU6I6qpzXGkWdEvGoMxLgJnDsEnsIYniDCUjvjU1KAP+A03eepmCHlyFcRHMSxRA==

0 comments on commit 8b007f3

Please sign in to comment.