/**
|
* @license
|
* Copyright 2017 Google Inc. 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.
|
* =============================================================================
|
*/
|
|
// We use the pattern below (as opposed to require('jasmine') to create the
|
// jasmine module in order to avoid loading node specific modules which may
|
// be ignored in browser environments but cannot be ignored in react-native
|
// due to the pre-bundling of dependencies that it must do.
|
// tslint:disable-next-line:no-require-imports
|
const jasmineRequire = require('jasmine-core/lib/jasmine-core/jasmine.js');
|
const jasmineCore = jasmineRequire.core(jasmineRequire);
|
import {KernelBackend} from './backends/backend';
|
import {ENGINE} from './engine';
|
import {env, Environment, Flags} from './environment';
|
|
Error.stackTraceLimit = Infinity;
|
jasmineCore.DEFAULT_TIMEOUT_INTERVAL = 10000;
|
|
export type Constraints = {
|
flags?: Flags,
|
predicate?: (testEnv: TestEnv) => boolean,
|
};
|
|
export const NODE_ENVS: Constraints = {
|
predicate: () => env().platformName === 'node'
|
};
|
export const CHROME_ENVS: Constraints = {
|
flags: {'IS_CHROME': true}
|
};
|
export const BROWSER_ENVS: Constraints = {
|
predicate: () => env().platformName === 'browser'
|
};
|
|
export const SYNC_BACKEND_ENVS: Constraints = {
|
predicate: (testEnv: TestEnv) => testEnv.isDataSync === true
|
};
|
|
export const HAS_WORKER = {
|
predicate: () => typeof (Worker) !== 'undefined' &&
|
typeof (Blob) !== 'undefined' && typeof (URL) !== 'undefined'
|
};
|
|
export const HAS_NODE_WORKER = {
|
predicate: () => {
|
let hasWorker = true;
|
try {
|
require.resolve('worker_threads');
|
} catch {
|
hasWorker = false;
|
}
|
return typeof (process) !== 'undefined' && hasWorker;
|
}
|
};
|
|
export const ALL_ENVS: Constraints = {};
|
|
// Tests whether the current environment satisfies the set of constraints.
|
export function envSatisfiesConstraints(
|
env: Environment, testEnv: TestEnv, constraints: Constraints): boolean {
|
if (constraints == null) {
|
return true;
|
}
|
|
if (constraints.flags != null) {
|
for (const flagName in constraints.flags) {
|
const flagValue = constraints.flags[flagName];
|
if (env.get(flagName) !== flagValue) {
|
return false;
|
}
|
}
|
}
|
if (constraints.predicate != null && !constraints.predicate(testEnv)) {
|
return false;
|
}
|
return true;
|
}
|
|
export interface TestFilter {
|
include?: string;
|
startsWith?: string;
|
excludes?: string[];
|
}
|
|
export function setupTestFilters(
|
testFilters: TestFilter[], customInclude: (name: string) => boolean) {
|
const env = jasmine.getEnv();
|
// Account for --grep flag passed to karma by saving the existing specFilter.
|
const grepFilter = env.specFilter;
|
|
/**
|
* Filter method that returns boolean, if a given test should run or be
|
* ignored based on its name. The exclude list has priority over the
|
* include list. Thus, if a test matches both the exclude and the include
|
* list, it will be exluded.
|
*/
|
// tslint:disable-next-line: no-any
|
env.specFilter = (spec: any) => {
|
// Filter out tests if the --grep flag is passed.
|
if (!grepFilter(spec)) {
|
return false;
|
}
|
|
const name = spec.getFullName();
|
|
if (customInclude(name)) {
|
return true;
|
}
|
|
// Include a describeWithFlags() test from tfjs-core only if the test is
|
// in the include list.
|
for (let i = 0; i < testFilters.length; ++i) {
|
const testFilter = testFilters[i];
|
if ((testFilter.include != null &&
|
name.indexOf(testFilter.include) > -1) ||
|
(testFilter.startsWith != null &&
|
name.startsWith(testFilter.startsWith))) {
|
if (testFilter.excludes != null) {
|
for (let j = 0; j < testFilter.excludes.length; j++) {
|
if (name.indexOf(testFilter.excludes[j]) > -1) {
|
return false;
|
}
|
}
|
}
|
return true;
|
}
|
}
|
// Otherwise ignore the test.
|
return false;
|
};
|
}
|
|
export function parseTestEnvFromKarmaFlags(
|
args: string[], registeredTestEnvs: TestEnv[]): TestEnv {
|
let flags: Flags;
|
let testEnvName: string;
|
|
args.forEach((arg, i) => {
|
if (arg === '--flags') {
|
flags = JSON.parse(args[i + 1]);
|
} else if (arg === '--testEnv') {
|
testEnvName = args[i + 1];
|
}
|
});
|
|
const testEnvNames = registeredTestEnvs.map(env => env.name).join(', ');
|
if (flags != null && testEnvName == null) {
|
throw new Error(
|
'--testEnv flag is required when --flags is present. ' +
|
`Available values are [${testEnvNames}].`);
|
}
|
if (testEnvName == null) {
|
return null;
|
}
|
|
let testEnv: TestEnv;
|
registeredTestEnvs.forEach(env => {
|
if (env.name === testEnvName) {
|
testEnv = env;
|
}
|
});
|
if (testEnv == null) {
|
throw new Error(
|
`Test environment with name ${testEnvName} not ` +
|
`found. Available test environment names are ` +
|
`${testEnvNames}`);
|
}
|
if (flags != null) {
|
testEnv.flags = flags;
|
}
|
|
return testEnv;
|
}
|
|
export function describeWithFlags(
|
name: string, constraints: Constraints, tests: (env: TestEnv) => void) {
|
if (TEST_ENVS.length === 0) {
|
throw new Error(
|
`Found no test environments. This is likely due to test environment ` +
|
`registries never being imported or test environment registries ` +
|
`being registered too late.`);
|
}
|
|
TEST_ENVS.forEach(testEnv => {
|
env().setFlags(testEnv.flags);
|
if (envSatisfiesConstraints(env(), testEnv, constraints)) {
|
const testName =
|
name + ' ' + testEnv.name + ' ' + JSON.stringify(testEnv.flags || {});
|
executeTests(testName, tests, testEnv);
|
}
|
});
|
}
|
|
export interface TestEnv {
|
name: string;
|
backendName: string;
|
flags?: Flags;
|
isDataSync?: boolean;
|
}
|
|
export const TEST_ENVS: TestEnv[] = [];
|
|
// Whether a call to setTestEnvs has been called so we turn off
|
// registration. This allows command line overriding or programmatic
|
// overriding of the default registrations.
|
let testEnvSet = false;
|
export function setTestEnvs(testEnvs: TestEnv[]) {
|
testEnvSet = true;
|
TEST_ENVS.length = 0;
|
TEST_ENVS.push(...testEnvs);
|
}
|
|
export function registerTestEnv(testEnv: TestEnv) {
|
// When using an explicit call to setTestEnvs, turn off registration of
|
// test environments because the explicit call will set the test
|
// environments.
|
if (testEnvSet) {
|
return;
|
}
|
TEST_ENVS.push(testEnv);
|
}
|
|
function executeTests(
|
testName: string, tests: (env: TestEnv) => void, testEnv: TestEnv) {
|
describe(testName, () => {
|
beforeAll(async () => {
|
ENGINE.reset();
|
if (testEnv.flags != null) {
|
env().setFlags(testEnv.flags);
|
}
|
env().set('IS_TEST', true);
|
// Await setting the new backend since it can have async init.
|
await ENGINE.setBackend(testEnv.backendName);
|
});
|
|
beforeEach(() => {
|
ENGINE.startScope();
|
});
|
|
afterEach(() => {
|
ENGINE.endScope();
|
ENGINE.disposeVariables();
|
});
|
|
afterAll(() => {
|
ENGINE.reset();
|
});
|
|
tests(testEnv);
|
});
|
}
|
|
export class TestKernelBackend extends KernelBackend {
|
dispose(): void {}
|
}
|