Skip to content

Commit

Permalink
test: do not inherit from the Node environment (#3348)
Browse files Browse the repository at this point in the history
  • Loading branch information
pavelfeldman authored Aug 7, 2020
1 parent b3091de commit f6d321f
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 140 deletions.
124 changes: 124 additions & 0 deletions test/jest/fixturePool.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/**
* Copyright Microsoft Corporation. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

const debug = require('debug');

const registrations = new Map();

class Fixture {
constructor(pool, name, scope, fn) {
this.pool = pool;
this.name = name;
this.scope = scope;
this.fn = fn;
this.deps = fixtureParameterNames(this.fn);
this.usages = new Set();
this.value = null;
}

async setup() {
for (const name of this.deps) {
await this.pool.setupFixture(name);
this.pool.instances.get(name).usages.add(this.name);
}

const params = {};
for (const n of this.deps)
params[n] = this.pool.instances.get(n).value;
let setupFenceFulfill;
let setupFenceReject;
const setupFence = new Promise((f, r) => { setupFenceFulfill = f; setupFenceReject = r; });
const teardownFence = new Promise(f => this._teardownFenceCallback = f);
debug('pw:test:hook')(`setup "${this.name}"`);
this._tearDownComplete = this.fn(params, async value => {
this.value = value;
setupFenceFulfill();
await teardownFence;
}).catch(e => setupFenceReject(e));
await setupFence;
this._setup = true;
}

async teardown() {
if (this._teardown)
return;
this._teardown = true;
for (const name of this.usages) {
const fixture = this.pool.instances.get(name);
if (!fixture)
continue;
await fixture.teardown();
}
if (this._setup) {
debug('pw:test:hook')(`teardown "${this.name}"`);
this._teardownFenceCallback();
}
await this._tearDownComplete;
this.pool.instances.delete(this.name);
}
}

class FixturePool {
constructor() {
this.instances = new Map();
}

async setupFixture(name) {
let fixture = this.instances.get(name);
if (fixture)
return fixture;

if (!registrations.has(name))
throw new Error('Unknown fixture: ' + name);
const { scope, fn } = registrations.get(name);
fixture = new Fixture(this, name, scope, fn);
this.instances.set(name, fixture);
await fixture.setup();
return fixture;
}

async teardownScope(scope) {
for (const [name, fixture] of this.instances) {
if (fixture.scope === scope)
await fixture.teardown();
}
}

async resolveParametersAndRun(fn) {
const names = fixtureParameterNames(fn);
for (const name of names)
await this.setupFixture(name);
const params = {};
for (const n of names)
params[n] = this.instances.get(n).value;
return fn(params);
}
}

function fixtureParameterNames(fn) {
const text = fn.toString();
const match = text.match(/async(?:\s+function)?\s*\(\s*{\s*([^}]*)\s*}/);
if (!match || !match[1].trim())
return [];
let signature = match[1];
return signature.split(',').map(t => t.trim());
}

function registerFixture(name, scope, fn) {
registrations.set(name, { scope, fn });
}

module.exports = { FixturePool, registerFixture };
186 changes: 46 additions & 140 deletions test/jest/playwrightEnvironment.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
* limitations under the License.
*/

const NodeEnvironment = require('jest-environment-node');
const registerFixtures = require('./fixtures');
const { FixturePool, registerFixture } = require('./fixturePool');
const os = require('os');
const path = require('path');
const fs = require('fs');
Expand All @@ -26,52 +26,62 @@ const GoldenUtils = require('../../utils/testrunner/GoldenUtils');
const {installCoverageHooks} = require('./coverage');
const browserName = process.env.BROWSER || 'chromium';
const reportOnly = !!process.env.REPORT_ONLY_PLATFORM;
const { ModuleMocker } = require('jest-mock');

const testOptions = {};
testOptions.MAC = platform === 'darwin';
testOptions.LINUX = platform === 'linux';
testOptions.WIN = platform === 'win32';
testOptions.CHROMIUM = browserName === 'chromium';
testOptions.FFOX = browserName === 'firefox';
testOptions.WEBKIT = browserName === 'webkit';
testOptions.USES_HOOKS = process.env.PWCHANNEL === 'wire';
testOptions.CHANNEL = !!process.env.PWCHANNEL;
testOptions.HEADLESS = !!valueFromEnv('HEADLESS', true);
testOptions.ASSETS_DIR = path.join(__dirname, '..', 'assets');
testOptions.GOLDEN_DIR = path.join(__dirname, '..', 'golden-' + browserName);
testOptions.OUTPUT_DIR = path.join(__dirname, '..', 'output-' + browserName);
global.testOptions = testOptions;

global.registerFixture = (name, fn) => {
registerFixture(name, 'test', fn);
};

global.registerWorkerFixture = (name, fn) => {
registerFixture(name, 'worker', fn);
};

registerFixtures(global);

let currentFixturePool = null;

process.on('SIGINT', async () => {
if (currentFixturePool) {
await currentFixturePool.teardownScope('test');
await currentFixturePool.teardownScope('worker');
}
process.exit(130);
});

class PlaywrightEnvironment extends NodeEnvironment {
class PlaywrightEnvironment {
constructor(config, context) {
super(config, context);
this.moduleMocker = new ModuleMocker(global);
this.fixturePool = new FixturePool();
const testOptions = {};
testOptions.MAC = platform === 'darwin';
testOptions.LINUX = platform === 'linux';
testOptions.WIN = platform === 'win32';
testOptions.CHROMIUM = browserName === 'chromium';
testOptions.FFOX = browserName === 'firefox';
testOptions.WEBKIT = browserName === 'webkit';
testOptions.USES_HOOKS = process.env.PWCHANNEL === 'wire';
testOptions.CHANNEL = !!process.env.PWCHANNEL;
testOptions.HEADLESS = !!valueFromEnv('HEADLESS', true);
testOptions.ASSETS_DIR = path.join(__dirname, '..', 'assets');
testOptions.GOLDEN_DIR = path.join(__dirname, '..', 'golden-' + browserName);
testOptions.OUTPUT_DIR = path.join(__dirname, '..', 'output-' + browserName);
this.global = global;
this.global.testOptions = testOptions;
this.testPath = context.testPath;

this.global.registerFixture = (name, fn) => {
this.fixturePool.registerFixture(name, 'test', fn);
};
this.global.registerWorkerFixture = (name, fn) => {
this.fixturePool.registerFixture(name, 'worker', fn);
};
registerFixtures(this.global);

process.on('SIGINT', async () => {
await this.fixturePool.teardownScope('test');
await this.fixturePool.teardownScope('worker');
process.exit(130);
});
}

async setup() {
await super.setup();
const {coverage, uninstall} = installCoverageHooks(browserName);
this.coverage = coverage;
this.uninstallCoverage = uninstall;
currentFixturePool = this.fixturePool;
}

async teardown() {
currentFixturePool = null;
await this.fixturePool.teardownScope('worker');
await super.teardown();
// If the setup throws an error, we don't want to override it
// with a useless error about this.coverage not existing.
if (!this.coverage)
Expand All @@ -88,7 +98,7 @@ class PlaywrightEnvironment extends NodeEnvironment {
}

runScript(script) {
return super.runScript(script);
return script.runInThisContext();
}

patchToEnableFixtures(object, name) {
Expand Down Expand Up @@ -200,113 +210,6 @@ class PlaywrightEnvironment extends NodeEnvironment {
}
}

class Fixture {
constructor(pool, name, scope, fn) {
this.pool = pool;
this.name = name;
this.scope = scope;
this.fn = fn;
this.deps = fixtureParameterNames(this.fn);
this.usages = new Set();
this.value = null;
}

async setup() {
for (const name of this.deps) {
await this.pool.setupFixture(name);
this.pool.instances.get(name).usages.add(this.name);
}

const params = {};
for (const n of this.deps)
params[n] = this.pool.instances.get(n).value;
let setupFenceFulfill;
let setupFenceReject;
const setupFence = new Promise((f, r) => { setupFenceFulfill = f; setupFenceReject = r; });
const teardownFence = new Promise(f => this._teardownFenceCallback = f);
debug('pw:test:hook')(`setup "${this.name}"`);
this._tearDownComplete = this.fn(params, async value => {
this.value = value;
setupFenceFulfill();
await teardownFence;
}).catch(e => setupFenceReject(e));
await setupFence;
this._setup = true;
}

async teardown() {
if (this._teardown)
return;
this._teardown = true;
for (const name of this.usages) {
const fixture = this.pool.instances.get(name);
if (!fixture)
continue;
await fixture.teardown();
}
if (this._setup) {
debug('pw:test:hook')(`teardown "${this.name}"`);
this._teardownFenceCallback();
}
await this._tearDownComplete;
this.pool.instances.delete(this.name);
}
}

class FixturePool {
constructor() {
this.registrations = new Map();
this.instances = new Map();
}

registerFixture(name, scope, fn) {
this.registrations.set(name, { scope, fn });
}

async setupFixture(name) {
let fixture = this.instances.get(name);
if (fixture)
return fixture;

if (!this.registrations.has(name))
throw new Error('Unknown fixture: ' + name);
const { scope, fn } = this.registrations.get(name);
fixture = new Fixture(this, name, scope, fn);
this.instances.set(name, fixture);
await fixture.setup();
return fixture;
}

async teardownScope(scope) {
for (const [name, fixture] of this.instances) {
if (fixture.scope === scope)
await fixture.teardown();
}
}

async resolveParametersAndRun(fn) {
const names = fixtureParameterNames(fn);
for (const name of names)
await this.setupFixture(name);
const params = {};
for (const n of names)
params[n] = this.instances.get(n).value;
return fn(params);
}
}

exports.getPlaywrightEnv = () => PlaywrightEnvironment;
exports.default = exports.getPlaywrightEnv();

function fixtureParameterNames(fn) {
const text = fn.toString();
const match = text.match(/async(?:\s+function)?\s*\(\s*{\s*([^}]*)\s*}/);
if (!match || !match[1].trim())
return [];
let signature = match[1];
return signature.split(',').map(t => t.trim());
}

function valueFromEnv(name, defaultValue) {
if (!(name in process.env))
return defaultValue;
Expand All @@ -321,3 +224,6 @@ function testOrSuiteName(o) {
name += ' ';
return name + o.name;
}

exports.getPlaywrightEnv = () => PlaywrightEnvironment;
exports.default = exports.getPlaywrightEnv();

0 comments on commit f6d321f

Please sign in to comment.