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 5 // test_custom_bindings.js 6 // mini-framework for ExtensionApiTest browser tests 7 8 var binding = require('binding').Binding.create('test'); 9 10 var chrome = requireNative('chrome').GetChrome(); 11 var GetExtensionAPIDefinitionsForTest = 12 requireNative('apiDefinitions').GetExtensionAPIDefinitionsForTest; 13 var GetAvailability = requireNative('v8_context').GetAvailability; 14 var GetAPIFeatures = requireNative('test_features').GetAPIFeatures; 15 var uncaughtExceptionHandler = require('uncaught_exception_handler'); 16 var userGestures = requireNative('user_gestures'); 17 18 binding.registerCustomHook(function(api) { 19 var chromeTest = api.compiledApi; 20 var apiFunctions = api.apiFunctions; 21 22 chromeTest.tests = chromeTest.tests || []; 23 24 var currentTest = null; 25 var lastTest = null; 26 var testsFailed = 0; 27 var testCount = 1; 28 var failureException = 'chrome.test.failure'; 29 30 // Helper function to get around the fact that function names in javascript 31 // are read-only, and you can't assign one to anonymous functions. 32 function testName(test) { 33 return test ? (test.name || test.generatedName) : "(no test)"; 34 } 35 36 function testDone() { 37 // Use setTimeout here to allow previous test contexts to be 38 // eligible for garbage collection. 39 setTimeout(chromeTest.runNextTest, 0); 40 } 41 42 function allTestsDone() { 43 if (testsFailed == 0) { 44 chromeTest.notifyPass(); 45 } else { 46 chromeTest.notifyFail('Failed ' + testsFailed + ' of ' + 47 testCount + ' tests'); 48 } 49 } 50 51 var pendingCallbacks = 0; 52 53 apiFunctions.setHandleRequest('callbackAdded', function() { 54 pendingCallbacks++; 55 56 var called = null; 57 return function() { 58 if (called != null) { 59 var redundantPrefix = 'Error\n'; 60 chrome.test.fail( 61 'Callback has already been run. ' + 62 'First call:\n' + 63 $String.slice(called, redundantPrefix.length) + '\n' + 64 'Second call:\n' + 65 $String.slice(new Error().stack, redundantPrefix.length)); 66 } 67 called = new Error().stack; 68 69 pendingCallbacks--; 70 if (pendingCallbacks == 0) { 71 chromeTest.succeed(); 72 } 73 }; 74 }); 75 76 apiFunctions.setHandleRequest('runNextTest', function() { 77 // There may have been callbacks which were interrupted by failure 78 // exceptions. 79 pendingCallbacks = 0; 80 81 lastTest = currentTest; 82 currentTest = chromeTest.tests.shift(); 83 84 if (!currentTest) { 85 allTestsDone(); 86 return; 87 } 88 89 try { 90 chromeTest.log("( RUN ) " + testName(currentTest)); 91 uncaughtExceptionHandler.setHandler(function(message, e) { 92 if (e !== failureException) 93 chromeTest.fail('uncaught exception: ' + message); 94 }); 95 currentTest.call(); 96 } catch (e) { 97 uncaughtExceptionHandler.handle(e.message, e); 98 } 99 }); 100 101 apiFunctions.setHandleRequest('fail', function(message) { 102 chromeTest.log("( FAILED ) " + testName(currentTest)); 103 104 var stack = {}; 105 Error.captureStackTrace(stack, chromeTest.fail); 106 107 if (!message) 108 message = "FAIL (no message)"; 109 110 message += "\n" + stack.stack; 111 console.log("[FAIL] " + testName(currentTest) + ": " + message); 112 testsFailed++; 113 testDone(); 114 115 // Interrupt the rest of the test. 116 throw failureException; 117 }); 118 119 apiFunctions.setHandleRequest('succeed', function() { 120 console.log("[SUCCESS] " + testName(currentTest)); 121 chromeTest.log("( SUCCESS )"); 122 testDone(); 123 }); 124 125 apiFunctions.setHandleRequest('assertTrue', function(test, message) { 126 chromeTest.assertBool(test, true, message); 127 }); 128 129 apiFunctions.setHandleRequest('assertFalse', function(test, message) { 130 chromeTest.assertBool(test, false, message); 131 }); 132 133 apiFunctions.setHandleRequest('assertBool', 134 function(test, expected, message) { 135 if (test !== expected) { 136 if (typeof(test) == "string") { 137 if (message) 138 message = test + "\n" + message; 139 else 140 message = test; 141 } 142 chromeTest.fail(message); 143 } 144 }); 145 146 apiFunctions.setHandleRequest('checkDeepEq', function(expected, actual) { 147 if ((expected === null) != (actual === null)) 148 return false; 149 150 if (expected === actual) 151 return true; 152 153 if (typeof(expected) !== typeof(actual)) 154 return false; 155 156 for (var p in actual) { 157 if ($Object.hasOwnProperty(actual, p) && 158 !$Object.hasOwnProperty(expected, p)) { 159 return false; 160 } 161 } 162 for (var p in expected) { 163 if ($Object.hasOwnProperty(expected, p) && 164 !$Object.hasOwnProperty(actual, p)) { 165 return false; 166 } 167 } 168 169 for (var p in expected) { 170 var eq = true; 171 switch (typeof(expected[p])) { 172 case 'object': 173 eq = chromeTest.checkDeepEq(expected[p], actual[p]); 174 break; 175 case 'function': 176 eq = (typeof(actual[p]) != 'undefined' && 177 expected[p].toString() == actual[p].toString()); 178 break; 179 default: 180 eq = (expected[p] == actual[p] && 181 typeof(expected[p]) == typeof(actual[p])); 182 break; 183 } 184 if (!eq) 185 return false; 186 } 187 return true; 188 }); 189 190 apiFunctions.setHandleRequest('assertEq', 191 function(expected, actual, message) { 192 var error_msg = "API Test Error in " + testName(currentTest); 193 if (message) 194 error_msg += ": " + message; 195 if (typeof(expected) == 'object') { 196 if (!chromeTest.checkDeepEq(expected, actual)) { 197 // Note: these JSON.stringify calls may fail in tests that explicitly 198 // override JSON.stringfy, so surround in try-catch. 199 try { 200 error_msg += "\nActual: " + JSON.stringify(actual) + 201 "\nExpected: " + JSON.stringify(expected); 202 } catch (e) {} 203 chromeTest.fail(error_msg); 204 } 205 return; 206 } 207 if (expected != actual) { 208 chromeTest.fail(error_msg + 209 "\nActual: " + actual + "\nExpected: " + expected); 210 } 211 if (typeof(expected) != typeof(actual)) { 212 chromeTest.fail(error_msg + 213 " (type mismatch)\nActual Type: " + typeof(actual) + 214 "\nExpected Type:" + typeof(expected)); 215 } 216 }); 217 218 apiFunctions.setHandleRequest('assertNoLastError', function() { 219 if (chrome.runtime.lastError != undefined) { 220 chromeTest.fail("lastError.message == " + 221 chrome.runtime.lastError.message); 222 } 223 }); 224 225 apiFunctions.setHandleRequest('assertLastError', function(expectedError) { 226 chromeTest.assertEq(typeof(expectedError), 'string'); 227 chromeTest.assertTrue(chrome.runtime.lastError != undefined, 228 "No lastError, but expected " + expectedError); 229 chromeTest.assertEq(expectedError, chrome.runtime.lastError.message); 230 }); 231 232 apiFunctions.setHandleRequest('assertThrows', 233 function(fn, self, args, message) { 234 chromeTest.assertTrue(typeof fn == 'function'); 235 try { 236 fn.apply(self, args); 237 chromeTest.fail('Did not throw error: ' + fn); 238 } catch (e) { 239 if (e != failureException && message !== undefined) { 240 if (message instanceof RegExp) { 241 chromeTest.assertTrue(message.test(e.message), 242 e.message + ' should match ' + message) 243 } else { 244 chromeTest.assertEq(message, e.message); 245 } 246 } 247 } 248 }); 249 250 function safeFunctionApply(func, args) { 251 try { 252 if (func) 253 return $Function.apply(func, undefined, args); 254 } catch (e) { 255 var msg = "uncaught exception " + e; 256 chromeTest.fail(msg); 257 } 258 }; 259 260 // Wrapper for generating test functions, that takes care of calling 261 // assertNoLastError() and (optionally) succeed() for you. 262 apiFunctions.setHandleRequest('callback', function(func, expectedError) { 263 if (func) { 264 chromeTest.assertEq(typeof(func), 'function'); 265 } 266 var callbackCompleted = chromeTest.callbackAdded(); 267 268 return function() { 269 if (expectedError == null) { 270 chromeTest.assertNoLastError(); 271 } else { 272 chromeTest.assertLastError(expectedError); 273 } 274 275 var result; 276 if (func) { 277 result = safeFunctionApply(func, arguments); 278 } 279 280 callbackCompleted(); 281 return result; 282 }; 283 }); 284 285 apiFunctions.setHandleRequest('listenOnce', function(event, func) { 286 var callbackCompleted = chromeTest.callbackAdded(); 287 var listener = function() { 288 event.removeListener(listener); 289 safeFunctionApply(func, arguments); 290 callbackCompleted(); 291 }; 292 event.addListener(listener); 293 }); 294 295 apiFunctions.setHandleRequest('listenForever', function(event, func) { 296 var callbackCompleted = chromeTest.callbackAdded(); 297 298 var listener = function() { 299 safeFunctionApply(func, arguments); 300 }; 301 302 var done = function() { 303 event.removeListener(listener); 304 callbackCompleted(); 305 }; 306 307 event.addListener(listener); 308 return done; 309 }); 310 311 apiFunctions.setHandleRequest('callbackPass', function(func) { 312 return chromeTest.callback(func); 313 }); 314 315 apiFunctions.setHandleRequest('callbackFail', function(expectedError, func) { 316 return chromeTest.callback(func, expectedError); 317 }); 318 319 apiFunctions.setHandleRequest('runTests', function(tests) { 320 chromeTest.tests = tests; 321 testCount = chromeTest.tests.length; 322 chromeTest.runNextTest(); 323 }); 324 325 apiFunctions.setHandleRequest('getApiDefinitions', function() { 326 return GetExtensionAPIDefinitionsForTest(); 327 }); 328 329 apiFunctions.setHandleRequest('getApiFeatures', function() { 330 return GetAPIFeatures(); 331 }); 332 333 apiFunctions.setHandleRequest('isProcessingUserGesture', function() { 334 return userGestures.IsProcessingUserGesture(); 335 }); 336 337 apiFunctions.setHandleRequest('runWithUserGesture', function(callback) { 338 chromeTest.assertEq(typeof(callback), 'function'); 339 return userGestures.RunWithUserGesture(callback); 340 }); 341 342 apiFunctions.setHandleRequest('runWithoutUserGesture', function(callback) { 343 chromeTest.assertEq(typeof(callback), 'function'); 344 return userGestures.RunWithoutUserGesture(callback); 345 }); 346 347 apiFunctions.setHandleRequest('setExceptionHandler', function(callback) { 348 chromeTest.assertEq(typeof(callback), 'function'); 349 uncaughtExceptionHandler.setHandler(callback); 350 }); 351 }); 352 353 exports.binding = binding.generate(); 354