test_custom_bindings.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. // test_custom_bindings.js
  5. // mini-framework for ExtensionApiTest browser tests
  6. var environmentSpecificBindings = require('test_environment_specific_bindings');
  7. var GetExtensionAPIDefinitionsForTest =
  8. requireNative('apiDefinitions').GetExtensionAPIDefinitionsForTest;
  9. var GetAPIFeatures = requireNative('test_features').GetAPIFeatures;
  10. var natives = requireNative('test_native_handler');
  11. var userGestures = requireNative('user_gestures');
  12. var GetModuleSystem = requireNative('v8_context').GetModuleSystem;
  13. function handleException(message, error) {
  14. bindingUtil.handleException(message || 'Unknown error', error);
  15. }
  16. apiBridge.registerCustomHook(function(api) {
  17. var chromeTest = api.compiledApi;
  18. var apiFunctions = api.apiFunctions;
  19. chromeTest.tests = chromeTest.tests || [];
  20. var currentTest = null;
  21. var lastTest = null;
  22. var testsFailed = 0;
  23. var testCount = 1;
  24. var failureException = 'chrome.test.failure';
  25. var pendingCallbacks = 0;
  26. var pendingPromiseRejections = 0;
  27. function safeFunctionApply(func, args) {
  28. try {
  29. if (func)
  30. return $Function.apply(func, undefined, args);
  31. } catch (e) {
  32. if (e === failureException)
  33. throw e;
  34. handleException(e.message, e);
  35. }
  36. }
  37. function runNextTest() {
  38. // There may have been callbacks or promise rejections which were
  39. // interrupted by failure exceptions.
  40. pendingCallbacks = 0;
  41. pendingPromiseRejections = 0;
  42. lastTest = currentTest;
  43. currentTest = $Array.shift(chromeTest.tests);
  44. if (!currentTest) {
  45. allTestsDone();
  46. return;
  47. }
  48. try {
  49. chromeTest.log("( RUN ) " + testName(currentTest));
  50. bindingUtil.setExceptionHandler(function(message, e) {
  51. if (e !== failureException)
  52. chromeTest.fail('uncaught exception: ' + message);
  53. });
  54. $Function.call(currentTest);
  55. } catch (e) {
  56. handleException(e.message, e);
  57. }
  58. }
  59. // Helper function to get around the fact that function names in javascript
  60. // are read-only, and you can't assign one to anonymous functions.
  61. function testName(test) {
  62. return test ? (test.name || test.generatedName) : "(no test)";
  63. }
  64. function testDone() {
  65. environmentSpecificBindings.testDone(runNextTest);
  66. }
  67. function allTestsDone() {
  68. if (testsFailed == 0) {
  69. chromeTest.notifyPass();
  70. } else {
  71. chromeTest.notifyFail('Failed ' + testsFailed + ' of ' +
  72. testCount + ' tests');
  73. }
  74. }
  75. // Helper function for boolean asserts. Compares |test| to |expected|.
  76. function assertBool(test, expected, message) {
  77. if (test !== expected) {
  78. if (typeof(test) == "string") {
  79. if (message)
  80. message = test + "\n" + message;
  81. else
  82. message = test;
  83. }
  84. chromeTest.fail(message);
  85. }
  86. }
  87. apiFunctions.setHandleRequest('callbackAdded', function() {
  88. pendingCallbacks++;
  89. var called = null;
  90. return function() {
  91. if (called != null) {
  92. var redundantPrefix = 'Error\n';
  93. chromeTest.fail(
  94. 'Callback has already been run. ' +
  95. 'First call:\n' +
  96. $String.slice(called, redundantPrefix.length) + '\n' +
  97. 'Second call:\n' +
  98. $String.slice(new Error().stack, redundantPrefix.length));
  99. }
  100. called = new Error().stack;
  101. pendingCallbacks--;
  102. if (pendingCallbacks == 0) {
  103. chromeTest.succeed();
  104. }
  105. };
  106. });
  107. apiFunctions.setHandleRequest('fail', function failHandler(message) {
  108. chromeTest.log("( FAILED ) " + testName(currentTest));
  109. var stack = {};
  110. // NOTE(devlin): captureStackTrace() populates a stack property of the
  111. // passed-in object with the stack trace. The second parameter (failHandler)
  112. // represents a function to serve as a relative point, and is removed from
  113. // the trace (so that everything doesn't include failHandler in the trace
  114. // itself). This (and other APIs) are documented here:
  115. // https://github.com/v8/v8/wiki/Stack%20Trace%20API. If we wanted to be
  116. // really fancy, there may be more sophisticated ways of doing this.
  117. Error.captureStackTrace(stack, failHandler);
  118. if (!message)
  119. message = "FAIL (no message)";
  120. message += "\n" + stack.stack;
  121. console.log("[FAIL] " + testName(currentTest) + ": " + message);
  122. testsFailed++;
  123. testDone();
  124. // Interrupt the rest of the test.
  125. throw failureException;
  126. });
  127. apiFunctions.setHandleRequest('succeed', function() {
  128. chromeTest.assertEq(
  129. 0, pendingPromiseRejections,
  130. 'Test had pending promise rejections. This is likely the result of ' +
  131. 'not waiting for the promise returned by `assertPromiseRejects()` to ' +
  132. 'resolve. Instead, use `await assertPromiseRejects(...)` or ' +
  133. '`assertPromiseRejects(...).then(...).`.');
  134. console.log("[SUCCESS] " + testName(currentTest));
  135. chromeTest.log("( SUCCESS )");
  136. testDone();
  137. });
  138. apiFunctions.setHandleRequest('getModuleSystem', function(context) {
  139. return GetModuleSystem(context);
  140. });
  141. apiFunctions.setHandleRequest('assertTrue', function(test, message) {
  142. assertBool(test, true, message);
  143. });
  144. apiFunctions.setHandleRequest('assertFalse', function(test, message) {
  145. assertBool(test, false, message);
  146. });
  147. apiFunctions.setHandleRequest('checkDeepEq', function(expected, actual) {
  148. if ((expected === null) != (actual === null))
  149. return false;
  150. if (expected === actual)
  151. return true;
  152. if (typeof(expected) !== typeof(actual))
  153. return false;
  154. if (Array.isArray(expected) !== Array.isArray(actual))
  155. return false;
  156. // Handle the ArrayBuffer cases. Bail out in case of type mismatch, to
  157. // prevent the ArrayBuffer from being treated as an empty enumerable below.
  158. if ((actual instanceof ArrayBuffer) !== (expected instanceof ArrayBuffer))
  159. return false;
  160. if ((actual instanceof ArrayBuffer) && (expected instanceof ArrayBuffer)) {
  161. if (actual.byteLength != expected.byteLength)
  162. return false;
  163. var actualView = new Uint8Array(actual);
  164. var expectedView = new Uint8Array(expected);
  165. for (var i = 0; i < actualView.length; ++i) {
  166. if (actualView[i] != expectedView[i]) {
  167. return false;
  168. }
  169. }
  170. return true;
  171. }
  172. for (var p in actual) {
  173. if ($Object.hasOwnProperty(actual, p) &&
  174. !$Object.hasOwnProperty(expected, p)) {
  175. return false;
  176. }
  177. }
  178. for (var p in expected) {
  179. if ($Object.hasOwnProperty(expected, p) &&
  180. !$Object.hasOwnProperty(actual, p)) {
  181. return false;
  182. }
  183. }
  184. for (var p in expected) {
  185. var eq = true;
  186. switch (typeof(expected[p])) {
  187. case 'object':
  188. eq = chromeTest.checkDeepEq(expected[p], actual[p]);
  189. break;
  190. case 'function':
  191. eq = (typeof(actual[p]) != 'undefined' &&
  192. expected[p].toString() == actual[p].toString());
  193. break;
  194. default:
  195. eq = (expected[p] == actual[p] &&
  196. typeof(expected[p]) == typeof(actual[p]));
  197. break;
  198. }
  199. if (!eq)
  200. return false;
  201. }
  202. return true;
  203. });
  204. apiFunctions.setHandleRequest('assertEq',
  205. function(expected, actual, message) {
  206. var error_msg = "API Test Error in " + testName(currentTest);
  207. if (message)
  208. error_msg += ": " + message;
  209. if (typeof(expected) == 'object') {
  210. if (!chromeTest.checkDeepEq(expected, actual)) {
  211. error_msg += "\nActual: " + $JSON.stringify(actual) +
  212. "\nExpected: " + $JSON.stringify(expected);
  213. chromeTest.fail(error_msg);
  214. }
  215. return;
  216. }
  217. if (expected != actual) {
  218. chromeTest.fail(error_msg +
  219. "\nActual: " + actual + "\nExpected: " + expected);
  220. }
  221. if (typeof(expected) != typeof(actual)) {
  222. chromeTest.fail(error_msg +
  223. " (type mismatch)\nActual Type: " + typeof(actual) +
  224. "\nExpected Type:" + typeof(expected));
  225. }
  226. });
  227. apiFunctions.setHandleRequest('assertNoLastError', function() {
  228. if (chrome.runtime.lastError != undefined) {
  229. chromeTest.fail("lastError.message == " +
  230. chrome.runtime.lastError.message);
  231. }
  232. });
  233. apiFunctions.setHandleRequest('assertLastError', function(expectedError) {
  234. chromeTest.assertEq(typeof(expectedError), 'string');
  235. chromeTest.assertTrue(chrome.runtime.lastError != undefined,
  236. "No lastError, but expected " + expectedError);
  237. chromeTest.assertEq(expectedError, chrome.runtime.lastError.message);
  238. });
  239. apiFunctions.setHandleRequest('assertThrows',
  240. function(fn, self, args, message) {
  241. chromeTest.assertTrue(typeof fn == 'function');
  242. try {
  243. fn.apply(self, args);
  244. chromeTest.fail('Did not throw error: ' + fn);
  245. } catch (e) {
  246. if (e != failureException && message !== undefined) {
  247. if (message instanceof RegExp) {
  248. chromeTest.assertTrue(message.test(e.message),
  249. e.message + ' should match ' + message)
  250. } else {
  251. chromeTest.assertEq(message, e.message);
  252. }
  253. }
  254. }
  255. });
  256. apiFunctions.setHandleRequest('loadScript', function(scriptUrl) {
  257. // Note: Importing scripts is different depending on if this script is
  258. // executing in a Service Worker context.
  259. const inServiceWorker = 'ServiceWorkerGlobalScope' in self;
  260. function createError(exception) {
  261. let errorStr = 'Unable to load script: "' + scriptUrl + '"';
  262. if (inServiceWorker) {
  263. return new Error(errorStr, { cause:exception });
  264. } else {
  265. return new Error(errorStr);
  266. }
  267. }
  268. if (inServiceWorker) {
  269. try {
  270. importScripts(scriptUrl);
  271. } catch (e) {
  272. return Promise.reject(createError(e));
  273. }
  274. return Promise.resolve();
  275. }
  276. let script = document.createElement('script');
  277. let onScriptLoad = new Promise((resolve, reject) => {
  278. script.onload = resolve;
  279. function onError() {
  280. reject(createError());
  281. }
  282. script.onerror = onError;
  283. });
  284. script.src = scriptUrl;
  285. document.body.appendChild(script);
  286. return onScriptLoad;
  287. });
  288. apiFunctions.setHandleRequest('assertPromiseRejects',
  289. function(promise, expectedMessage) {
  290. pendingPromiseRejections++;
  291. return promise.then(
  292. () => {
  293. pendingPromiseRejections--;
  294. chromeTest.assertTrue(pendingPromiseRejections >= 0,
  295. 'Negative pending promise rejection count!');
  296. chromeTest.fail(
  297. 'Promise did not reject. Expected error: ' + expectedMessage);
  298. },
  299. (e) => {
  300. pendingPromiseRejections--;
  301. chromeTest.assertTrue(pendingPromiseRejections >= 0,
  302. 'Negative pending promise rejection count!');
  303. if (expectedMessage instanceof RegExp) {
  304. chromeTest.assertTrue(
  305. expectedMessage.test(e.toString()),
  306. `"${e.message}" should match "${expectedMessage}"`);
  307. } else {
  308. chromeTest.assertEq('string', typeof expectedMessage);
  309. chromeTest.assertEq(expectedMessage, e.toString());
  310. }
  311. });
  312. });
  313. // Wrapper for generating test functions, that takes care of calling
  314. // assertNoLastError() and (optionally) succeed() for you.
  315. apiFunctions.setHandleRequest('callback', function(func, expectedError) {
  316. if (func) {
  317. chromeTest.assertEq(typeof(func), 'function');
  318. }
  319. var callbackCompleted = chromeTest.callbackAdded();
  320. return function() {
  321. if (expectedError == null) {
  322. chromeTest.assertNoLastError();
  323. } else {
  324. chromeTest.assertLastError(expectedError);
  325. }
  326. var result;
  327. if (func) {
  328. result = safeFunctionApply(func, arguments);
  329. }
  330. callbackCompleted();
  331. return result;
  332. };
  333. });
  334. apiFunctions.setHandleRequest('listenOnce', function(event, func) {
  335. var callbackCompleted = chromeTest.callbackAdded();
  336. var listener = function() {
  337. event.removeListener(listener);
  338. safeFunctionApply(func, arguments);
  339. callbackCompleted();
  340. };
  341. event.addListener(listener);
  342. });
  343. apiFunctions.setHandleRequest('listenForever', function(event, func) {
  344. var callbackCompleted = chromeTest.callbackAdded();
  345. var listener = function() {
  346. safeFunctionApply(func, arguments);
  347. };
  348. var done = function() {
  349. event.removeListener(listener);
  350. callbackCompleted();
  351. };
  352. event.addListener(listener);
  353. return done;
  354. });
  355. apiFunctions.setHandleRequest('callbackPass', function(func) {
  356. return chromeTest.callback(func);
  357. });
  358. apiFunctions.setHandleRequest('callbackFail', function(expectedError, func) {
  359. return chromeTest.callback(func, expectedError);
  360. });
  361. apiFunctions.setHandleRequest('runTests', function(tests) {
  362. chromeTest.tests = tests;
  363. testCount = chromeTest.tests.length;
  364. runNextTest();
  365. });
  366. apiFunctions.setHandleRequest('getApiDefinitions', function() {
  367. return GetExtensionAPIDefinitionsForTest();
  368. });
  369. apiFunctions.setHandleRequest('getApiFeatures', function() {
  370. return GetAPIFeatures();
  371. });
  372. apiFunctions.setHandleRequest('isProcessingUserGesture', function() {
  373. return userGestures.IsProcessingUserGesture();
  374. });
  375. apiFunctions.setHandleRequest('runWithUserGesture', function(callback) {
  376. chromeTest.assertEq(typeof(callback), 'function');
  377. return userGestures.RunWithUserGesture(callback);
  378. });
  379. apiFunctions.setHandleRequest('setExceptionHandler', function(callback) {
  380. chromeTest.assertEq(typeof(callback), 'function');
  381. bindingUtil.setExceptionHandler(callback);
  382. });
  383. apiFunctions.setHandleRequest('getWakeEventPage', function() {
  384. return natives.GetWakeEventPage();
  385. });
  386. environmentSpecificBindings.registerHooks(api);
  387. });