diff --git a/.bazelrc b/.bazelrc index 67f82768f..5bf24c01e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -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 # ############################### diff --git a/bazel/integration/index.bzl b/bazel/integration/index.bzl index bab387f3c..2c32cc998 100644 --- a/bazel/integration/index.bzl +++ b/bazel/integration/index.bzl @@ -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), @@ -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, @@ -204,6 +215,7 @@ def integration_test( tool_mappings = {}, environment = {}, toolchains = [], + working_dir = None, data = [], tags = [], **kwargs): @@ -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, ) diff --git a/bazel/integration/test_runner/main.ts b/bazel/integration/test_runner/main.ts index 2ae517509..14d3d548b 100644 --- a/bazel/integration/test_runner/main.ts +++ b/bazel/integration/test_runner/main.ts @@ -17,6 +17,7 @@ import {debug} from './debug'; */ interface TestConfig { testPackage: string; + testPackageRelativeWorkingDir: string; testFiles: BazelFileInfo[]; npmPackageMappings: Record; toolMappings: Record; @@ -38,6 +39,7 @@ async function main(): Promise { const runner = new TestRunner( config.testFiles, config.testPackage, + config.testPackageRelativeWorkingDir, config.toolMappings, config.npmPackageMappings, config.commands, diff --git a/bazel/integration/test_runner/runner.ts b/bazel/integration/test_runner/runner.ts index c82398a6f..2f9b43dc8 100644 --- a/bazel/integration/test_runner/runner.ts +++ b/bazel/integration/test_runner/runner.ts @@ -42,6 +42,7 @@ export class TestRunner { constructor( private readonly testFiles: BazelFileInfo[], private readonly testPackage: string, + private readonly testPackageRelativeWorkingDir: string, private readonly toolMappings: Record, private readonly npmPackageMappings: Record, private readonly commands: [[binary: BazelExpandedValue, ...args: string[]]], @@ -49,15 +50,17 @@ export class TestRunner { ) {} 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); } /** @@ -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); @@ -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 { const testEnv: NodeJS.ProcessEnv = {...process.env}; @@ -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); } diff --git a/bazel/integration/tests/nested_bazel_workspaces/BUILD.bazel b/bazel/integration/tests/nested_bazel_workspaces/BUILD.bazel new file mode 100644 index 000000000..1edf3bbd4 --- /dev/null +++ b/bazel/integration/tests/nested_bazel_workspaces/BUILD.bazel @@ -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": "", + "LOCALAPPDATA": "", + }, + 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/", +) diff --git a/bazel/integration/tests/nested_bazel_workspaces/basic/.bazelrc b/bazel/integration/tests/nested_bazel_workspaces/basic/.bazelrc new file mode 100644 index 000000000..78003332b --- /dev/null +++ b/bazel/integration/tests/nested_bazel_workspaces/basic/.bazelrc @@ -0,0 +1 @@ +build --symlink_prefix=/ diff --git a/bazel/integration/tests/nested_bazel_workspaces/basic/BUILD.bazel b/bazel/integration/tests/nested_bazel_workspaces/basic/BUILD.bazel new file mode 100644 index 000000000..50698d1c0 --- /dev/null +++ b/bazel/integration/tests/nested_bazel_workspaces/basic/BUILD.bazel @@ -0,0 +1,4 @@ +sh_test( + name = "test", + srcs = ["some_test.sh"], +) diff --git a/bazel/integration/tests/nested_bazel_workspaces/basic/WORKSPACE b/bazel/integration/tests/nested_bazel_workspaces/basic/WORKSPACE new file mode 100644 index 000000000..21bbb4ca5 --- /dev/null +++ b/bazel/integration/tests/nested_bazel_workspaces/basic/WORKSPACE @@ -0,0 +1,3 @@ +workspace( + name = "nested_bazel_workspace", +) diff --git a/bazel/integration/tests/nested_bazel_workspaces/basic/package.json b/bazel/integration/tests/nested_bazel_workspaces/basic/package.json new file mode 100644 index 000000000..755191f71 --- /dev/null +++ b/bazel/integration/tests/nested_bazel_workspaces/basic/package.json @@ -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" + } +} diff --git a/bazel/integration/tests/nested_bazel_workspaces/basic/some_test.sh b/bazel/integration/tests/nested_bazel_workspaces/basic/some_test.sh new file mode 100644 index 000000000..756948cdb --- /dev/null +++ b/bazel/integration/tests/nested_bazel_workspaces/basic/some_test.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +echo "Passes" +exit 0 diff --git a/bazel/integration/tests/nested_bazel_workspaces/basic/yarn.lock b/bazel/integration/tests/nested_bazel_workspaces/basic/yarn.lock new file mode 100644 index 000000000..8e19a7f04 --- /dev/null +++ b/bazel/integration/tests/nested_bazel_workspaces/basic/yarn.lock @@ -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==