Wait for port to be freed before continuing

This commit is contained in:
SteveSandersonMS
2016-12-15 20:25:20 +00:00
parent 200d80627c
commit 953f370336
3 changed files with 49 additions and 5 deletions

View File

@@ -18,6 +18,7 @@
"chai": "^3.5.0", "chai": "^3.5.0",
"cross-spawn": "^5.0.1", "cross-spawn": "^5.0.1",
"mkdirp": "^0.5.1", "mkdirp": "^0.5.1",
"portastic": "^1.0.1",
"rimraf": "^2.5.4", "rimraf": "^2.5.4",
"selenium-standalone": "^5.9.0", "selenium-standalone": "^5.9.0",
"tree-kill": "^1.1.0", "tree-kill": "^1.1.0",

View File

@@ -1,10 +1,12 @@
import * as childProcess from 'child_process'; import * as childProcess from 'child_process';
import * as path from 'path'; import * as path from 'path';
import * as readline from 'readline'; import * as readline from 'readline';
import { waitUntilPortState } from './ports';
const treeKill = require('tree-kill'); const treeKill = require('tree-kill');
const crossSpawn: typeof childProcess.spawn = require('cross-spawn'); 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 { export enum AspNetCoreEnviroment {
development, development,
@@ -50,7 +52,7 @@ export class AspNetProcess {
}); });
// Ensure the process isn't orphaned even if Node crashes before we're disposed // 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 // Also track whether it exited on its own already
this._process.on('exit', () => { this._process.on('exit', () => {
@@ -74,7 +76,7 @@ export class AspNetProcess {
public dispose(): Promise<any> { public dispose(): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
this._killProcessSync(err => { this._killAspNetProcess(err => {
if (err) { if (err) {
reject(err); reject(err);
} else { } 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) { if (!this._processHasExited) {
// It's important to kill the whole tree, because 'dotnet run' launches a separate 'dotnet exec' // It's important to kill the whole tree, because 'dotnet run' launches a separate 'dotnet exec'
// child process that would otherwise be left running // 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);
}
});
}
});
} }
} }
} }

View File

@@ -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
)
}