Skip to content

Commit

Permalink
feat: support "processes" to handle extra procs
Browse files Browse the repository at this point in the history
  • Loading branch information
dbushong committed Oct 20, 2021
1 parent 9c74463 commit 715be81
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 20 deletions.
34 changes: 20 additions & 14 deletions lib/processes.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const Proxy = require('./processes/proxy');
const App = require('./processes/application');
const Selenium = require('./processes/selenium');
const ChromeDriver = require('./processes/chromedriver');
const { checkExtraProcs } = require('./processes/extra');

const CHROME = 'chrome';

Expand Down Expand Up @@ -87,7 +88,7 @@ function unrefAll(procs) {
function launchAllProcesses(config) {
const launchApp = config.getBool('launch', false);

function launchWithAppPort(appPort) {
async function launchWithAppPort(appPort) {
config.set('app.port', appPort);

const browserName = config.get('browser');
Expand Down Expand Up @@ -115,6 +116,11 @@ function launchAllProcesses(config) {
debug('Using existing selenium server', seleniumUrl);
}

const extraProcs = config.get('processes', {});
if (extraProcs) {
servers.push(...(await checkExtraProcs(extraProcs)));
}

if (browserName === CHROME) {
const chromePath = config.get('chrome.command', null);
if (chromePath) {
Expand Down Expand Up @@ -149,19 +155,19 @@ function launchAllProcesses(config) {

if (process.getuid() === 0) args.push('--no-sandbox');

return isChromeInDocker(browserName)
.then(() => {
debug('Running in docker env');
args.push('--disable-dev-shm-usage');
})
.catch(() => {
debug('Not running in docker env');
})
.then(() => {
config.set('desiredCapabilities.chromeOptions.args', args);
return spawnServers(config, servers);
})
.then(unrefAll);
if (
await isChromeInDocker(browserName).then(
() => true,
() => false
)
) {
debug('Running in docker env');
args.push('--disable-dev-shm-usage');
} else {
debug('Not running in docker env');
}

config.set('desiredCapabilities.chromeOptions.args', args);
}

return spawnServers(config, servers).then(unrefAll);
Expand Down
111 changes: 111 additions & 0 deletions lib/processes/extra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Copyright (c) 2015, Groupon, Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of GROUPON nor the names of its contributors may be
* used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

'use strict';

/*
Given configuration like:
{
// ...
processes: {
memcached: {
port: 11211,
command: 'memcached',
commandArgs: ['-u', 'memcached', '-d'],
reuseExisting: true,
},
// ...
}
}
This will turn them into server configuration bits and start things up
for you; skipping them if something's already listening on that port
*/

const { isAvailable } = require('find-open-port');
const debug = require('debug')('testium-core:processes:extra');

/**
* @typedef Server
* @property {string} name
* @property {() => SubprocessOpts | Promise<SubprocessOpts>} getOptions
*
* @typedef SubprocessOpts
* @property {number} port
* ...and a bunch more options; see subprocess/README.md
*
* @typedef {SubprocessOpts & { reuseExisting?: boolean }} ExtraProcOpts
*/

/** @param {Record<string, ExtraProcOpts>} extraProcs */
async function checkExtraProcs(extraProcs) {
/** @type {Server[]} */
const servers = [];

/** @type {[ string, SubprocessOpts & { port: number } ]} */
const toCheck = [];

for (const [name, opts] of Object.entries(extraProcs)) {
const { reuseExisting = false, ...subPOpts } = opts;

if (!reuseExisting) {
servers.push({ name, getOptions: () => subPOpts });
continue;
}

if (!subPOpts.port) {
throw new Error(
`process.* with reuseExisting=true must include static port`
);
}

toCheck.push([name, subPOpts]);
}

if (toCheck.length > 0) {
await Promise.all(
toCheck.map(async ([name, opts]) => {
const { port } = opts;
if (await isAvailable(port)) {
servers.push({ name, getOptions: () => opts });
} else {
debug(
`Not starting reuseExisting process ${name}: port ${port} is in use`
);
}
})
);
}

return servers;
}
exports.checkExtraProcs = checkExtraProcs;
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"gofer": "^5.1.0",
"memfs": "^2.17.1",
"mocha": "^9.0.1",
"nlm": "^5.5.1",
"nlm": "^5.6.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.3.1",
"sinon": "^7.5.0",
Expand Down
14 changes: 10 additions & 4 deletions test/processes.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,22 @@ const { promisify } = require('util');
const execFile = promisify(require('child_process').execFile);

const assert = require('assertive');
const { each } = require('lodash');
const { patchFs } = require('fs-monkey');
const { ufs } = require('unionfs');
const { Volume } = require('memfs');
const sinon = require('sinon');
const debug = require('debug')('testium-core:test:processes');

const Config = require('../lib/config');
const launchAllProcesses = require('../lib/processes');

const HELLO_WORLD = path.resolve(__dirname, '../examples/hello-world');

function killProcs(procs) {
each(procs, ({ rawProcess }) => rawProcess.kill());
for (const [name, { rawProcess }] of Object.entries(procs)) {
debug(`killing ${name} process`);
rawProcess.kill();
}
}

describe('Launch all processes PhantomJS', () => {
Expand Down Expand Up @@ -74,18 +77,21 @@ describe('Launch all processes Chrome', () => {
procs = undefined;
});

afterEach(() => {
afterEach(async () => {
config = null;
process.env.DISPLAY = display;
if (procs) killProcs(procs);
});

it('launches all the processes', async () => {
config.set('processes', {
nc: { command: 'nc', commandArgs: ['-l', '%port%'] },
});
procs = await launchAllProcesses(config);
const procNames = Object.keys(procs).sort();
assert.deepEqual(
'Spawns app, chrome, and proxy',
['application', 'chromedriver', 'proxy'],
['application', 'chromedriver', 'nc', 'proxy'],
procNames
);
});
Expand Down
79 changes: 79 additions & 0 deletions test/processes/extra.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict';

const assert = require('assert');
const { createServer } = require('net');

const findOpenPort = require('find-open-port');

const { checkExtraProcs } = require('../../lib/processes/extra');

describe('extra processes in config', () => {
/** @type {import('net').Server | undefined} */
let server;
afterEach(async () => {
if (server) {
await new Promise(r => server.close(r));
server = undefined;
}
});

it('returns server objects for extra procs', async () => {
// don't parallelize to avoid collisions
const freePort1 = await findOpenPort.findPort();
const freePort2 = await findOpenPort.findPort();

server = createServer();
await new Promise(r => server.listen({ port: 0, host: '127.0.0.1' }, r));
const usedPort = server.address().port;

const processes = {
reuse1: {
command: 'nc',
commandArgs: ['-l', '%port%'],
reuseExisting: true,
port: freePort1,
},
reuse2: {
command: 'nc',
commandArgs: ['-l', '%port%'],
reuseExisting: true,
port: usedPort,
},
start1: {
command: 'nc',
commandArgs: ['-l', '%port%'],
port: freePort2,
},
start2: {
command: 'nc',
commandArgs: ['-l', '%port%'],
},
};

const servers = await checkExtraProcs(processes);

assert.ok(
servers.every(s => s.name !== 'reuse2'),
'reuseExisting with port in use should not be in servers list'
);

assert.strictEqual(servers.length, 3);
assert.deepStrictEqual(
servers.find(s => s.name === 'reuse1').getOptions(),
{
command: 'nc',
commandArgs: ['-l', '%port%'],
port: freePort1,
}
);
});

it('errors if configs are incorrect', async () => {
await assert.rejects(
checkExtraProcs({
kaboom: { reuseExisting: true },
}),
{ message: /reuseExisting.+static port/ }
);
});
});

0 comments on commit 715be81

Please sign in to comment.