Skip to content

Commit

Permalink
feat: read tests sequentially in worker
Browse files Browse the repository at this point in the history
  • Loading branch information
j0tunn committed Oct 31, 2022
1 parent 1db9cdc commit 78e8485
Show file tree
Hide file tree
Showing 7 changed files with 283 additions and 75 deletions.
26 changes: 8 additions & 18 deletions lib/worker/runner/caching-test-parser.js
Expand Up @@ -2,7 +2,7 @@

const {EventEmitter} = require('events');
const {passthroughEvent} = require('../../core/events/utils');
const TestParser = require('../../test-reader/mocha-test-parser');
const SequenceTestParser = require('./sequence-test-parser');
const TestCollection = require('../../test-collection');
const RunnerEvents = require('../constants/runner-events');

Expand All @@ -17,7 +17,11 @@ module.exports = class CachingTestParser extends EventEmitter {
this._config = config;
this._cache = {};

TestParser.prepare();
this._sequenceTestParser = SequenceTestParser.create(config);
passthroughEvent(this._sequenceTestParser, this, [
RunnerEvents.BEFORE_FILE_READ,
RunnerEvents.AFTER_FILE_READ
]);
}

async parse({file, browserId}) {
Expand All @@ -26,8 +30,9 @@ module.exports = class CachingTestParser extends EventEmitter {
return cached;
}

const tests = await this._parse({file, browserId});
const tests = await this._sequenceTestParser.parse({file, browserId});
this._putToCache(tests, {file, browserId});

this.emit(RunnerEvents.AFTER_TESTS_READ, TestCollection.create({[browserId]: tests}, this._config));

return tests;
Expand All @@ -41,19 +46,4 @@ module.exports = class CachingTestParser extends EventEmitter {
this._cache[browserId] = this._cache[browserId] || {};
this._cache[browserId][file] = tests;
}

async _parse({file, browserId}) {
const parser = TestParser.create(browserId, this._config);

passthroughEvent(parser, this, [
RunnerEvents.BEFORE_FILE_READ,
RunnerEvents.AFTER_FILE_READ
]);

parser.applyConfigController();

await parser.loadFiles(file);

return parser.parse();
}
};
29 changes: 29 additions & 0 deletions lib/worker/runner/sequence-test-parser.js
@@ -0,0 +1,29 @@
'use strict';

const {EventEmitter} = require('events');
const {passthroughEvent} = require('../../core/events/utils');
const SimpleTestParser = require('./simple-test-parser');
const RunnerEvents = require('../constants/runner-events');
const fastq = require('fastq');

module.exports = class SequenceTestParser extends EventEmitter {
static create(...args) {
return new this(...args);
}

constructor(config) {
super();

this._parser = SimpleTestParser.create(config);
passthroughEvent(this._parser, this, [
RunnerEvents.BEFORE_FILE_READ,
RunnerEvents.AFTER_FILE_READ
]);

this._queue = fastq.promise((fn) => fn(), 1);
}

async parse({file, browserId}) {
return this._queue.push(() => this._parser.parse({file, browserId}));
}
};
35 changes: 35 additions & 0 deletions lib/worker/runner/simple-test-parser.js
@@ -0,0 +1,35 @@
'use strict';

const {EventEmitter} = require('events');
const {passthroughEvent} = require('../../core/events/utils');
const TestParser = require('../../test-reader/mocha-test-parser');
const RunnerEvents = require('../constants/runner-events');

module.exports = class SimpleTestParser extends EventEmitter {
static create(...args) {
return new this(...args);
}

constructor(config) {
super();

this._config = config;

TestParser.prepare();
}

async parse({file, browserId}) {
const parser = TestParser.create(browserId, this._config);

passthroughEvent(parser, this, [
RunnerEvents.BEFORE_FILE_READ,
RunnerEvents.AFTER_FILE_READ
]);

parser.applyConfigController();

await parser.loadFiles(file);

return parser.parse();
}
};
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -46,6 +46,7 @@
"chalk": "^1.1.1",
"clear-require": "^1.0.1",
"debug": "^2.6.9",
"fastq": "^1.13.0",
"fs-extra": "^5.0.0",
"gemini-configparser": "^1.3.0",
"glob-extra": "^5.0.2",
Expand Down
74 changes: 17 additions & 57 deletions test/lib/worker/runner/caching-test-parser.js
@@ -1,8 +1,8 @@
'use strict';

const CachingTestParser = require('lib/worker/runner/caching-test-parser');
const SequenceTestParser = require('lib/worker/runner/sequence-test-parser');
const RunnerEvents = require('lib/worker/constants/runner-events');
const TestParser = require('lib/test-reader/mocha-test-parser');
const TestCollection = require('lib/test-collection');
const {makeConfigStub, makeTest} = require('../../../utils');

Expand All @@ -15,45 +15,35 @@ describe('worker/runner/caching-test-parser', () => {
};

beforeEach(() => {
sandbox.stub(TestParser, 'prepare');
sandbox.stub(TestParser, 'create').returns(Object.create(TestParser.prototype));
sandbox.stub(TestParser.prototype, 'applyConfigController').returnsThis();
sandbox.stub(TestParser.prototype, 'loadFiles').resolvesThis();
sandbox.stub(TestParser.prototype, 'parse').returns([]);
sandbox.stub(SequenceTestParser, 'create').returns(Object.create(SequenceTestParser.prototype));
sandbox.stub(SequenceTestParser.prototype, 'parse').resolves([]);
});

afterEach(() => sandbox.restore());

describe('constructor', () => {
it('should prepare test parser', () => {
mkCachingParser_();

assert.calledOnce(TestParser.prepare);
});
});

describe('parse', () => {
it('should create test parser', async () => {
it('should create sequnce test parser', () => {
const config = makeConfigStub();
const cachingParser = mkCachingParser_({config});

await cachingParser.parse({browserId: 'bro'});
mkCachingParser_({config});

assert.calledOnceWith(TestParser.create, 'bro', config);
assert.calledOnceWith(SequenceTestParser.create, config);
});
});

describe('parse', () => {
[
'BEFORE_FILE_READ',
'AFTER_FILE_READ'
].forEach((event) => {
it(`should passthrough ${event} event from caching test parser`, async () => {
it(`should passthrough ${event} event from seqeunce test parser`, async () => {
const onEvent = sinon.spy().named(`on${event}`);
const cachingParser = mkCachingParser_()
.on(RunnerEvents[event], onEvent);

TestParser.prototype.parse.callsFake(function() {
SequenceTestParser.prototype.parse.callsFake(function() {
this.emit(RunnerEvents[event], {foo: 'bar'});
return [];
return Promise.resolve([]);
});

await cachingParser.parse({});
Expand All @@ -62,40 +52,10 @@ describe('worker/runner/caching-test-parser', () => {
});
});

it('should apply config controller before loading file', async () => {
const cachingParser = mkCachingParser_();

await cachingParser.parse({});

assert.callOrder(
TestParser.prototype.applyConfigController,
TestParser.prototype.loadFiles
);
});

it('should load file', async () => {
const cachingParser = mkCachingParser_();

await cachingParser.parse({file: 'some/file.js'});

assert.calledOnceWith(TestParser.prototype.loadFiles, 'some/file.js');
});

it('should load file before parse', async () => {
const cachingParser = mkCachingParser_();

await cachingParser.parse({});

assert.callOrder(
TestParser.prototype.loadFiles,
TestParser.prototype.parse
);
});

it('should return parsed tests', async () => {
const cachingParser = mkCachingParser_();
const tests = [makeTest(), makeTest()];
TestParser.prototype.parse.returns(tests);
SequenceTestParser.prototype.parse.resolves(tests);

const result = await cachingParser.parse({});

Expand All @@ -105,13 +65,13 @@ describe('worker/runner/caching-test-parser', () => {
it('should parse each file in each browser only once', async () => {
const cachingParser = mkCachingParser_();
const tests = [makeTest(), makeTest()];
TestParser.prototype.parse.returns(tests);
SequenceTestParser.prototype.parse.resolves(tests);

await cachingParser.parse({file: 'some/file.js', browserId: 'bro'});
const result = await cachingParser.parse({file: 'some/file.js', browserId: 'bro'});

assert.deepEqual(result, tests);
assert.calledOnce(TestParser.prototype.parse);
assert.calledOnce(SequenceTestParser.prototype.parse);
});

it('should parse same file in different browsers', async () => {
Expand All @@ -120,7 +80,7 @@ describe('worker/runner/caching-test-parser', () => {
await cachingParser.parse({file: 'some/file.js', browserId: 'bro1'});
await cachingParser.parse({file: 'some/file.js', browserId: 'bro2'});

assert.calledTwice(TestParser.prototype.parse);
assert.calledTwice(SequenceTestParser.prototype.parse);
});

it('should parse different files in same browser', async () => {
Expand All @@ -129,7 +89,7 @@ describe('worker/runner/caching-test-parser', () => {
await cachingParser.parse({file: 'some/file.js', browserId: 'bro'});
await cachingParser.parse({file: 'other/file.js', browserId: 'bro'});

assert.calledTwice(TestParser.prototype.parse);
assert.calledTwice(SequenceTestParser.prototype.parse);
});

it('should emit AFTER_TESTS_READ event on parse', async () => {
Expand All @@ -145,7 +105,7 @@ describe('worker/runner/caching-test-parser', () => {
it('should create test collection with parsed tests', async () => {
const cachingParser = mkCachingParser_();
const tests = [makeTest(), makeTest()];
TestParser.prototype.parse.returns(tests);
SequenceTestParser.prototype.parse.resolves(tests);

sinon.spy(TestCollection, 'create');

Expand Down
83 changes: 83 additions & 0 deletions test/lib/worker/runner/sequence-test-parser.js
@@ -0,0 +1,83 @@
'use strict';

const SequenceTestParser = require('lib/worker/runner/sequence-test-parser');
const SimpleTestParser = require('lib/worker/runner/simple-test-parser');
const RunnerEvents = require('lib/worker/constants/runner-events');
const {makeConfigStub, makeTest} = require('../../../utils');
const Promise = require('bluebird');

describe('worker/runner/sequence-test-parser', () => {
const sandbox = sinon.sandbox.create();

const mkSequenceParser_ = (opts = {}) => {
const config = opts.config || makeConfigStub();
return SequenceTestParser.create(config);
};

beforeEach(() => {
sandbox.stub(SimpleTestParser, 'create').returns(Object.create(SimpleTestParser.prototype));
sandbox.stub(SimpleTestParser.prototype, 'parse').resolves([]);
});

afterEach(() => sandbox.restore());

describe('constructor', () => {
it('should create simple test parser', () => {
const config = makeConfigStub();

mkSequenceParser_({config});

assert.calledOnceWith(SimpleTestParser.create, config);
});
});

describe('parse', () => {
[
'BEFORE_FILE_READ',
'AFTER_FILE_READ'
].forEach((event) => {
it(`should passthrough ${event} event from inner test parser`, async () => {
const onEvent = sinon.spy().named(`on${event}`);
const sequenceParser = mkSequenceParser_()
.on(RunnerEvents[event], onEvent);

SimpleTestParser.prototype.parse.callsFake(function() {
this.emit(RunnerEvents[event], {foo: 'bar'});
return Promise.resolve([]);
});

await sequenceParser.parse({});

assert.calledOnceWith(onEvent, {foo: 'bar'});
});
});

it('should return parsed tests', async () => {
const sequenceParser = mkSequenceParser_();
const tests = [makeTest(), makeTest()];
SimpleTestParser.prototype.parse.resolves(tests);

const result = await sequenceParser.parse({});

assert.deepEqual(result, tests);
});

it('should parse tests sequentially', async () => {
const calls = [];

SimpleTestParser.prototype.parse.callsFake(async () => {
calls.push('parse');
await Promise.delay(1);
calls.push('afterParse');

return [];
});

const sequenceParser = mkSequenceParser_();

await Promise.all([sequenceParser.parse({}), sequenceParser.parse({})]);

assert.deepEqual(calls, ['parse', 'afterParse', 'parse', 'afterParse']);
});
});
});

0 comments on commit 78e8485

Please sign in to comment.