Skip to content

Commit

Permalink
Merge f037daf into e341ea4
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas Scholtes committed Jun 13, 2019
2 parents e341ea4 + f037daf commit 16d1984
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 139 deletions.
@@ -0,0 +1 @@
module.exports.testShouldFail = false;
@@ -0,0 +1,8 @@
// This will be replaced in the tests
const testShouldFail = true;

it('checks dependency', () => {
if (testShouldFail === true) {
throw new Error('test failed');
}
});
@@ -0,0 +1,7 @@
const dependency = require('./lib/dependency');

it('checks dependency', () => {
if (dependency.testShouldFail === true) {
throw new Error('test failed');
}
});
66 changes: 33 additions & 33 deletions test/integration/helpers.js
Expand Up @@ -113,39 +113,6 @@ module.exports = {
opts
);
},
/**
* Invokes the mocha binary with the given arguments fixture using
* the JSON reporter. Returns the child process and a promise for the
* results of running the command. The result includes the **raw**
* string output, as well as exit code.
*
* By default, `STDERR` is ignored. Pass `{stdio: 'pipe'}` as `opts` if you
* want it as part of the result output.
*
* @param {string[]} args - Array of args
* @param {Object} [opts] - Opts for `spawn()`
* @returns {[ChildProcess|Promise<Result>]}
*/
runMochaJSONRawAsync: function(args, opts) {
args = args || [];

let childProcess;
const resultPromise = new Promise((resolve, reject) => {
childProcess = invokeSubMocha(
[...args, '--reporter', 'json'],
function(err, resRaw) {
if (err) {
reject(err);
} else {
resolve(resRaw);
}
},
opts
);
});

return [childProcess, resultPromise];
},

/**
* regular expression used for splitting lines based on new line / dot symbol.
Expand Down Expand Up @@ -174,6 +141,8 @@ module.exports = {
*/
invokeMocha: invokeMocha,

invokeMochaAsync: invokeMochaAsync,

/**
* Resolves the path to a fixture to the full path.
*/
Expand Down Expand Up @@ -227,6 +196,37 @@ function invokeMocha(args, fn, opts) {
);
}

/**
* Invokes the mocha binary with the given arguments. Returns the
* child process and a promise for the results of running the
* command. The promise resolves when the child process exits. The
* result includes the **raw** string output, as well as exit code.
*
* By default, `STDERR` is ignored. Pass `{stdio: 'pipe'}` as `opts` if you
* want it as part of the result output.
*
* @param {string[]} args - Array of args
* @param {Object} [opts] - Opts for `spawn()`
* @returns {[ChildProcess|Promise<Result>]}
*/
function invokeMochaAsync(args, opts) {
let mochaProcess;
const resultPromise = new Promise((resolve, reject) => {
mochaProcess = _spawnMochaWithListeners(
defaultArgs([MOCHA_EXECUTABLE].concat(args)),
(err, result) => {
if (err) {
reject(err);
} else {
resolve(result);
}
},
opts
);
});
return [mochaProcess, resultPromise];
}

function invokeSubMocha(args, fn, opts) {
if (typeof args === 'function') {
opts = fn;
Expand Down
225 changes: 119 additions & 106 deletions test/integration/options/watch.spec.js
Expand Up @@ -4,9 +4,6 @@ const fs = require('fs-extra');
const os = require('os');
const path = require('path');
const helpers = require('../helpers');
const runMochaJSONRawAsync = helpers.runMochaJSONRawAsync;

const sigintExitCode = 130;

describe('--watch', function() {
describe('when enabled', function() {
Expand All @@ -15,11 +12,6 @@ describe('--watch', function() {

beforeEach(function() {
this.tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mocha-'));

const fixtureSource = helpers.DEFAULT_FIXTURE;

this.testFile = path.join(this.tempDir, 'test.js');
fs.copySync(fixtureSource, this.testFile);
});

afterEach(function() {
Expand All @@ -28,79 +20,39 @@ describe('--watch', function() {
}
});

it('should show the cursor and signal correct exit code, when watch process is terminated', function() {
// Feature works but SIMULATING the signal (ctrl+c) via child process
// does not work due to lack of POSIX signal compliance on Windows.
if (process.platform === 'win32') {
this.skip();
}

const [mocha, resultPromise] = runMochaJSONRawAsync([
helpers.DEFAULT_FIXTURE,
'--watch'
]);

return sleep(1000)
.then(() => {
mocha.kill('SIGINT');
return resultPromise;
})
.then(data => {
const expectedCloseCursor = '\u001b[?25h';
expect(data.output, 'to contain', expectedCloseCursor);

expect(data.code, 'to be', sigintExitCode);
});
});

it('reruns test when watched test file is touched', function() {
const [mocha, outputPromise] = runMochaJSONWatchAsync([this.testFile], {
cwd: this.tempDir
});
const testFile = path.join(this.tempDir, 'test.js');
copyFixture('__default__', testFile);

return expect(
sleep(1000)
.then(() => {
touchFile(this.testFile);
return sleep(1000);
})
.then(() => {
mocha.kill('SIGINT');
return outputPromise;
}),
'when fulfilled',
'to have length',
2
);
return runMochaWatch([testFile], this.tempDir, () => {
touchFile(testFile);
}).then(results => {
expect(results, 'to have length', 2);
});
});

it('reruns test when file matching extension is touched', function() {
const testFile = path.join(this.tempDir, 'test.js');
copyFixture('__default__', testFile);

const watchedFile = path.join(this.tempDir, 'file.xyz');
touchFile(watchedFile);
const [mocha, outputPromise] = runMochaJSONWatchAsync(
[this.testFile, '--extension', 'xyz,js'],
{
cwd: this.tempDir
}
);

return expect(
sleep(1000)
.then(() => {
touchFile(watchedFile);
return sleep(1000);
})
.then(() => {
mocha.kill('SIGINT');
return outputPromise;
}),
'when fulfilled',
'to have length',
2
);
return runMochaWatch(
[testFile, '--extension', 'xyz,js'],
this.tempDir,
() => {
touchFile(watchedFile);
}
).then(results => {
expect(results, 'to have length', 2);
});
});

it('ignores files in "node_modules" and ".git"', function() {
it('ignores files in "node_modules" and ".git" by default', function() {
const testFile = path.join(this.tempDir, 'test.js');
copyFixture('__default__', testFile);

const nodeModulesFile = path.join(
this.tempDir,
'node_modules',
Expand All @@ -111,50 +63,91 @@ describe('--watch', function() {
touchFile(gitFile);
touchFile(nodeModulesFile);

const [mocha, outputPromise] = runMochaJSONWatchAsync(
[this.testFile, '--extension', 'xyz,js'],
{
cwd: this.tempDir
return runMochaWatch(
[testFile, '--extension', 'xyz,js'],
this.tempDir,
() => {
touchFile(gitFile);
touchFile(nodeModulesFile);
}
);
).then(results => {
expect(results, 'to have length', 1);
});
});

return expect(
sleep(1000)
.then(() => {
touchFile(gitFile);
touchFile(nodeModulesFile);
})
.then(() => sleep(1000))
.then(() => {
mocha.kill('SIGINT');
return outputPromise;
}),
'when fulfilled',
'to have length',
1
);
it('reloads test files when they change', function() {
const testFile = path.join(this.tempDir, 'test.js');
copyFixture('options/watch/test-file-change', testFile);

return runMochaWatch([testFile], this.tempDir, () => {
replaceFileContents(
testFile,
'testShouldFail = true',
'testShouldFail = false'
);
}).then(results => {
expect(results, 'to have length', 2);
expect(results[0].passes, 'to have length', 0);
expect(results[0].failures, 'to have length', 1);
expect(results[1].passes, 'to have length', 1);
expect(results[1].failures, 'to have length', 0);
});
});

it('reloads test dependencies when they change', function() {
const testFile = path.join(this.tempDir, 'test.js');
copyFixture('options/watch/test-with-dependency', testFile);

const dependency = path.join(this.tempDir, 'lib', 'dependency.js');
copyFixture('options/watch/dependency', dependency);

return runMochaWatch([testFile], this.tempDir, () => {
replaceFileContents(
dependency,
'module.exports.testShouldFail = false',
'module.exports.testShouldFail = true'
);
}).then(results => {
expect(results, 'to have length', 2);
expect(results[0].passes, 'to have length', 1);
expect(results[0].failures, 'to have length', 0);
expect(results[1].passes, 'to have length', 0);
expect(results[1].failures, 'to have length', 1);
});
});
});
});

/**
* Invokes the mocha binary with the `--watch` argument for the given fixture.
* Runs the mocha binary in watch mode calls `change` and returns the
* JSON reporter output.
*
* Returns child process and a promise for the test results. The test results
* are an array of JSON objects generated by the JSON reporter.
* The function starts mocha with the given arguments and `--watch` and
* waits until the first test run has completed. Then it calls `change`
* and waits until the second test run has been completed. Mocha is
* killed and the list of JSON outputs is returned.
*/
function runMochaJSONWatchAsync(args, spawnOpts) {
args = [...args, '--watch'];
const [mocha, mochaDone] = runMochaJSONRawAsync(args, spawnOpts);
const testResults = mochaDone.then(data => {
const testResults = data.output
// eslint-disable-next-line no-control-regex
.replace(/\u001b\[\?25./g, '')
.split('\u001b[2K')
.map(x => JSON.parse(x));
return testResults;
});
return [mocha, testResults];
function runMochaWatch(args, cwd, change) {
const [mochaProcess, resultPromise] = helpers.invokeMochaAsync(
[...args, '--watch', '--reporter', 'json'],
{cwd}
);

return sleep(1000)
.then(() => change())
.then(() => sleep(1000))
.then(() => {
mochaProcess.kill('SIGINT');
return resultPromise;
})
.then(data => {
const testResults = data.output
// eslint-disable-next-line no-control-regex
.replace(/\u001b\[\?25./g, '')
.split('\u001b[2K')
.map(x => JSON.parse(x));
return testResults;
});
}

/**
Expand All @@ -166,6 +159,26 @@ function touchFile(file) {
fs.appendFileSync(file, ' ');
}

/**
* Synchronously eplace all substrings matched by `pattern` with
* `replacement` in the file’s content.
*/
function replaceFileContents(file, pattern, replacement) {
const contents = fs.readFileSync(file, 'utf-8');
const newContents = contents.replace(pattern, replacement);
fs.writeFileSync(file, newContents, 'utf-8');
}

/**
* Synchronously copy a fixture to the given destion file path. Creates
* parent directories of the destination path if necessary.
*/
function copyFixture(fixtureName, dest) {
const fixtureSource = helpers.resolveFixturePath(fixtureName);
fs.ensureDirSync(path.dirname(dest));
fs.copySync(fixtureSource, dest);
}

function sleep(time) {
return new Promise(resolve => {
setTimeout(resolve, time);
Expand Down

0 comments on commit 16d1984

Please sign in to comment.