diff --git a/.bazelrc b/.bazelrc index 03bef3226..16314a09f 100644 --- a/.bazelrc +++ b/.bazelrc @@ -43,6 +43,11 @@ build --incompatible_strict_action_env run --incompatible_strict_action_env test --incompatible_strict_action_env +# Do not build runfile forests by default. If an execution strategy relies on runfile +# forests, the forest is created on-demand. See: https://github.com/bazelbuild/bazel/issues/6627 +# and https://github.com/bazelbuild/bazel/commit/03246077f948f2790a83520e7dccc2625650e6df. This +# also helps with: https://github.com/bazelbuild/bazel/issues/4327#issuecomment-922106293. +build --nobuild_runfile_links ################################ # Remote Execution Setup # diff --git a/bazel/http-server/index.bzl b/bazel/http-server/index.bzl index 3a5b2b7c0..f88f83469 100644 --- a/bazel/http-server/index.bzl +++ b/bazel/http-server/index.bzl @@ -138,7 +138,7 @@ http_server_rule = rule( ) def http_server(name, testonly = False, port = 4200, tags = [], **kwargs): - """Creates a HTTP server that can depend on individual bazel targets. The server uses + """Creates an HTTP server that can depend on individual bazel targets. The server uses bazel runfile resolution in order to work with Bazel package paths. e.g. developers can request files through their manifest path: "my_workspace/src/dev-app/my-genfile".""" diff --git a/bazel/http-server/server.ts b/bazel/http-server/server.ts index da43bcfce..c6436494c 100644 --- a/bazel/http-server/server.ts +++ b/bazel/http-server/server.ts @@ -105,7 +105,7 @@ export class HttpServer { if (resolvedPath === null) { res.statusCode = 404; - res.end('Page not found'); + res.end('Not found - Error 404'); return; } diff --git a/bazel/http-server/test/BUILD.bazel b/bazel/http-server/test/BUILD.bazel index 3f6d27945..d25cbc315 100644 --- a/bazel/http-server/test/BUILD.bazel +++ b/bazel/http-server/test/BUILD.bazel @@ -1,8 +1,9 @@ +load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_test") load("//bazel/http-server:index.bzl", "http_server") load("//tools:defaults.bzl", "ts_library") ts_library( - name = "test_lib", + name = "app_lib", testonly = True, srcs = ["main.ts"], ) @@ -12,5 +13,35 @@ http_server( testonly = True, srcs = ["index.html"], environment_variables = ["GOOGLE_MAPS_API_KEY"], - deps = [":test_lib"], + deps = [":app_lib"], +) + +ts_library( + name = "test_lib", + testonly = True, + srcs = ["server-test.ts"], + deps = [ + "@npm//@bazel/runfiles", + "@npm//@types/selenium-webdriver", + "@npm//@types/wait-on", + "@npm//selenium-webdriver", + "@npm//wait-on", + ], +) + +nodejs_test( + name = "test", + # Pass the chromium and chromedriver binaries as arguments to the test. + # These variables are made available by the toolchain alias. + args = [ + "$(CHROMIUM)", + "$(CHROMEDRIVER)", + ], + data = [ + ":server", + ":test_lib", + "//bazel/browsers/chromium", + ], + entry_point = ":server-test.ts", + toolchains = ["//bazel/browsers/chromium:toolchain_alias"], ) diff --git a/bazel/http-server/test/main.ts b/bazel/http-server/test/main.ts index adeeb949c..5796ea4d5 100644 --- a/bazel/http-server/test/main.ts +++ b/bazel/http-server/test/main.ts @@ -8,7 +8,9 @@ /// <reference lib="dom" /> -const span = document.createElement('span'); -span.innerHTML = 'Hello!'; +const spanEl = document.createElement('span'); +const secretValue = (window as any)['GOOGLE_MAPS_API_KEY']!; -document.body.appendChild(span); +spanEl.innerHTML = `My key: ${secretValue}`; + +document.body.appendChild(spanEl); diff --git a/bazel/http-server/test/server-test.ts b/bazel/http-server/test/server-test.ts new file mode 100644 index 000000000..f07e86b98 --- /dev/null +++ b/bazel/http-server/test/server-test.ts @@ -0,0 +1,98 @@ +/** + * @license + * Copyright Google LLC All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import {runfiles} from '@bazel/runfiles'; +import {Builder, By, WebDriver} from 'selenium-webdriver'; +import {Options as ChromeOptions, ServiceBuilder} from 'selenium-webdriver/chrome'; + +import * as waitOn from 'wait-on'; +import * as childProcess from 'child_process'; + +/** + * Test script that will start the test http server binary in a background process. + * Once the server is available and listening, Chromium from `bazel/browsers` is + * launched through Selenium to ensure that the environment variable inlining, + * actual resolution of JavaScript resources and `index.html` works as expected. + */ +async function runTest() { + const [chromiumRootpath, chromedriverRootpath] = process.argv.slice(2); + + // Resolve chromium, chromedriver and the server binary to disk paths. + const chromiumPath = runfiles.resolveWorkspaceRelative(chromiumRootpath); + const chromedriverPath = runfiles.resolveWorkspaceRelative(chromedriverRootpath); + const serverBinPath = runfiles.resolveWorkspaceRelative('bazel/http-server/test/server'); + + const serverPort = 1234; + const serverHost = `127.0.0.1:${serverPort}`; + + // Start test http server in background + const serverProcess = childProcess.spawn(serverBinPath, ['--port', `${serverPort}`], { + env: {...process.env, GOOGLE_MAPS_API_KEY: 'myPersonalSecret'}, + stdio: 'inherit', + }); + + // Ensure the process gets killed, if the test terminates early. + process.on('exit', () => serverProcess.kill()); + + // Keep track of potentially launched webdriver instance, so that + // we can kill it when the test code errors unexpectedly. + let driver: WebDriver | null = null; + + try { + // Wait for server to be ready, regardless of status code (404/200 or else) + await waitOn({ + resources: [`http-get://${serverHost}`], + headers: { + 'accept': 'text/html', + }, + }); + + const service = new ServiceBuilder(chromedriverPath); + const options = new ChromeOptions() + .setChromeBinaryPath(chromiumPath) + .headless() + .addArguments('--no-sandbox'); + + driver = await new Builder() + .forBrowser('chrome') + .setChromeOptions(options) + .setChromeService(service) + .build(); + + await driver.get(`http://${serverHost}`); + + let bodyText = await driver.findElement(By.css('body')).getText(); + + // Assert that the variable is inlined, and that the `index.html` renders. + if (bodyText !== 'Works My key: myPersonalSecret') { + throw Error(`Unexpected body: ${bodyText}`); + } + + console.log('Valid text for index file: ', bodyText); + + await driver.get(`http://${serverHost}/not-found.txt`); + + bodyText = await driver.findElement(By.css('body')).getText(); + + if (bodyText !== 'Not found - Error 404') { + throw Error(`Unexpected text when requesting unknown resource: ${bodyText}`); + } + + console.log('Valid text for unknown resource:', bodyText); + } finally { + await driver?.quit(); + + // Kill server process if we exit normally (no script termination). + serverProcess.kill(); + } +} + +runTest().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/package.json b/package.json index d8ac869eb..aeeb47755 100644 --- a/package.json +++ b/package.json @@ -93,6 +93,7 @@ "@types/semver": "^7.3.6", "@types/shelljs": "^0.8.8", "@types/uuid": "^8.3.1", + "@types/wait-on": "^5.3.1", "@types/which": "^2.0.1", "@types/yargs": "^17.0.0", "@types/yarnpkg__lockfile": "^1.1.5", @@ -102,6 +103,7 @@ "protobufjs": "^6.11.2", "rxjs": "^7.4.0", "uglify-js": "^3.14.2", + "wait-on": "^6.0.0", "zone.js": "^0.11.4" } } diff --git a/yarn.lock b/yarn.lock index b13f5065d..fd5531f49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1206,6 +1206,18 @@ resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210" integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw== +"@hapi/hoek@^9.0.0": + version "9.2.1" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.2.1.tgz#9551142a1980503752536b5050fd99f4a7f13b17" + integrity sha512-gfta+H8aziZsm8pZa0vj04KO6biEiisppNgA1kbJvFrrWu9Vm7eaUEy76DIxsuTaWvti5fkJVhllWc6ZTE+Mdw== + +"@hapi/topo@^5.0.0": + version "5.1.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.1.0.tgz#dc448e332c6c6e37a4dc02fd84ba8d44b9afb012" + integrity sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1573,6 +1585,23 @@ colors "~1.2.1" string-argv "~0.3.1" +"@sideway/address@^4.1.3": + version "4.1.3" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.3.tgz#d93cce5d45c5daec92ad76db492cc2ee3c64ab27" + integrity sha512-8ncEUtmnTsMmL7z1YPB47kPUq7LpKWJNFPsRzHiIajGC5uXlWGn+AmkYPcHNl8S4tcEGx+cnORnNYaw2wvL+LQ== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" + integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@tootallnate/once@2": version "2.0.0" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf" @@ -1966,6 +1995,13 @@ resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc" integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw== +"@types/wait-on@^5.3.1": + version "5.3.1" + resolved "https://registry.yarnpkg.com/@types/wait-on/-/wait-on-5.3.1.tgz#bc5520d1d8b90b9caab1bef23315685ded73320d" + integrity sha512-2FFOKCF/YydrMUaqg+fkk49qf0e5rDgwt6aQsMzFQzbS419h2gNOXyiwp/o2yYy27bi/C1z+HgfncryjGzlvgQ== + dependencies: + "@types/node" "*" + "@types/which@^2.0.1": version "2.0.1" resolved "https://registry.yarnpkg.com/@types/which/-/which-2.0.1.tgz#27ecd67f915b7c3d6ba552135bb1eecd66e63501" @@ -2463,7 +2499,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59" integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA== -axios@0.21.4: +axios@0.21.4, axios@^0.21.1: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== @@ -5057,6 +5093,17 @@ jju@~1.4.0: resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a" integrity sha1-o6vicYryQaKykE+EpiWXDzia4yo= +joi@^17.4.0: + version "17.5.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.5.0.tgz#7e66d0004b5045d971cf416a55fb61d33ac6e011" + integrity sha512-R7hR50COp7StzLnDi4ywOXHrBrgNXuUUfJWIR5lPY5Bm/pOD3jZaTwpluUXVLRWcoWZxkrHBBJ5hLxgnlehbdw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.3" + "@sideway/formula" "^3.0.0" + "@sideway/pinpoint" "^2.0.0" + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -6975,7 +7022,7 @@ rxjs@^5.5.6: dependencies: symbol-observable "1.0.1" -rxjs@^7.2.0, rxjs@^7.4.0: +rxjs@^7.1.0, rxjs@^7.2.0, rxjs@^7.4.0: version "7.5.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.5.2.tgz#11e4a3a1dfad85dbf7fb6e33cbba17668497490b" integrity sha512-PwDt186XaL3QN5qXj/H9DGyHhP3/RYYgZZwqBv9Tv8rsAaiwFH1IsJJlcgD37J7UW5a6O67qX0KWKS3/pu0m4w== @@ -8093,6 +8140,17 @@ w3c-xmlserializer@^3.0.0: dependencies: xml-name-validator "^4.0.0" +wait-on@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-6.0.0.tgz#7e9bf8e3d7fe2daecbb7a570ac8ca41e9311c7e7" + integrity sha512-tnUJr9p5r+bEYXPUdRseolmz5XqJTTj98JgOsfBn7Oz2dxfE2g3zw1jE+Mo8lopM3j3et/Mq1yW7kKX6qw7RVw== + dependencies: + axios "^0.21.1" + joi "^17.4.0" + lodash "^4.17.21" + minimist "^1.2.5" + rxjs "^7.1.0" + watchpack@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25"