OLD | NEW |
| (Empty) |
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 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(); | |
OLD | NEW |