/** * @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 {} }