Skip to content

Commit

Permalink
Implement tests for chrome launcher.
Browse files Browse the repository at this point in the history
* Refactor code bits to make testable.
* Add the ability for a user to pass in module mocks to enable testing.
  • Loading branch information
samccone committed Jun 5, 2017
1 parent 32fcb44 commit 0fe2fa8
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 38 deletions.
48 changes: 28 additions & 20 deletions chrome-launcher/chrome-launcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ export interface LaunchedChrome {
kill: () => Promise<{}>;
}

export interface ModuleOverrides { fs?: typeof fs, rimraf?: typeof rimraf, }

export async function launch(opts: Options = {}): Promise<LaunchedChrome> {
opts.handleSIGINT = defaults(opts.handleSIGINT, true);

Expand All @@ -73,17 +75,23 @@ export class Launcher {
private pollInterval: number = 500;
private pidFile: string;
private startingUrl: string;
private userDataDir?: string;
private outFile?: number;
private errFile?: number;
private chromePath?: string;
private chromeFlags: string[];
private requestedPort?: number;
chrome?: childProcess.ChildProcess;
private chrome?: childProcess.ChildProcess;
private fs: typeof fs;
private rimraf: typeof rimraf;

userDataDir?: string;
port?: number;
pid?: number;

constructor(private opts: Options = {}) {
constructor(private opts: Options = {}, moduleOverrides: ModuleOverrides = {}) {
this.fs = moduleOverrides.fs || fs;
this.rimraf = moduleOverrides.rimraf || rimraf;

// choose the first one (default)
this.startingUrl = defaults(this.opts.startingUrl, 'about:blank');
this.chromeFlags = defaults(this.opts.chromeFlags, []);
Expand All @@ -108,16 +116,20 @@ export class Launcher {
return flags;
}

private prepare() {
// Wrapper function to enable easy testing.
makeTmpDir() {
return makeTmpDir();
}

prepare() {
const platform = process.platform as SupportedPlatforms;
if (!_SUPPORTED_PLATFORMS.has(platform)) {
throw new Error(`Platform ${platform} is not supported`);
}

this.userDataDir = this.opts.userDataDir || makeTmpDir();

this.outFile = fs.openSync(`${this.userDataDir}/chrome-out.log`, 'a');
this.errFile = fs.openSync(`${this.userDataDir}/chrome-err.log`, 'a');
this.userDataDir = this.opts.userDataDir || this.makeTmpDir();
this.outFile = this.fs.openSync(`${this.userDataDir}/chrome-out.log`, 'a');
this.errFile = this.fs.openSync(`${this.userDataDir}/chrome-err.log`, 'a');

// fix for Node4
// you can't pass a fd to fs.writeFileSync
Expand Down Expand Up @@ -180,7 +192,7 @@ export class Launcher {
execPath, this.flags, {detached: true, stdio: ['ignore', this.outFile, this.errFile]});
this.chrome = chrome;

fs.writeFileSync(this.pidFile, chrome.pid.toString());
this.fs.writeFileSync(this.pidFile, chrome.pid.toString());

log.verbose('ChromeLauncher', `Chrome running with pid ${chrome.pid} on port ${this.port}.`);
resolve(chrome.pid);
Expand Down Expand Up @@ -249,10 +261,7 @@ export class Launcher {
return new Promise(resolve => {
if (this.chrome) {
this.chrome.on('close', () => {
// Only clean up the tmp dir if we created it.
if (this.opts.userDataDir === undefined) {
this.destroyTmp().then(resolve);
}
this.destroyTmp().then(resolve);
});

log.log('ChromeLauncher', 'Killing all Chrome Instances');
Expand All @@ -274,25 +283,24 @@ export class Launcher {
});
}

private destroyTmp() {
destroyTmp() {
return new Promise(resolve => {
if (!this.userDataDir) {
// Only clean up the tmp dir if we created it.
if (this.userDataDir === undefined || this.opts.userDataDir !== undefined) {
return resolve();
}

log.verbose('ChromeLauncher', `Removing ${this.userDataDir}`);

if (this.outFile) {
fs.closeSync(this.outFile);
this.fs.closeSync(this.outFile);
delete this.outFile;
}

if (this.errFile) {
fs.closeSync(this.errFile);
this.fs.closeSync(this.errFile);
delete this.errFile;
}

rimraf(this.userDataDir, () => resolve());
this.rimraf(this.userDataDir, () => resolve());
});
}
};
89 changes: 71 additions & 18 deletions chrome-launcher/test/chrome-launcher-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,85 @@

import {Launcher} from '../chrome-launcher';

import * as assert from 'assert';

const log = require('../../lighthouse-core/lib/log');
const assert = require('assert');
const fsMock = {
openSync: () => {},
closeSync: () => {}
};

describe('Launcher', () => {
it('doesn\'t fail when killed twice', () => {
it('accepts and uses a custom path', async () => {
log.setLevel('error');
let callCount = 0;
const rimrafMock = (_: string, done: () => void) => {
callCount++;
done();
};

const chromeInstance =
new Launcher({userDataDir: 'some_path'}, {fs: fsMock as any, rimraf: rimrafMock as any});

chromeInstance.prepare();

await chromeInstance.destroyTmp();
assert.strictEqual(callCount, 0);
});

it('cleans up the tmp dir after closing', async () => {
log.setLevel('error');
let callCount = 0;
const rimrafMock = (_: string, done: () => void) => {
callCount++;
done();
};

const chromeInstance = new Launcher({}, {fs: fsMock as any, rimraf: rimrafMock as any});

chromeInstance.prepare();
await chromeInstance.destroyTmp();
assert.strictEqual(callCount, 1);
});

it('does not delete created directory when custom path passed', () => {
log.setLevel('error');

const chromeInstance = new Launcher({userDataDir: 'some_path'}, {fs: fsMock as any});

chromeInstance.prepare();
assert.equal(chromeInstance.userDataDir, 'some_path');
});

it('defaults to genering a tmp dir when no data dir is passed', () => {
log.setLevel('error');
const chromeInstance = new Launcher({}, {fs: fsMock as any});
const originalMakeTmp = chromeInstance.makeTmpDir;
chromeInstance.makeTmpDir = () => 'tmp_dir'
chromeInstance.prepare()
assert.equal(chromeInstance.userDataDir, 'tmp_dir');

// Restore the original fn.
chromeInstance.makeTmpDir = originalMakeTmp;
});

it('doesn\'t fail when killed twice', async () => {
log.setLevel('error');
const chromeInstance = new Launcher();
return chromeInstance.launch().then(() => {
log.setLevel();
return Promise.all([chromeInstance.kill(), chromeInstance.kill()]);
});
await chromeInstance.launch();
log.setLevel();
await chromeInstance.kill();
await chromeInstance.kill();
});

it('doesn\'t launch multiple chrome processes', () => {
it('doesn\'t launch multiple chrome processes', async () => {
log.setLevel('error');
const chromeInstance = new Launcher();
let pid: number;
return chromeInstance.launch()
.then(() => {
pid = chromeInstance.chrome!.pid;
return chromeInstance.launch();
})
.then(() => {
log.setLevel();
assert.strictEqual(pid, chromeInstance.chrome!.pid);
return chromeInstance.kill();
});
await chromeInstance.launch();
let pid = chromeInstance.pid!;
await chromeInstance.launch();
log.setLevel();
assert.strictEqual(pid, chromeInstance.pid);
await chromeInstance.kill();
});
});

0 comments on commit 0fe2fa8

Please sign in to comment.