Within my tests, I'm using require('child_process').exec to run "npm run start-app" which starts up a webpack-dev-server. After my tests have run I want to kill the process that I have started.
Currently I am able to successfully kill the process when running "mocha tests.js" directly. However, when I run the tests using "npm run tests" which calls "mocha tests.js", the process is not killed. Is this because the node process is blocking me from killing the process?
I am killing the process by discovering pids using ps-tree and using a kill -9 or taskkill depending on operating system.
test.after(function () {
psTree(appStartProcess.pid, function (err, children) {
if(/^win/.test(process.platform)) {
for(var i = 0; i < children.length; i++) {
exec('taskkill /pid ' + children[i].PID + ' /T /F');
}
} else{
cp.spawn('kill', ['-9'].concat(children.map(function (p) {
return p.PID;
})));
}
});
});
Any suggestions will be greatly appreciated !
You can use the ChildProcess returned by spawn after our test to kill it afterwards.
Given the following server to act as a running process:
#!/usr/bin/env node
require('http').createServer((_, res) => {
res.end('Hi from process')
}).listen(3000, console.log)
Your tests might look like this:
const { exec } = require('child_process')
// this would typically be done in a separate helper
// that mocha executes before your tests
before(function (done) {
// `this` is the shared mocha execution context
this.runningProcess = exec('test/server.js')
setTimeout(done, 500)
})
after(function () {
this.runningProcess.kill()
})
describe('my tests', () => {
it('properly initializes process', (done) => {
require('http').get({
port: 3000,
agent: false
}, (res) => {
let data = ''
res
.setEncoding('utf8')
.on('data', chunk => data += chunk)
.on('end', () => {
require('assert')(data === 'Hi from process')
done()
})
})
})
})
Alternately, you can use something outside of mocha (e.g. a custom script that starts your server, runs mocha, and then shuts your server down) but the concept is essentially the same.
Related
Context
I have spiked a TCP Echo server and am trying to write integration tests for it. I'm familiar with testing but not asynchronously.
Desired Behaviour
I would like my tests to spy on logs to verify that the code is being executed. Any asynchronous code should be handled properly, but this is where my understanding falls through.
Problem
I am getting asynchronous errors:
Cannot log after tests are done. Did you forget to wait for something async in your test?
Attempted to log "Server Ready".
Attempted to log "Client Connected".
And finally a warning:
A worker process has failed to exit gracefully and has been force exited. This is likely caused by tests leaking due to improper teardown. Try running with --detectOpenHandles to find leaks.
Code
import * as net from 'net';
export const runServer = async () => {
console.log('Initialising...');
const port: number = 4567;
const server = net.createServer((socket: net.Socket) => {
socket.write('Ready for input:\n');
console.log('Client Connected');
socket.on('data', (data) => {
echo(data, socket);
server.close();
})
socket.on('end', () => {
console.log('Client Disconnected');
});
});
server.listen(port, () => {
console.log('Server Ready');
});
server.on('error', (err) => {
console.log(err);
});
function echo(data: Buffer, socket: net.Socket) {
console.log('Input received')
socket.write(data)
};
return server;
}
Test
More such tests will be added when these are working as intended.
import * as index from '../../src/index';
import * as process from 'child_process';
test('the server accepts a connection', async () => {
const consoleSpy = spyOn(console, 'log');
try {
const server = await index.runServer();
await consoleConnect();
await consoleEcho();
await consoleStop();
} catch (error) {
console.log(error);
}
expect(consoleSpy).toHaveBeenCalledWith('Initialising...');
expect(consoleSpy).toHaveBeenCalledWith('Client Connected');
expect(consoleSpy).toHaveBeenCalledTimes(2);
})
const consoleConnect = async () => {
process.exec("netcat localhost 4567");
}
const consoleEcho = async () => {
process.exec("Echo!");
}
const consoleStop = async () => {
process.exec("\^C");
}
My overall question is how do I manage the events in such a way that the tests are able to run without async-related errors?
You are not properly waiting for your child processes to finish. Calls to exec return a ChildProcess object as documented here. They execute asynchronously so you need to wait for them to finish using the event emitter api.
Ex from docs
ls.on('exit', (code) => {
console.log(`child process exited with code ${code}`);
});
To use async await you need to convert to using a promise. Something like
return new Promise((resolve, reject) => {
ls.on('exit', (code) => {
resolve(code);
});
// Handle errors or ignore them. Whatevs.
}
You are closing your server on the first data event. You probably don't want to do that. At least wait until the end event so you have read all the data.
socket.on('data', (data) => {
echo(data, socket);
server.close(); // Remove this line
})
I use child_process to execute a sh file which download file with curl but when I use .exit() function the process didn't stop
const { spawn } = require('child_process');
let command = spawn('./download.sh', ["url", "name"])
setTimeout(() => {
command.kill()
}, 2000);
command.stdout.on('data', data => {
console.log("Data")
})
command.on('exit', (code, signal) => {
console.log("exit");
})
command.on('close', (code, signal) => {
console.log("close");
})
And this is my output
Data
Data
Data
Data
Exit
Data
Data
Data
...
This is my download.sh
curl {test url} --output test.mp4
But when I execute spawn with the curl command directly, the process stop so I don't understand why.
const { spawn } = require('child_process');
let command = spawn('curl', ["test url", "--output", "test.mp4"])
setTimeout(() => {
command.kill()
}, 2000);
command.stdout.on('data', data => {
console.log("Data")
})
command.on('exit', (code, signal) => {
console.log("exit");
})
command.on('close', (code, signal) => {
console.log("close");
})
And this is my output
Data
Data
Data
Data
Exit
Close
If someone has an idea
Since you mentioned that you receive all the data in both cases, there is no problem.
Processes are asynchronous, so there is no guarantee that you will receive exit before data is finished. exit simply means that your process has exited at that point (and thus, the download should be complete). If the process wrote something to standard output/standard error, that data might still be stored in some buffers until it is consumed.
What I test: An express server endpoints
My goal: automate API tests in a single script
What I do: I launch the express server in a NodeJS child process and would like to wait for it to be launched before the test suite is run (frisby.js endpoints testing)
What isn't working as expected: Test suite is launched before Promise resolution
I rely on the wait-on package which server polls and resolves once the resource(s) is/are available.
const awaitServer = async () => {
await waitOn({
resources: [`http://localhost:${PORT}`],
interval: 1000,
}).then(() => {
console.log('Server is running, launching test suite now!');
});
};
This function is used in the startServer function:
const startServer = async () => {
console.log(`Launching server http://localhost:${PORT} ...`);
// npmRunScripts is a thin wrapper around child_process.exec to easily access node_modules/.bin like in package.json scripts
await npmRunScripts(
`cross-env PORT=${PORT} node -r ts-node/register -r dotenv/config src/index.ts dotenv_config_path=.env-tests`
);
await awaitServer();
}
And finally, I use this in something like
describe('Endpoints' () => {
beforeAll(startTestServer);
// describes and tests here ...
});
Anyway, when I launch jest the 'Server is running, launching test suite now!' console.log never shows up and the test suite fails (as the server isn't running already). Why does jest starts testing as awaitServer obviously hasn't resolved yet?
The npmRunScripts function works fine as the test server is up and running a short while after the tests have failed. For this question's sake, here's how npmRunScripts resolves:
// From https://humanwhocodes.com/blog/2016/03/mimicking-npm-script-in-node-js/
const { exec } = require('child_process');
const { delimiter, join } = require('path');
const env = { ...process.env };
const binPath = join(__dirname, '../..', 'node_modules', '.bin');
env.PATH = `${binPath}${delimiter}${env.PATH}`;
/**
* Executes a CLI command with `./node_modules/.bin` in the scope like you
* would use in the `scripts` sections of a `package.json`
* #param cmd The actual command
*/
const npmRunScripts = (cmd, resolveProcess = false) =>
new Promise((resolve, reject) => {
if (typeof cmd !== 'string') {
reject(
new TypeError(
`npmRunScripts Error: cmd is a "${typeof cmd}", "string" expected.`
)
);
return;
}
if (cmd === '') {
reject(
new Error(`npmRunScripts Error: No command provided (cmd is empty).`)
);
return;
}
const subProcess = exec(
cmd,
{ cwd: process.cwd(), env }
);
if (resolveProcess) {
resolve(subProcess);
} else {
const cleanUp = () => {
subProcess.stdout.removeAllListeners();
subProcess.stderr.removeAllListeners();
};
subProcess.stdout.on('data', (data) => {
resolve(data);
cleanUp();
});
subProcess.stderr.on('data', (data) => {
reject(data);
cleanUp();
});
}
});
module.exports = npmRunScripts;
I found the solution. After trying almost anything, I didn't realize jest had a timeout setup which defaults at 5 seconds. So I increased this timeout and the tests now wait for the server promise to resolve.
I simply added jest.setTimeout(3 * 60 * 1000); before the test suite.
In my case, it caused by the flaw of the beforeAll part. Make sure the beforeAll doesn't contain any uncaught exceptions, otherwise it will behaves that the testing started without waiting for beforeAll resolves.
After much digging I found a reason for why my beforeAll didn't seem to be running before my tests. This might be obvious to some, but it wasn't to me.
If you have code in your describe outside an it or other beforeX or afterY, and that code is dependent on any beforeX, you'll run into this problem.
The problem is that code in your describe is run before any beforeX. Therefore, that code won't have access to the dependencies that are resolved in any beforeX.
For example:
describe('Outer describe', () => {
let server;
beforeAll(async () => {
// Set up the server before all tests...
server = await setupServer();
});
describe('Inner describe', () => {
// The below line is run before the above beforeAll, so server doesn't exist here yet!
const queue = server.getQueue(); // Error! server.getQueue is not a function
it('Should use the queue', () => {
queue.getMessage(); // Test fails due to error above
});
});
});
To me this seems unexpected, considering that code is run in the describe callback, so my impression was that that callback would be run after all beforeX outside the current describe.
It also seems this behavior won't be changed any time soon: https://github.com/facebook/jest/issues/4097
In newer versions of jest (at least >1.3.1) you can pass a done function to your beforeAll function and call it after everything is done:
beforeAll(async (done) => {
await myAsyncFunc();
done();
})
it("Some test", async () => {
// Runs after beforeAll
})
More discussions here: https://github.com/facebook/jest/issues/1256
Description
Executing child_process.fork from a vscode debug process fails to run with and returns exit code 12. Running the same test from a terminal session succeeds.
Sample Mocha Unit Test
import { expect } from 'chai';
import { fork } from 'child_process';
import path from 'path';
describe('Child Process Fork', () => {
it('Successfully Forks A Simple Process', (done) => {
const child = fork(path.join(__dirname, 'SimplyExit.js'), [], { stdio: 'pipe' });
child.on('exit', (data) => {
expect(data).to.equal(0);
done();
});
});
});
SimplyExit.js
process.exit(0);
Executing child_process.fork from a parent node process that was started with an inspect-brk option active will cause this error if you don't manually specify a different inspect-brk port or remove the option.
Here's the line of code in the node.js source that causes this to happen
Solution
Add execArgv: [] to your fork options to prevent the child process from inheriting the inspect-brk option. Here's the full working code.
import { expect } from 'chai';
import { fork } from 'child_process';
import path from 'path';
describe('Child Process Fork', () => {
it('Successfully Forks A Simple Process', (done) => {
const child = fork(path.join(__dirname, 'SimplyExit.js'), [], { stdio: 'pipe', execArgv: [] });
child.on('exit', (data) => {
expect(data).to.equal(0);
done();
});
});
});
I need to have a gulp task that starts the server, runs mocha tests against it and finally closes it. I have the following code:
var mocha = require('gulp-mocha');
var nodemon = require('nodemon');
gulp.task('my-integration-tests', function () {
return nodemon({ script: './server.js' })
.on('start', function () {
gulp.src(['./mySpecs.spec.js'])
.pipe(mocha());
});
});
The server is successfully started and the tests are run. However after this the process created by nodemon is still alive. Is there a way to instruct nodemon to close upon completion? Also having the application opening and closing in the same process as the mocha tests is not an option with the current configuration.
UPDATE:
Apart from ThomasBromans answer, I came up with this solution which seems to work in my case. Whenever gulp-mocha finishes the tests it will kind of emit an 'end' event. When this happens we only need to emit 'quit' on the child process then kill the main process, like so:
gulp.task('my-integration-tests', function () {
var childProc = nodemon({ script: './server.js' });
childProc.on('quit', function () {
console.log('event emitted, child process is being killed');
})
childProc.on('start', function () {
gulp.src(['./mySpecs.spec.js'])
.pipe(mocha())
.once('end', function () {
console.log('mocha stuff ended. time to kill processes');
childProc.emit('quit');
setTimeout(function () {
console.log('kill main process');
process.exit();
}, 1500);
});
});
});
Unfortunately I still need the timeout between the child process being killed and the killing of the main process, if I remove the timeout it happens that the child process remains hanging. This solution is of course open to improvements.
You can exit the process with process.exit(). Just add another .pipe. Your task will look like this:
gulp.task('my-integration-tests', function () {
return nodemon({ script: './server.js' })
.on('start', function () {
gulp.src(['./mySpecs.spec.js'])
.pipe(mocha())
.pipe(process.exit());
});
});
EDIT running tasks in a sequence (I am not sure this works without any changes):
var gulp = require('gulp'),
mocha = require('gulp-mocha'),
nodemon = require('nodemon'),
runSequence = require('run-sequence');
gulp.task('nodemon', function() {
return nodemon({script: './server.js'});
});
gulp.task('mocha', function() {
return mocha();
});
gulp.task('stop', function() {
process.exit();
});
gulp.task('my-integration-tests', function () {
runSequence('nodemon',
'mocha',
'stop');
});