From 953f370336b08250234f1c947f6e8e414031671f Mon Sep 17 00:00:00 2001 From: SteveSandersonMS Date: Thu, 15 Dec 2016 20:25:20 +0000 Subject: [PATCH] Wait for port to be freed before continuing --- test/package.json | 1 + test/templates/util/aspnet.ts | 30 +++++++++++++++++++++++++----- test/templates/util/ports.ts | 23 +++++++++++++++++++++++ 3 files changed, 49 insertions(+), 5 deletions(-) create mode 100644 test/templates/util/ports.ts diff --git a/test/package.json b/test/package.json index 3470f6c..9bcf24a 100644 --- a/test/package.json +++ b/test/package.json @@ -18,6 +18,7 @@ "chai": "^3.5.0", "cross-spawn": "^5.0.1", "mkdirp": "^0.5.1", + "portastic": "^1.0.1", "rimraf": "^2.5.4", "selenium-standalone": "^5.9.0", "tree-kill": "^1.1.0", diff --git a/test/templates/util/aspnet.ts b/test/templates/util/aspnet.ts index 5286d81..11d75ae 100644 --- a/test/templates/util/aspnet.ts +++ b/test/templates/util/aspnet.ts @@ -1,10 +1,12 @@ import * as childProcess from 'child_process'; import * as path from 'path'; import * as readline from 'readline'; +import { waitUntilPortState } from './ports'; const treeKill = require('tree-kill'); const crossSpawn: typeof childProcess.spawn = require('cross-spawn'); +const defaultPort = 5000; -export const defaultUrl = 'http://localhost:5000'; +export const defaultUrl = `http://localhost:${ defaultPort }`; export enum AspNetCoreEnviroment { development, @@ -50,7 +52,7 @@ export class AspNetProcess { }); // Ensure the process isn't orphaned even if Node crashes before we're disposed - process.on('exit', () => this._killProcessSync()); + process.on('exit', () => this._killAspNetProcess()); // Also track whether it exited on its own already this._process.on('exit', () => { @@ -74,7 +76,7 @@ export class AspNetProcess { public dispose(): Promise { return new Promise((resolve, reject) => { - this._killProcessSync(err => { + this._killAspNetProcess(err => { if (err) { reject(err); } else { @@ -84,11 +86,29 @@ export class AspNetProcess { }); } - private _killProcessSync(callback?: (err: any) => void) { + private _killAspNetProcess(callback?: (err: any) => void) { + callback = callback || (() => {}); if (!this._processHasExited) { // It's important to kill the whole tree, because 'dotnet run' launches a separate 'dotnet exec' // child process that would otherwise be left running - treeKill(this._process.pid, 'SIGINT', callback); + treeKill(this._process.pid, 'SIGINT', err => { + if (err) { + callback(err); + } else { + // It's not enough just to send a SIGINT to ASP.NET. It will stay open for a moment, completing + // any outstanding requests. We have to wait for it really to be gone before continuing, otherwise + // the next test might be unable to start because of the port still being in use. + console.log(`Waiting until port ${ defaultPort } is closed...`); + waitUntilPortState(defaultPort, /* isOpen */ true, /* timeoutMs */ 15000, err => { + if (err) { + callback(err); + } else { + console.log(`Port ${ defaultPort } is now closed`); + callback(null); + } + }); + } + }); } } } diff --git a/test/templates/util/ports.ts b/test/templates/util/ports.ts new file mode 100644 index 0000000..5a124d6 --- /dev/null +++ b/test/templates/util/ports.ts @@ -0,0 +1,23 @@ +import * as portastic from 'portastic'; +const pollInterval = 500; + +export function waitUntilPortState(port: number, isOpen: boolean, timeoutMs: number, callback: (err: any) => void) { + if (!(timeoutMs > 0)) { + throw new Error(`Timed out after ${ timeoutMs }ms waiting for port ${ port } to become ${ isOpen ? 'free' : 'in use' }`); + } + + portastic.test(port).then( + actualIsOpenState => { + if (actualIsOpenState === isOpen) { + // Desired state is reached + callback(null); + } else { + // Wait longer + setTimeout(() => { + waitUntilPortState(port, isOpen, timeoutMs - pollInterval, callback); + }, pollInterval); + } + }, + callback + ) +}