blob: 16e2322eb4c2e339060edaa300216a5587913333 [file]
/**
* @license
* Copyright 2017 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
*
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']!);
import * as child_process from 'child_process';
import * as net from 'net';
export function isTcpPortFree(port: number): Promise<boolean> {
return new Promise((resolve, reject) => {
const server = net.createServer();
server.on('error', (e) => {
resolve(false);
});
server.on('close', () => {
resolve(true);
});
server.listen(port, () => {
server.close();
});
});
}
export function isTcpPortBound(port: number): Promise<boolean> {
return new Promise((resolve, reject) => {
const client = new net.Socket();
client.once('connect', () => {
resolve(true);
});
client.once('error', (e) => {
resolve(false);
});
client.connect(port);
});
}
export async function findFreeTcpPort(): Promise<number> {
const range = {
min: 32768,
max: 60000,
};
for (let i = 0; i < 100; i++) {
let port = Math.floor(Math.random() * (range.max - range.min) + range.min);
if (await isTcpPortFree(port)) {
return port;
}
}
throw new Error('Unable to find a free port');
}
// Interface for config parameter of the protractor_web_test_suite onPrepare function
export interface OnPrepareConfig {
// The workspace name
workspace: string;
// The server binary to run
server: string;
}
export function waitForServer(port: number, timeout: number): Promise<boolean> {
return isTcpPortBound(port).then(isBound => {
if (!isBound) {
if (timeout <= 0) {
throw new Error('Timeout waiting for server to start');
}
const wait = Math.min(timeout, 500);
return new Promise((res, rej) => setTimeout(res, wait))
.then(() => waitForServer(port, timeout - wait));
}
return true;
});
}
// Return type from runServer function
export interface ServerSpec {
// Port number that the server is running on
port: number;
}
/**
* Runs the specified server binary from a given workspace and waits for the server
* being ready. The server binary will be resolved from the Bazel runfiles. Note that
* the server will be launched with a random free port in order to support test concurrency
* with Bazel.
*/
export async function runServer(
workspace: string, serverTarget: string, portFlag: string, serverArgs: string[],
timeout = 5000): Promise<ServerSpec> {
const serverPath = runfiles.resolve(`${workspace}/${serverTarget}`);
const port = await findFreeTcpPort();
// Start the Bazel server binary with a random free TCP port.
const serverProcess = child_process.spawn(
serverPath, serverArgs.concat([portFlag, port.toString()]), {stdio: 'inherit'});
// In case the process exited with an error, we want to propagate the error.
serverProcess.on('exit', exitCode => {
if (exitCode !== 0) {
throw new Error(`Server exited with error code: ${exitCode}`);
}
});
// Wait for the server to be bound to the given port.
await waitForServer(port, timeout);
return {port};
}