chore(testrunner): complete ts migration (#3587)
This commit is contained in:
parent
224d3df899
commit
53ac35a613
58
package-lock.json
generated
58
package-lock.json
generated
|
|
@ -1168,6 +1168,47 @@
|
|||
"defer-to-connect": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz",
|
||||
"integrity": "sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.1.0",
|
||||
"@babel/types": "^7.0.0",
|
||||
"@types/babel__generator": "*",
|
||||
"@types/babel__template": "*",
|
||||
"@types/babel__traverse": "*"
|
||||
}
|
||||
},
|
||||
"@types/babel__generator": {
|
||||
"version": "7.6.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz",
|
||||
"integrity": "sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@types/babel__template": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz",
|
||||
"integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.1.0",
|
||||
"@babel/types": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"@types/babel__traverse": {
|
||||
"version": "7.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz",
|
||||
"integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.3.0"
|
||||
}
|
||||
},
|
||||
"@types/color-name": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
|
||||
|
|
@ -1287,6 +1328,23 @@
|
|||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/source-map-support": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/source-map-support/-/source-map-support-0.5.3.tgz",
|
||||
"integrity": "sha512-fvjMjVH8Rmokw2dWh1dkj90iX5R8FPjeZzjNH+6eFXReh0QnHFf1YBl3B0CF0RohIAA3SDRJsGeeUWKl6d7HqA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"source-map": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
"@babel/core": "^7.10.3",
|
||||
"@babel/preset-env": "^7.10.3",
|
||||
"@babel/preset-typescript": "^7.10.1",
|
||||
"@types/babel__core": "^7.1.9",
|
||||
"@types/debug": "0.0.31",
|
||||
"@types/extract-zip": "^1.6.2",
|
||||
"@types/mime": "^2.0.1",
|
||||
|
|
@ -63,6 +64,7 @@
|
|||
"@types/progress": "^2.0.3",
|
||||
"@types/proxy-from-env": "^1.0.0",
|
||||
"@types/rimraf": "^2.0.2",
|
||||
"@types/source-map-support": "^0.5.3",
|
||||
"@types/stack-utils": "^1.0.1",
|
||||
"@types/ws": "^6.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^2.6.1",
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Microsoft Corporation.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
require('./lib/cli');
|
||||
|
|
@ -14,7 +14,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import GoldenUtils from './GoldenUtils';
|
||||
import { compare } from './golden';
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
|
||||
declare global {
|
||||
const expect: typeof import('expect');
|
||||
|
|
@ -28,16 +29,16 @@ declare module 'expect/build/types' {
|
|||
|
||||
global['expect'] = require('expect');
|
||||
|
||||
let relativeTestFile: string;
|
||||
let testFile: string;
|
||||
|
||||
export function initializeImageMatcher(options) {
|
||||
function toMatchImage(received, name, config) {
|
||||
const { pass, message } = GoldenUtils.compare(received, name, { ...options, relativeTestFile, config });
|
||||
export function initializeImageMatcher(config: RunnerConfig) {
|
||||
function toMatchImage(received: Buffer, name: string, options?: { threshold?: number }) {
|
||||
const { pass, message } = compare(received, name, config, testFile, options);
|
||||
return { pass, message: () => message };
|
||||
};
|
||||
expect.extend({ toMatchImage });
|
||||
}
|
||||
|
||||
export function setCurrentTestFile(testFile: string) {
|
||||
relativeTestFile = testFile;
|
||||
export function setCurrentTestFile(file: string) {
|
||||
testFile = file;
|
||||
}
|
||||
|
|
@ -17,32 +17,29 @@
|
|||
import debug from 'debug';
|
||||
import { Test } from './test';
|
||||
|
||||
declare global {
|
||||
interface WorkerState {
|
||||
}
|
||||
type Scope = 'test' | 'worker';
|
||||
|
||||
interface TestState {
|
||||
}
|
||||
type FixtureRegistration = {
|
||||
name: string;
|
||||
scope: Scope;
|
||||
fn: Function;
|
||||
};
|
||||
|
||||
interface FixtureParameters {
|
||||
}
|
||||
}
|
||||
|
||||
const registrations = new Map();
|
||||
const registrationsByFile = new Map();
|
||||
export let parameters: FixtureParameters = {} as FixtureParameters;
|
||||
const registrations = new Map<string, FixtureRegistration>();
|
||||
const registrationsByFile = new Map<string, FixtureRegistration[]>();
|
||||
export let parameters: any = {};
|
||||
export const parameterRegistrations = new Map();
|
||||
|
||||
export function setParameters(params: any) {
|
||||
parameters = Object.assign(parameters, params);
|
||||
for (const name of Object.keys(params))
|
||||
registerWorkerFixture(name as keyof WorkerState, async ({}, test) => await test(parameters[name] as never));
|
||||
registerWorkerFixture(name, async ({}, test) => await test(parameters[name]));
|
||||
}
|
||||
|
||||
class Fixture<Config> {
|
||||
pool: FixturePool<Config>;
|
||||
name: string;
|
||||
scope: string;
|
||||
scope: Scope;
|
||||
fn: Function;
|
||||
deps: string[];
|
||||
usages: Set<string>;
|
||||
|
|
@ -53,7 +50,7 @@ class Fixture<Config> {
|
|||
_setup = false;
|
||||
_teardown = false;
|
||||
|
||||
constructor(pool: FixturePool<Config>, name: string, scope: string, fn: any) {
|
||||
constructor(pool: FixturePool<Config>, name: string, scope: Scope, fn: any) {
|
||||
this.pool = pool;
|
||||
this.name = name;
|
||||
this.scope = scope;
|
||||
|
|
@ -137,7 +134,7 @@ export class FixturePool<Config> {
|
|||
}
|
||||
}
|
||||
|
||||
async resolveParametersAndRun(fn: (arg0: {}) => any, timeout: number, config: Config, test?: Test) {
|
||||
async resolveParametersAndRun(fn: Function, timeout: number, config: Config, test?: Test) {
|
||||
const names = fixtureParameterNames(fn);
|
||||
for (const name of names)
|
||||
await this.setupFixture(name, config, test);
|
||||
|
|
@ -148,7 +145,7 @@ export class FixturePool<Config> {
|
|||
if (!timeout)
|
||||
return fn(params);
|
||||
|
||||
let timer;
|
||||
let timer: NodeJS.Timer;
|
||||
let timerPromise = new Promise(f => timer = setTimeout(f, timeout));
|
||||
return Promise.race([
|
||||
Promise.resolve(fn(params)).then(() => clearTimeout(timer)),
|
||||
|
|
@ -169,9 +166,9 @@ export class FixturePool<Config> {
|
|||
}
|
||||
}
|
||||
|
||||
export function fixturesForCallback(callback: any): string[] {
|
||||
export function fixturesForCallback(callback: Function): string[] {
|
||||
const names = new Set<string>();
|
||||
const visit = (callback: any) => {
|
||||
const visit = (callback: Function) => {
|
||||
for (const name of fixtureParameterNames(callback)) {
|
||||
if (name in names)
|
||||
continue;
|
||||
|
|
@ -189,7 +186,7 @@ export function fixturesForCallback(callback: any): string[] {
|
|||
return result;
|
||||
}
|
||||
|
||||
function fixtureParameterNames(fn: { toString: () => any; }): string[] {
|
||||
function fixtureParameterNames(fn: Function): string[] {
|
||||
const text = fn.toString();
|
||||
const match = text.match(/async(?:\s+function)?\s*\(\s*{\s*([^}]*)\s*}/);
|
||||
if (!match || !match[1].trim())
|
||||
|
|
@ -198,7 +195,7 @@ function fixtureParameterNames(fn: { toString: () => any; }): string[] {
|
|||
return signature.split(',').map((t: string) => t.trim());
|
||||
}
|
||||
|
||||
function innerRegisterFixture(name: string, scope: string, fn: Function, caller: Function) {
|
||||
function innerRegisterFixture(name: string, scope: Scope, fn: Function, caller: Function) {
|
||||
const obj = {stack: ''};
|
||||
Error.captureStackTrace(obj, caller);
|
||||
const stackFrame = obj.stack.split('\n')[2];
|
||||
|
|
@ -211,20 +208,20 @@ function innerRegisterFixture(name: string, scope: string, fn: Function, caller:
|
|||
registrationsByFile.get(file).push(registration);
|
||||
};
|
||||
|
||||
export function registerFixture<Config, T extends keyof TestState>(name: T, fn: (params: FixtureParameters & WorkerState & TestState, runTest: (arg: TestState[T]) => Promise<void>, config: Config, test: Test) => Promise<void>) {
|
||||
export function registerFixture<Config>(name: string, fn: (params: any, runTest: (arg: any) => Promise<void>, config: Config, test: Test) => Promise<void>) {
|
||||
innerRegisterFixture(name, 'test', fn, registerFixture);
|
||||
};
|
||||
|
||||
export function registerWorkerFixture<Config, T extends keyof (WorkerState & FixtureParameters)>(name: T, fn: (params: FixtureParameters & WorkerState, runTest: (arg: (WorkerState & FixtureParameters)[T]) => Promise<void>, config: Config) => Promise<void>) {
|
||||
export function registerWorkerFixture<Config>(name: string, fn: (params: any, runTest: (arg: any) => Promise<void>, config: Config) => Promise<void>) {
|
||||
innerRegisterFixture(name, 'worker', fn, registerWorkerFixture);
|
||||
};
|
||||
|
||||
export function registerParameter<T extends keyof WorkerState>(name: T, fn: () => WorkerState[T][]) {
|
||||
registerWorkerFixture(name, async ({}: any, test: (arg0: any) => any) => await test(parameters[name]));
|
||||
export function registerParameter(name: string, fn: () => any) {
|
||||
registerWorkerFixture(name, async ({}: any, test: Function) => await test(parameters[name]));
|
||||
parameterRegistrations.set(name, fn);
|
||||
}
|
||||
|
||||
function collectRequires(file: string | number, result: Set<unknown>) {
|
||||
function collectRequires(file: string, result: Set<string>) {
|
||||
if (result.has(file))
|
||||
return;
|
||||
result.add(file);
|
||||
|
|
@ -236,8 +233,8 @@ function collectRequires(file: string | number, result: Set<unknown>) {
|
|||
collectRequires(dep, result);
|
||||
}
|
||||
|
||||
export function lookupRegistrations(file: any, scope: any) {
|
||||
const deps = new Set();
|
||||
export function lookupRegistrations(file: string, scope: Scope) {
|
||||
const deps = new Set<string>();
|
||||
collectRequires(file, deps);
|
||||
const allDeps = [...deps].reverse();
|
||||
let result = new Map();
|
||||
|
|
@ -254,7 +251,7 @@ export function lookupRegistrations(file: any, scope: any) {
|
|||
return result;
|
||||
}
|
||||
|
||||
export function rerunRegistrations(file: any, scope: any) {
|
||||
export function rerunRegistrations(file: string, scope: Scope) {
|
||||
// When we are running several tests in the same worker, we should re-run registrations before
|
||||
// each file. That way we erase potential fixture overrides from the previous test runs.
|
||||
for (const registration of lookupRegistrations(file, scope).values())
|
||||
|
|
|
|||
|
|
@ -14,15 +14,14 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const Diff = require('text-diff');
|
||||
const PNG = require('pngjs').PNG;
|
||||
const jpeg = require('jpeg-js');
|
||||
const pixelmatch = require('pixelmatch');
|
||||
const c = require('colors/safe');
|
||||
|
||||
module.exports = {compare};
|
||||
import c from 'colors/safe';
|
||||
import fs from 'fs';
|
||||
import jpeg from 'jpeg-js';
|
||||
import path from 'path';
|
||||
import pixelmatch from 'pixelmatch';
|
||||
import { PNG } from 'pngjs';
|
||||
import Diff from 'text-diff';
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
|
||||
const extensionToMimeType = {
|
||||
'png': 'image/png',
|
||||
|
|
@ -37,14 +36,7 @@ const GoldenComparators = {
|
|||
'text/plain': compareText
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {?Object} actualBuffer
|
||||
* @param {!Buffer} expectedBuffer
|
||||
* @param {!string} mimeType
|
||||
* @return {?{diff?: Object, errorMessage?: string}}
|
||||
*/
|
||||
function compareImages(actualBuffer, expectedBuffer, mimeType, config = {}) {
|
||||
function compareImages(actualBuffer: Buffer, expectedBuffer: Buffer, mimeType: string, options = {}): { diff?: object; errorMessage?: string; } | null {
|
||||
if (!actualBuffer || !(actualBuffer instanceof Buffer))
|
||||
return { errorMessage: 'Actual result should be Buffer.' };
|
||||
|
||||
|
|
@ -56,16 +48,11 @@ function compareImages(actualBuffer, expectedBuffer, mimeType, config = {}) {
|
|||
};
|
||||
}
|
||||
const diff = new PNG({width: expected.width, height: expected.height});
|
||||
const count = pixelmatch(expected.data, actual.data, diff.data, expected.width, expected.height, {threshold: 0.2, ...config});
|
||||
const count = pixelmatch(expected.data, actual.data, diff.data, expected.width, expected.height, { threshold: 0.2, ...options });
|
||||
return count > 0 ? { diff: PNG.sync.write(diff) } : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Object} actual
|
||||
* @param {!Buffer} expectedBuffer
|
||||
* @return {?{diff?: Object, errorMessage?: string, diffExtension?: string}}
|
||||
*/
|
||||
function compareText(actual, expectedBuffer) {
|
||||
function compareText(actual: Buffer, expectedBuffer: Buffer): { diff?: object; errorMessage?: string; diffExtension?: string; } | null {
|
||||
if (typeof actual !== 'string')
|
||||
return { errorMessage: 'Actual result should be string' };
|
||||
const expected = expectedBuffer.toString('utf-8');
|
||||
|
|
@ -83,19 +70,14 @@ function compareText(actual, expectedBuffer) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {?Object} actual
|
||||
* @param {string} name
|
||||
* @return {!{pass: boolean, message?: string}}
|
||||
*/
|
||||
function compare(actual, name, options) {
|
||||
const { relativeTestFile, snapshotDir, outputDir, updateSnapshots } = options;
|
||||
let expectedPath;
|
||||
export function compare(actual: Buffer, name: string, config: RunnerConfig, testFile: string, options?: { threshold?: number } ): { pass: boolean; message?: string; } {
|
||||
let expectedPath: string;
|
||||
const relativeTestFile = path.relative(config.testDir, testFile);
|
||||
const testAssetsDir = relativeTestFile.replace(/\.spec\.[jt]s/, '');
|
||||
if (path.isAbsolute(name))
|
||||
expectedPath = name;
|
||||
else
|
||||
expectedPath = path.join(snapshotDir, testAssetsDir, name);
|
||||
expectedPath = path.join(config.snapshotDir, testAssetsDir, name);
|
||||
if (!fs.existsSync(expectedPath)) {
|
||||
fs.mkdirSync(path.dirname(expectedPath), { recursive: true });
|
||||
fs.writeFileSync(expectedPath, actual);
|
||||
|
|
@ -115,11 +97,11 @@ function compare(actual, name, options) {
|
|||
};
|
||||
}
|
||||
|
||||
const result = comparator(actual, expected, mimeType, options.config);
|
||||
const result = comparator(actual, expected, mimeType, options);
|
||||
if (!result)
|
||||
return { pass: true };
|
||||
|
||||
if (updateSnapshots) {
|
||||
if (config.updateSnapshots) {
|
||||
fs.mkdirSync(path.dirname(expectedPath), { recursive: true });
|
||||
fs.writeFileSync(expectedPath, actual);
|
||||
return {
|
||||
|
|
@ -134,7 +116,7 @@ function compare(actual, name, options) {
|
|||
actualPath = addSuffix(expectedPath, '-actual');
|
||||
diffPath = addSuffix(expectedPath, '-diff', result.diffExtension);
|
||||
} else {
|
||||
const outputPath = path.join(outputDir, testAssetsDir, name);
|
||||
const outputPath = path.join(config.outputDir, testAssetsDir, name);
|
||||
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
||||
const expectedPathOut = addSuffix(outputPath, '-expected');
|
||||
actualPath = addSuffix(outputPath, '-actual');
|
||||
|
|
@ -162,13 +144,7 @@ function compare(actual, name, options) {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} filePath
|
||||
* @param {string} suffix
|
||||
* @param {string=} customExtension
|
||||
* @return {string}
|
||||
*/
|
||||
function addSuffix(filePath, suffix, customExtension) {
|
||||
function addSuffix(filePath: string, suffix: string, customExtension?: string): string {
|
||||
const dirname = path.dirname(filePath);
|
||||
const ext = path.extname(filePath);
|
||||
const name = path.basename(filePath, ext);
|
||||
|
|
@ -30,6 +30,17 @@ export { parameters, registerParameter } from './fixtures';
|
|||
export { RunnerConfig } from './runnerConfig';
|
||||
export { Suite, Test } from './test';
|
||||
|
||||
declare global {
|
||||
interface WorkerState {
|
||||
}
|
||||
|
||||
interface TestState {
|
||||
}
|
||||
|
||||
interface FixtureParameters {
|
||||
}
|
||||
}
|
||||
|
||||
let beforeFunctions: Function[] = [];
|
||||
let afterFunctions: Function[] = [];
|
||||
let matrix: Matrix = {};
|
||||
|
|
@ -39,11 +50,11 @@ global['after'] = (fn: Function) => afterFunctions.push(fn);
|
|||
global['matrix'] = (m: Matrix) => matrix = m;
|
||||
|
||||
export function registerFixture<T extends keyof TestState>(name: T, fn: (params: FixtureParameters & WorkerState & TestState, runTest: (arg: TestState[T]) => Promise<void>, config: RunnerConfig, test: Test) => Promise<void>) {
|
||||
registerFixtureT<RunnerConfig, T>(name, fn);
|
||||
registerFixtureT<RunnerConfig>(name, fn);
|
||||
};
|
||||
|
||||
export function registerWorkerFixture<T extends keyof (WorkerState & FixtureParameters)>(name: T, fn: (params: FixtureParameters & WorkerState, runTest: (arg: (WorkerState & FixtureParameters)[T]) => Promise<void>, config: RunnerConfig) => Promise<void>) {
|
||||
registerWorkerFixtureT<RunnerConfig, T>(name, fn);
|
||||
registerWorkerFixtureT<RunnerConfig>(name, fn);
|
||||
};
|
||||
|
||||
export function collectTests(config: RunnerConfig, files: string[]): Suite {
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import crypto from 'crypto';
|
|||
import path from 'path';
|
||||
import { EventEmitter } from 'events';
|
||||
import { lookupRegistrations, FixturePool } from './fixtures';
|
||||
import { Suite } from './test';
|
||||
import { Suite, Test, Configuration } from './test';
|
||||
import { TestRunnerEntry } from './testRunner';
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
|
||||
|
|
@ -29,8 +29,7 @@ export class Runner extends EventEmitter {
|
|||
private _workerClaimers: (() => void)[] = [];
|
||||
stats: { duration: number; failures: number; passes: number; pending: number; tests: number; };
|
||||
|
||||
private _testById = new Map<any, any>();
|
||||
private _testsByConfiguredFile = new Map<any, any>();
|
||||
private _testById = new Map<string, Test>();
|
||||
private _queue: TestRunnerEntry[] = [];
|
||||
private _stopCallback: () => void;
|
||||
readonly _config: RunnerConfig;
|
||||
|
|
@ -48,37 +47,34 @@ export class Runner extends EventEmitter {
|
|||
tests: 0,
|
||||
};
|
||||
|
||||
this._testById = new Map();
|
||||
this._testsByConfiguredFile = new Map();
|
||||
this._suite = suite;
|
||||
this._suite.eachTest(test => {
|
||||
const configuredFile = `${test.file}::[${test._configurationString}]`;
|
||||
if (!this._testsByConfiguredFile.has(configuredFile)) {
|
||||
this._testsByConfiguredFile.set(configuredFile, {
|
||||
file: test.file,
|
||||
configuredFile,
|
||||
ordinals: [],
|
||||
configurationObject: test._configurationObject,
|
||||
configurationString: test._configurationString
|
||||
});
|
||||
}
|
||||
const { ordinals } = this._testsByConfiguredFile.get(configuredFile);
|
||||
ordinals.push(test._ordinal);
|
||||
this._testById.set(`${test._ordinal}@${configuredFile}`, test);
|
||||
});
|
||||
for (const suite of this._suite.suites) {
|
||||
suite.eachTest(test => {
|
||||
this._testById.set(`${test._ordinal}@${suite.file}::[${suite._configurationString}]`, test);
|
||||
});
|
||||
}
|
||||
|
||||
if (process.stdout.isTTY) {
|
||||
const total = suite.total();
|
||||
console.log();
|
||||
const jobs = Math.min(config.jobs, this._testsByConfiguredFile.size);
|
||||
const jobs = Math.min(config.jobs, suite.suites.length);
|
||||
console.log(`Running ${total} test${ total > 1 ? 's' : '' } using ${jobs} worker${ jobs > 1 ? 's' : ''}`);
|
||||
}
|
||||
}
|
||||
|
||||
_filesSortedByWorkerHash() {
|
||||
const result = [];
|
||||
for (const entry of this._testsByConfiguredFile.values())
|
||||
result.push({ ...entry, hash: entry.configurationString + '@' + computeWorkerHash(entry.file) });
|
||||
_filesSortedByWorkerHash(): TestRunnerEntry[] {
|
||||
const result: TestRunnerEntry[] = [];
|
||||
for (const suite of this._suite.suites) {
|
||||
const ordinals: number[] = [];
|
||||
suite.eachTest(test => ordinals.push(test._ordinal) && false);
|
||||
result.push({
|
||||
ordinals,
|
||||
file: suite.file,
|
||||
configuration: suite.configuration,
|
||||
configurationString: suite._configurationString,
|
||||
hash: suite._configurationString + '@' + computeWorkerHash(suite.file)
|
||||
});
|
||||
}
|
||||
result.sort((a, b) => a.hash < b.hash ? -1 : (a.hash === b.hash ? 0 : 1));
|
||||
return result;
|
||||
}
|
||||
|
|
@ -258,7 +254,7 @@ class OopWorker extends Worker {
|
|||
await new Promise(f => this.process.once('message', f)); // Ready ack
|
||||
}
|
||||
|
||||
run(entry) {
|
||||
run(entry: TestRunnerEntry) {
|
||||
this.hash = entry.hash;
|
||||
this.process.send({ method: 'run', params: { entry, config: this.runner._config } });
|
||||
}
|
||||
|
|
@ -316,13 +312,13 @@ class InProcessWorker extends Worker {
|
|||
}
|
||||
}
|
||||
|
||||
function chunkFromParams(params) {
|
||||
function chunkFromParams(params: string | { buffer: string }): string | Buffer {
|
||||
if (typeof params === 'string')
|
||||
return params;
|
||||
return Buffer.from(params.buffer, 'base64');
|
||||
}
|
||||
|
||||
function computeWorkerHash(file) {
|
||||
function computeWorkerHash(file: string) {
|
||||
// At this point, registrationsByFile contains all the files with worker fixture registrations.
|
||||
// For every test, build the require closure and map each file to fixtures declared in it.
|
||||
// This collection of fixtures is the fingerprint of the worker setup, a "worker hash".
|
||||
|
|
@ -332,5 +328,3 @@ function computeWorkerHash(file) {
|
|||
hash.update(registration.location);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
module.exports = { Runner };
|
||||
|
|
|
|||
|
|
@ -29,8 +29,6 @@ export class Test {
|
|||
error: any;
|
||||
|
||||
_ordinal: number;
|
||||
_configurationObject: Configuration;
|
||||
_configurationString: string;
|
||||
_overriddenFn: Function;
|
||||
_startTime: number;
|
||||
|
||||
|
|
@ -68,6 +66,7 @@ export class Suite {
|
|||
pending = false;
|
||||
file: string;
|
||||
configuration: Configuration;
|
||||
_configurationString: string;
|
||||
|
||||
_hooks: { type: string, fn: Function } [] = [];
|
||||
_entries: (Suite | Test)[] = [];
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
import path from 'path';
|
||||
import { fixturesForCallback } from './fixtures';
|
||||
import { Configuration, Test, Suite } from './test';
|
||||
import { Test, Suite } from './test';
|
||||
import { fixturesUI } from './fixturesUI';
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
|
||||
|
|
@ -51,7 +51,7 @@ export class TestCollector {
|
|||
return this._hasOnly;
|
||||
}
|
||||
|
||||
_addFile(file: string) {
|
||||
private _addFile(file: string) {
|
||||
const suite = new Suite('');
|
||||
const revertBabelRequire = fixturesUI(suite, file, this._config.timeout);
|
||||
require(file);
|
||||
|
|
@ -83,35 +83,36 @@ export class TestCollector {
|
|||
if (!generatorConfigurations.length)
|
||||
generatorConfigurations.push([]);
|
||||
|
||||
for (const configurationObject of generatorConfigurations) {
|
||||
for (const configuration of generatorConfigurations) {
|
||||
// Serialize configuration as readable string, we will use it as a hash.
|
||||
const tokens = [];
|
||||
for (const { name, value } of configurationObject)
|
||||
for (const { name, value } of configuration)
|
||||
tokens.push(`${name}=${value}`);
|
||||
const configurationString = tokens.join(', ');
|
||||
// Allocate worker for this configuration, add test into it.
|
||||
if (!workerGeneratorConfigurations.has(configurationString))
|
||||
workerGeneratorConfigurations.set(configurationString, { configurationObject, configurationString, tests: new Set() });
|
||||
workerGeneratorConfigurations.set(configurationString, { configuration, configurationString, tests: new Set() });
|
||||
workerGeneratorConfigurations.get(configurationString).tests.add(test);
|
||||
}
|
||||
});
|
||||
|
||||
// Clone the suite as many times as there are worker hashes.
|
||||
// Only include the tests that requested these generations.
|
||||
for (const [hash, {configurationObject, configurationString, tests}] of workerGeneratorConfigurations.entries()) {
|
||||
const clone = this._cloneSuite(suite, configurationObject, configurationString, tests);
|
||||
for (const [hash, {configuration, configurationString, tests}] of workerGeneratorConfigurations.entries()) {
|
||||
const clone = this._cloneSuite(suite, tests);
|
||||
this.suite._addSuite(clone);
|
||||
clone.title = path.basename(file) + (hash.length ? `::[${hash}]` : '');
|
||||
clone.configuration = configuration;
|
||||
clone._configurationString = configurationString;
|
||||
}
|
||||
}
|
||||
|
||||
_cloneSuite(suite: Suite, configurationObject: Configuration, configurationString: string, tests: Set<Test>) {
|
||||
private _cloneSuite(suite: Suite, tests: Set<Test>) {
|
||||
const copy = suite._clone();
|
||||
copy.only = suite.only;
|
||||
copy.configuration = configurationObject;
|
||||
for (const entry of suite._entries) {
|
||||
if (entry instanceof Suite) {
|
||||
copy._addSuite(this._cloneSuite(entry, configurationObject, configurationString, tests));
|
||||
copy._addSuite(this._cloneSuite(entry, tests));
|
||||
} else {
|
||||
const test = entry;
|
||||
if (!tests.has(test))
|
||||
|
|
@ -121,15 +122,13 @@ export class TestCollector {
|
|||
const testCopy = test._clone();
|
||||
testCopy.only = test.only;
|
||||
testCopy._ordinal = test._ordinal;
|
||||
testCopy._configurationObject = configurationObject;
|
||||
testCopy._configurationString = configurationString;
|
||||
copy._addTest(testCopy);
|
||||
}
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
_filterOnly(suite) {
|
||||
private _filterOnly(suite) {
|
||||
const onlySuites = suite.suites.filter(child => this._filterOnly(child) || child.only);
|
||||
const onlyTests = suite.tests.filter(test => test.only);
|
||||
if (onlySuites.length || onlyTests.length) {
|
||||
|
|
@ -140,5 +139,3 @@ export class TestCollector {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { TestCollector };
|
||||
|
|
|
|||
|
|
@ -14,11 +14,10 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import { FixturePool, rerunRegistrations, setParameters } from './fixtures';
|
||||
import { EventEmitter } from 'events';
|
||||
import { setCurrentTestFile } from './expect';
|
||||
import { Test, Suite } from './test';
|
||||
import { Test, Suite, Configuration } from './test';
|
||||
import { fixturesUI } from './fixturesUI';
|
||||
import { RunnerConfig } from './runnerConfig';
|
||||
|
||||
|
|
@ -27,8 +26,8 @@ export const fixturePool = new FixturePool<RunnerConfig>();
|
|||
export type TestRunnerEntry = {
|
||||
file: string;
|
||||
ordinals: number[];
|
||||
configuredFile: string;
|
||||
configurationObject: any;
|
||||
configurationString: string;
|
||||
configuration: Configuration;
|
||||
hash: string;
|
||||
};
|
||||
|
||||
|
|
@ -53,11 +52,11 @@ export class TestRunner extends EventEmitter {
|
|||
this._trialRun = config.trialRun;
|
||||
this._timeout = config.timeout;
|
||||
this._config = config;
|
||||
this._configuredFile = entry.configuredFile;
|
||||
for (const {name, value} of entry.configurationObject)
|
||||
this._configuredFile = entry.file + `::[${entry.configurationString}]`;
|
||||
for (const {name, value} of entry.configuration)
|
||||
this._parsedGeneratorConfiguration[name] = value;
|
||||
this._parsedGeneratorConfiguration['parallelIndex'] = workerId;
|
||||
setCurrentTestFile(path.relative(config.testDir, this._file));
|
||||
setCurrentTestFile(this._file);
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
|
|
|||
|
|
@ -13,18 +13,18 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const crypto = require('crypto');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const pirates = require('pirates');
|
||||
const babel = require('@babel/core');
|
||||
const sourceMapSupport = require('source-map-support');
|
||||
|
||||
import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as pirates from 'pirates';
|
||||
import * as babel from '@babel/core';
|
||||
import * as sourceMapSupport from 'source-map-support';
|
||||
|
||||
const version = 1;
|
||||
const cacheDir = path.join(os.tmpdir(), 'playwright-transform-cache');
|
||||
/** @type {Map<string, string>} */
|
||||
const sourceMaps = new Map();
|
||||
const sourceMaps: Map<string, string> = new Map();
|
||||
|
||||
sourceMapSupport.install({
|
||||
environment: 'node',
|
||||
|
|
@ -42,19 +42,13 @@ sourceMapSupport.install({
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* @param {string} content
|
||||
* @param {string} filePath
|
||||
* @return {string}
|
||||
*/
|
||||
function calculateCachePath(content, filePath) {
|
||||
function calculateCachePath(content: string, filePath: string): string {
|
||||
const hash = crypto.createHash('sha1').update(content).update(filePath).update(String(version)).digest('hex');
|
||||
const fileName = path.basename(filePath, path.extname(filePath)).replace(/\W/g, '') + '_' + hash;
|
||||
|
||||
return path.join(cacheDir, hash[0] + hash[1], fileName);
|
||||
}
|
||||
|
||||
function installTransform() {
|
||||
export function installTransform(): () => void {
|
||||
return pirates.addHook((code, filename) => {
|
||||
const cachePath = calculateCachePath(code, filename);
|
||||
const codePath = cachePath + '.js';
|
||||
|
|
@ -80,5 +74,3 @@ function installTransform() {
|
|||
exts: ['.ts']
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {installTransform};
|
||||
Loading…
Reference in a new issue