1 // Copyright (c) 2012 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 userGestures = requireNative('user_gestures'); 16 17 binding.registerCustomHook(function(api) { 18 var chromeTest = api.compiledApi; 19 var apiFunctions = api.apiFunctions; 20 21 chromeTest.tests = chromeTest.tests || []; 22 23 var currentTest = null; 24 var lastTest = null; 25 var testsFailed = 0; 26 var testCount = 1; 27 var failureException = 'chrome.test.failure'; 28 29 // Helper function to get around the fact that function names in javascript 30 // are read-only, and you can't assign one to anonymous functions. 31 function testName(test) { 32 return test ? (test.name || test.generatedName) : "(no test)"; 33 } 34 35 function testDone() { 36 // Use setTimeout here to allow previous test contexts to be 37 // eligible for garbage collection. 38 setTimeout(chromeTest.runNextTest, 0); 39 } 40 41 function allTestsDone() { 42 if (testsFailed == 0) { 43 chromeTest.notifyPass(); 44 } else { 45 chromeTest.notifyFail('Failed ' + testsFailed + ' of ' + 46 testCount + ' tests'); 47 } 48 49 // Try to get the script to stop running immediately. 50 // This isn't an error, just an attempt at saying "done". 51 throw "completed"; 52 } 53 54 var pendingCallbacks = 0; 55 56 apiFunctions.setHandleRequest('callbackAdded', function() { 57 pendingCallbacks++; 58 59 var called = null; 60 return function() { 61 if (called != null) { 62 var redundantPrefix = 'Error\n'; 63 chrome.test.fail( 64 'Callback has already been run. ' + 65 'First call:\n' + 66 $String.slice(called, redundantPrefix.length) + '\n' + 67 'Second call:\n' + 68 $String.slice(new Error().stack, redundantPrefix.length)); 69 } 70 called = new Error().stack; 71 72 pendingCallbacks--; 73 if (pendingCallbacks == 0) { 74 chromeTest.succeed(); 75 } 76 }; 77 }); 78 79 apiFunctions.setHandleRequest('runNextTest', function() { 80 // There may have been callbacks which were interrupted by failure 81 // exceptions. 82 pendingCallbacks = 0; 83 84 lastTest = currentTest; 85 currentTest = chromeTest.tests.shift(); 86 87 if (!currentTest) { 88 allTestsDone(); 89 return; 90 } 91 92 try { 93 chromeTest.log("( RUN ) " + testName(currentTest)); 94 currentTest.call(); 95 } catch (e) { 96 if (e !== failureException) 97 chromeTest.fail('uncaught exception: ' + 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 $Function.apply(func, null, 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 if (func) { 276 safeFunctionApply(func, arguments); 277 } 278 279 callbackCompleted(); 280 }; 281 }); 282 283 apiFunctions.setHandleRequest('listenOnce', function(event, func) { 284 var callbackCompleted = chromeTest.callbackAdded(); 285 var listener = function() { 286 event.removeListener(listener); 287 safeFunctionApply(func, arguments); 288 callbackCompleted(); 289 }; 290 event.addListener(listener); 291 }); 292 293 apiFunctions.setHandleRequest('listenForever', function(event, func) { 294 var callbackCompleted = chromeTest.callbackAdded(); 295 296 var listener = function() { 297 safeFunctionApply(func, arguments); 298 }; 299 300 var done = function() { 301 event.removeListener(listener); 302 callbackCompleted(); 303 }; 304 305 event.addListener(listener); 306 return done; 307 }); 308 309 apiFunctions.setHandleRequest('callbackPass', function(func) { 310 return chromeTest.callback(func); 311 }); 312 313 apiFunctions.setHandleRequest('callbackFail', function(expectedError, func) { 314 return chromeTest.callback(func, expectedError); 315 }); 316 317 apiFunctions.setHandleRequest('runTests', function(tests) { 318 chromeTest.tests = tests; 319 testCount = chromeTest.tests.length; 320 chromeTest.runNextTest(); 321 }); 322 323 apiFunctions.setHandleRequest('getApiDefinitions', function() { 324 return GetExtensionAPIDefinitionsForTest(); 325 }); 326 327 apiFunctions.setHandleRequest('getApiFeatures', function() { 328 return GetAPIFeatures(); 329 }); 330 331 apiFunctions.setHandleRequest('isProcessingUserGesture', function() { 332 return userGestures.IsProcessingUserGesture(); 333 }); 334 335 apiFunctions.setHandleRequest('runWithUserGesture', function(callback) { 336 chromeTest.assertEq(typeof(callback), 'function'); 337 return userGestures.RunWithUserGesture(callback); 338 }); 339 340 apiFunctions.setHandleRequest('runWithoutUserGesture', function(callback) { 341 chromeTest.assertEq(typeof(callback), 'function'); 342 return userGestures.RunWithoutUserGesture(callback); 343 }); 344 }); 345 346 exports.binding = binding.generate(); 347