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