UNPKG

folio

Version:

A customizable test framework to build your own test frameworks. Foundation for the [Playwright test runner](https://github.com/microsoft/playwright-test).

291 lines 12.7 kB
"use strict"; /** * 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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.FixturePool = exports.assignConfig = exports.config = exports.setParameterValues = exports.matrix = exports.assignParameters = exports.parameters = exports.parameterRegistrations = exports.currentTestInfo = exports.setCurrentTestInfo = void 0; const debug_1 = require("./debug"); const util_1 = require("./util"); let currentTestInfoValue = null; function setCurrentTestInfo(testInfo) { currentTestInfoValue = testInfo; } exports.setCurrentTestInfo = setCurrentTestInfo; function currentTestInfo() { return currentTestInfoValue; } exports.currentTestInfo = currentTestInfo; exports.parameterRegistrations = new Map(); exports.parameters = {}; function assignParameters(params) { exports.parameters = Object.assign(exports.parameters, params); } exports.assignParameters = assignParameters; exports.matrix = {}; function setParameterValues(name, values) { if (!(name in exports.matrix)) throw util_1.errorWithCallLocation(`Unregistered parameter '${name}' was set.`); exports.matrix[name] = values; } exports.setParameterValues = setParameterValues; exports.config = {}; function assignConfig(c) { exports.config = Object.assign(exports.config, c); } exports.assignConfig = assignConfig; class Fixture { constructor(pool, registration) { this._setup = false; this._teardown = false; this.pool = pool; this.registration = registration; this.usages = new Set(); this.hasGeneratorValue = registration.name in exports.parameters; this.value = this.hasGeneratorValue ? exports.parameters[registration.name] : null; if (this.hasGeneratorValue && this.registration.deps.length) throw util_1.errorWithCallLocation(`Parameter fixture "${this.registration.name}" should not have dependencies`); } async setup() { if (this.hasGeneratorValue) return; const params = {}; for (const name of this.registration.deps) { const registration = this.pool._resolveDependency(this.registration, name); if (!registration) throw util_1.errorWithCallLocation(`Unknown fixture "${name}"`); const dep = await this.pool.setupFixtureForRegistration(registration); dep.usages.add(this); params[name] = dep.value; } let setupFenceFulfill; let setupFenceReject; let called = false; const setupFence = new Promise((f, r) => { setupFenceFulfill = f; setupFenceReject = r; }); const teardownFence = new Promise(f => this._teardownFenceCallback = f); debug_1.debugLog(`setup fixture "${this.registration.name}"`); this._tearDownComplete = this.registration.fn(params, async (value) => { if (called) throw util_1.errorWithCallLocation(`Cannot provide fixture value for the second time`); called = true; this.value = value; setupFenceFulfill(); return await teardownFence; }).catch((e) => { if (!this._setup) setupFenceReject(e); else throw e; }); await setupFence; this._setup = true; } async teardown() { if (this.hasGeneratorValue) { this.pool.instances.delete(this.registration); return; } if (this._teardown) return; this._teardown = true; for (const fixture of this.usages) await fixture.teardown(); this.usages.clear(); if (this._setup) { debug_1.debugLog(`teardown fixture "${this.registration.name}"`); this._teardownFenceCallback(); await this._tearDownComplete; } this.pool.instances.delete(this.registration); } } let lastFixturePoolId = 0; class FixturePool { constructor(parentPool) { this.instances = new Map(); this.parentPool = parentPool; this.id = ++lastFixturePoolId; this.registrations = new Map(parentPool ? parentPool.registrations : []); } union(other) { const containsRegistrationRecursively = (pool, registration) => { if (pool.registrations.get(registration.name) === registration) return true; if (!pool.parentPool) return false; return containsRegistrationRecursively(pool.parentPool, registration); }; const result = new FixturePool(this); for (const [name, otherRegistration] of other.registrations) { const thisRegistration = this.registrations.get(name); if (!thisRegistration) { result.registrations.set(name, otherRegistration); } else if (containsRegistrationRecursively(this, otherRegistration)) { // |this| contains an override - do nothing. } else if (containsRegistrationRecursively(other, thisRegistration)) { // |other| contains an override - use it. result.registrations.set(name, otherRegistration); } else { // Both |this| and |other| have a different override - throw. throw util_1.errorWithCallLocation(`Fixture "${name}" is defined in both fixture sets.`); } } return result; } registerFixture(name, scope, fn, auto) { const previous = this.registrations.get(name); if (previous) { if (previous.scope !== scope) throw util_1.errorWithCallLocation(`Fixture "${name}" has already been registered as a { scope: '${previous.scope}' } fixture. Use a different name for this ${scope} fixture.`); else throw util_1.errorWithCallLocation(`Fixture "${name}" has already been registered. Use 'override' to override it in a specific test file.`); } const deps = fixtureParameterNames(fn); const registration = { name, scope, fn, auto, isOverride: false, deps, super: previous }; this.registrations.set(name, registration); } overrideFixture(name, fn) { const previous = this.registrations.get(name); if (!previous) throw util_1.errorWithCallLocation(`Fixture "${name}" has not been registered yet. Use 'init' instead.`); const deps = fixtureParameterNames(fn); const registration = { name, scope: previous.scope, fn, auto: previous.auto, isOverride: true, deps, super: previous }; this.registrations.set(name, registration); } registerWorkerParameter(parameter) { if (exports.parameterRegistrations.has(parameter.name)) throw util_1.errorWithCallLocation(`Parameter "${parameter.name}" has been already registered`); exports.parameterRegistrations.set(parameter.name, parameter); exports.matrix[parameter.name] = [parameter.defaultValue]; } validate() { const markers = new Map(); const stack = []; const visit = (registration) => { markers.set(registration, 'visiting'); stack.push(registration); for (const name of registration.deps) { const dep = this._resolveDependency(registration, name); if (!dep) throw util_1.errorWithCallLocation(`Fixture "${registration.name}" has unknown parameter "${name}".`); if (registration.scope === 'worker' && dep.scope === 'test') throw util_1.errorWithCallLocation(`Worker fixture "${registration.name}" cannot depend on a test fixture "${name}".`); if (!markers.has(dep)) { visit(dep); } else if (markers.get(dep) === 'visiting') { const index = stack.indexOf(dep); const names = stack.slice(index, stack.length).map(r => `"${r.name}"`); throw util_1.errorWithCallLocation(`Fixtures ${names.join(' -> ')} -> "${dep.name}" form a dependency cycle.`); } } markers.set(registration, 'visited'); stack.pop(); }; for (const registration of this.registrations.values()) visit(registration); } parametersForFunction(fn, prefix, allowTestFixtures) { const result = new Set(); const visit = (registration) => { if (exports.parameterRegistrations.has(registration.name)) result.add(registration.name); for (const name of registration.deps) visit(this._resolveDependency(registration, name)); }; for (const name of fixtureParameterNames(fn)) { const registration = this.registrations.get(name); if (!registration) throw util_1.errorWithCallLocation(`${prefix} has unknown parameter "${name}".`); if (!allowTestFixtures && registration.scope === 'test') throw util_1.errorWithCallLocation(`${prefix} cannot depend on a test fixture "${name}".`); visit(registration); } for (const registration of this.registrations.values()) { if (registration.auto) { if (!allowTestFixtures && registration.scope === 'test') throw util_1.errorWithCallLocation(`${prefix} cannot depend on a test fixture "${registration.name}".`); visit(registration); } } return Array.from(result); } async setupFixture(name) { const registration = this.registrations.get(name); if (!registration) throw util_1.errorWithCallLocation('Unknown fixture: ' + name); return this.setupFixtureForRegistration(registration); } async setupFixtureForRegistration(registration) { let fixture = this.instances.get(registration); if (fixture) return fixture; fixture = new Fixture(this, registration); this.instances.set(registration, fixture); await fixture.setup(); return fixture; } async teardownScope(scope) { for (const [, fixture] of this.instances) { if (fixture.registration.scope === scope) await fixture.teardown(); } } async resolveParametersAndRunHookOrTest(fn) { // Install all automatic fixtures. for (const registration of this.registrations.values()) { if (registration.auto) await this.setupFixtureForRegistration(registration); } // Install used fixtures. const names = fixtureParameterNames(fn); const params = {}; for (const name of names) { const fixture = await this.setupFixture(name); params[name] = fixture.value; } return fn(params); } _resolveDependency(registration, name) { if (name === registration.name) return registration.super; return this.registrations.get(name); } } exports.FixturePool = FixturePool; const signatureSymbol = Symbol('signature'); function fixtureParameterNames(fn) { if (!fn[signatureSymbol]) fn[signatureSymbol] = innerFixtureParameterNames(fn); return fn[signatureSymbol]; } function innerFixtureParameterNames(fn) { const text = fn.toString(); const match = text.match(/(?:async)?(?:\s+function)?[^\(]*\(([^})]*)/); if (!match) return []; const trimmedParams = match[1].trim(); if (!trimmedParams) return []; if (trimmedParams && trimmedParams[0] !== '{') throw util_1.errorWithCallLocation('First argument must use the object destructuring pattern: ' + trimmedParams); const signature = trimmedParams.substring(1).trim(); if (!signature) return []; return signature.split(',').map((t) => t.trim().split(':')[0].trim()); } //# sourceMappingURL=fixtures.js.map