123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- // Copyright 2014 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- // test_custom_bindings.js
- // mini-framework for ExtensionApiTest browser tests
- var environmentSpecificBindings = require('test_environment_specific_bindings');
- var GetExtensionAPIDefinitionsForTest =
- requireNative('apiDefinitions').GetExtensionAPIDefinitionsForTest;
- var GetAPIFeatures = requireNative('test_features').GetAPIFeatures;
- var natives = requireNative('test_native_handler');
- var userGestures = requireNative('user_gestures');
- var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
- function handleException(message, error) {
- bindingUtil.handleException(message || 'Unknown error', error);
- }
- apiBridge.registerCustomHook(function(api) {
- var chromeTest = api.compiledApi;
- var apiFunctions = api.apiFunctions;
- chromeTest.tests = chromeTest.tests || [];
- var currentTest = null;
- var lastTest = null;
- var testsFailed = 0;
- var testCount = 1;
- var failureException = 'chrome.test.failure';
- var pendingCallbacks = 0;
- var pendingPromiseRejections = 0;
- function safeFunctionApply(func, args) {
- try {
- if (func)
- return $Function.apply(func, undefined, args);
- } catch (e) {
- if (e === failureException)
- throw e;
- handleException(e.message, e);
- }
- }
- function runNextTest() {
- // There may have been callbacks or promise rejections which were
- // interrupted by failure exceptions.
- pendingCallbacks = 0;
- pendingPromiseRejections = 0;
- lastTest = currentTest;
- currentTest = $Array.shift(chromeTest.tests);
- if (!currentTest) {
- allTestsDone();
- return;
- }
- try {
- chromeTest.log("( RUN ) " + testName(currentTest));
- bindingUtil.setExceptionHandler(function(message, e) {
- if (e !== failureException)
- chromeTest.fail('uncaught exception: ' + message);
- });
- $Function.call(currentTest);
- } catch (e) {
- handleException(e.message, e);
- }
- }
- // Helper function to get around the fact that function names in javascript
- // are read-only, and you can't assign one to anonymous functions.
- function testName(test) {
- return test ? (test.name || test.generatedName) : "(no test)";
- }
- function testDone() {
- environmentSpecificBindings.testDone(runNextTest);
- }
- function allTestsDone() {
- if (testsFailed == 0) {
- chromeTest.notifyPass();
- } else {
- chromeTest.notifyFail('Failed ' + testsFailed + ' of ' +
- testCount + ' tests');
- }
- }
- // Helper function for boolean asserts. Compares |test| to |expected|.
- function assertBool(test, expected, message) {
- if (test !== expected) {
- if (typeof(test) == "string") {
- if (message)
- message = test + "\n" + message;
- else
- message = test;
- }
- chromeTest.fail(message);
- }
- }
- apiFunctions.setHandleRequest('callbackAdded', function() {
- pendingCallbacks++;
- var called = null;
- return function() {
- if (called != null) {
- var redundantPrefix = 'Error\n';
- chromeTest.fail(
- 'Callback has already been run. ' +
- 'First call:\n' +
- $String.slice(called, redundantPrefix.length) + '\n' +
- 'Second call:\n' +
- $String.slice(new Error().stack, redundantPrefix.length));
- }
- called = new Error().stack;
- pendingCallbacks--;
- if (pendingCallbacks == 0) {
- chromeTest.succeed();
- }
- };
- });
- apiFunctions.setHandleRequest('fail', function failHandler(message) {
- chromeTest.log("( FAILED ) " + testName(currentTest));
- var stack = {};
- // NOTE(devlin): captureStackTrace() populates a stack property of the
- // passed-in object with the stack trace. The second parameter (failHandler)
- // represents a function to serve as a relative point, and is removed from
- // the trace (so that everything doesn't include failHandler in the trace
- // itself). This (and other APIs) are documented here:
- // https://github.com/v8/v8/wiki/Stack%20Trace%20API. If we wanted to be
- // really fancy, there may be more sophisticated ways of doing this.
- Error.captureStackTrace(stack, failHandler);
- if (!message)
- message = "FAIL (no message)";
- message += "\n" + stack.stack;
- console.log("[FAIL] " + testName(currentTest) + ": " + message);
- testsFailed++;
- testDone();
- // Interrupt the rest of the test.
- throw failureException;
- });
- apiFunctions.setHandleRequest('succeed', function() {
- chromeTest.assertEq(
- 0, pendingPromiseRejections,
- 'Test had pending promise rejections. This is likely the result of ' +
- 'not waiting for the promise returned by `assertPromiseRejects()` to ' +
- 'resolve. Instead, use `await assertPromiseRejects(...)` or ' +
- '`assertPromiseRejects(...).then(...).`.');
- console.log("[SUCCESS] " + testName(currentTest));
- chromeTest.log("( SUCCESS )");
- testDone();
- });
- apiFunctions.setHandleRequest('getModuleSystem', function(context) {
- return GetModuleSystem(context);
- });
- apiFunctions.setHandleRequest('assertTrue', function(test, message) {
- assertBool(test, true, message);
- });
- apiFunctions.setHandleRequest('assertFalse', function(test, message) {
- assertBool(test, false, message);
- });
- apiFunctions.setHandleRequest('checkDeepEq', function(expected, actual) {
- if ((expected === null) != (actual === null))
- return false;
- if (expected === actual)
- return true;
- if (typeof(expected) !== typeof(actual))
- return false;
- if (Array.isArray(expected) !== Array.isArray(actual))
- return false;
- // Handle the ArrayBuffer cases. Bail out in case of type mismatch, to
- // prevent the ArrayBuffer from being treated as an empty enumerable below.
- if ((actual instanceof ArrayBuffer) !== (expected instanceof ArrayBuffer))
- return false;
- if ((actual instanceof ArrayBuffer) && (expected instanceof ArrayBuffer)) {
- if (actual.byteLength != expected.byteLength)
- return false;
- var actualView = new Uint8Array(actual);
- var expectedView = new Uint8Array(expected);
- for (var i = 0; i < actualView.length; ++i) {
- if (actualView[i] != expectedView[i]) {
- return false;
- }
- }
- return true;
- }
- for (var p in actual) {
- if ($Object.hasOwnProperty(actual, p) &&
- !$Object.hasOwnProperty(expected, p)) {
- return false;
- }
- }
- for (var p in expected) {
- if ($Object.hasOwnProperty(expected, p) &&
- !$Object.hasOwnProperty(actual, p)) {
- return false;
- }
- }
- for (var p in expected) {
- var eq = true;
- switch (typeof(expected[p])) {
- case 'object':
- eq = chromeTest.checkDeepEq(expected[p], actual[p]);
- break;
- case 'function':
- eq = (typeof(actual[p]) != 'undefined' &&
- expected[p].toString() == actual[p].toString());
- break;
- default:
- eq = (expected[p] == actual[p] &&
- typeof(expected[p]) == typeof(actual[p]));
- break;
- }
- if (!eq)
- return false;
- }
- return true;
- });
- apiFunctions.setHandleRequest('assertEq',
- function(expected, actual, message) {
- var error_msg = "API Test Error in " + testName(currentTest);
- if (message)
- error_msg += ": " + message;
- if (typeof(expected) == 'object') {
- if (!chromeTest.checkDeepEq(expected, actual)) {
- error_msg += "\nActual: " + $JSON.stringify(actual) +
- "\nExpected: " + $JSON.stringify(expected);
- chromeTest.fail(error_msg);
- }
- return;
- }
- if (expected != actual) {
- chromeTest.fail(error_msg +
- "\nActual: " + actual + "\nExpected: " + expected);
- }
- if (typeof(expected) != typeof(actual)) {
- chromeTest.fail(error_msg +
- " (type mismatch)\nActual Type: " + typeof(actual) +
- "\nExpected Type:" + typeof(expected));
- }
- });
- apiFunctions.setHandleRequest('assertNoLastError', function() {
- if (chrome.runtime.lastError != undefined) {
- chromeTest.fail("lastError.message == " +
- chrome.runtime.lastError.message);
- }
- });
- apiFunctions.setHandleRequest('assertLastError', function(expectedError) {
- chromeTest.assertEq(typeof(expectedError), 'string');
- chromeTest.assertTrue(chrome.runtime.lastError != undefined,
- "No lastError, but expected " + expectedError);
- chromeTest.assertEq(expectedError, chrome.runtime.lastError.message);
- });
- apiFunctions.setHandleRequest('assertThrows',
- function(fn, self, args, message) {
- chromeTest.assertTrue(typeof fn == 'function');
- try {
- fn.apply(self, args);
- chromeTest.fail('Did not throw error: ' + fn);
- } catch (e) {
- if (e != failureException && message !== undefined) {
- if (message instanceof RegExp) {
- chromeTest.assertTrue(message.test(e.message),
- e.message + ' should match ' + message)
- } else {
- chromeTest.assertEq(message, e.message);
- }
- }
- }
- });
- apiFunctions.setHandleRequest('loadScript', function(scriptUrl) {
- // Note: Importing scripts is different depending on if this script is
- // executing in a Service Worker context.
- const inServiceWorker = 'ServiceWorkerGlobalScope' in self;
- function createError(exception) {
- let errorStr = 'Unable to load script: "' + scriptUrl + '"';
- if (inServiceWorker) {
- return new Error(errorStr, { cause:exception });
- } else {
- return new Error(errorStr);
- }
- }
- if (inServiceWorker) {
- try {
- importScripts(scriptUrl);
- } catch (e) {
- return Promise.reject(createError(e));
- }
- return Promise.resolve();
- }
- let script = document.createElement('script');
- let onScriptLoad = new Promise((resolve, reject) => {
- script.onload = resolve;
- function onError() {
- reject(createError());
- }
- script.onerror = onError;
- });
- script.src = scriptUrl;
- document.body.appendChild(script);
- return onScriptLoad;
- });
- apiFunctions.setHandleRequest('assertPromiseRejects',
- function(promise, expectedMessage) {
- pendingPromiseRejections++;
- return promise.then(
- () => {
- pendingPromiseRejections--;
- chromeTest.assertTrue(pendingPromiseRejections >= 0,
- 'Negative pending promise rejection count!');
- chromeTest.fail(
- 'Promise did not reject. Expected error: ' + expectedMessage);
- },
- (e) => {
- pendingPromiseRejections--;
- chromeTest.assertTrue(pendingPromiseRejections >= 0,
- 'Negative pending promise rejection count!');
- if (expectedMessage instanceof RegExp) {
- chromeTest.assertTrue(
- expectedMessage.test(e.toString()),
- `"${e.message}" should match "${expectedMessage}"`);
- } else {
- chromeTest.assertEq('string', typeof expectedMessage);
- chromeTest.assertEq(expectedMessage, e.toString());
- }
- });
- });
- // Wrapper for generating test functions, that takes care of calling
- // assertNoLastError() and (optionally) succeed() for you.
- apiFunctions.setHandleRequest('callback', function(func, expectedError) {
- if (func) {
- chromeTest.assertEq(typeof(func), 'function');
- }
- var callbackCompleted = chromeTest.callbackAdded();
- return function() {
- if (expectedError == null) {
- chromeTest.assertNoLastError();
- } else {
- chromeTest.assertLastError(expectedError);
- }
- var result;
- if (func) {
- result = safeFunctionApply(func, arguments);
- }
- callbackCompleted();
- return result;
- };
- });
- apiFunctions.setHandleRequest('listenOnce', function(event, func) {
- var callbackCompleted = chromeTest.callbackAdded();
- var listener = function() {
- event.removeListener(listener);
- safeFunctionApply(func, arguments);
- callbackCompleted();
- };
- event.addListener(listener);
- });
- apiFunctions.setHandleRequest('listenForever', function(event, func) {
- var callbackCompleted = chromeTest.callbackAdded();
- var listener = function() {
- safeFunctionApply(func, arguments);
- };
- var done = function() {
- event.removeListener(listener);
- callbackCompleted();
- };
- event.addListener(listener);
- return done;
- });
- apiFunctions.setHandleRequest('callbackPass', function(func) {
- return chromeTest.callback(func);
- });
- apiFunctions.setHandleRequest('callbackFail', function(expectedError, func) {
- return chromeTest.callback(func, expectedError);
- });
- apiFunctions.setHandleRequest('runTests', function(tests) {
- chromeTest.tests = tests;
- testCount = chromeTest.tests.length;
- runNextTest();
- });
- apiFunctions.setHandleRequest('getApiDefinitions', function() {
- return GetExtensionAPIDefinitionsForTest();
- });
- apiFunctions.setHandleRequest('getApiFeatures', function() {
- return GetAPIFeatures();
- });
- apiFunctions.setHandleRequest('isProcessingUserGesture', function() {
- return userGestures.IsProcessingUserGesture();
- });
- apiFunctions.setHandleRequest('runWithUserGesture', function(callback) {
- chromeTest.assertEq(typeof(callback), 'function');
- return userGestures.RunWithUserGesture(callback);
- });
- apiFunctions.setHandleRequest('setExceptionHandler', function(callback) {
- chromeTest.assertEq(typeof(callback), 'function');
- bindingUtil.setExceptionHandler(callback);
- });
- apiFunctions.setHandleRequest('getWakeEventPage', function() {
- return natives.GetWakeEventPage();
- });
- environmentSpecificBindings.registerHooks(api);
- });
|