• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
8var binding = require('binding').Binding.create('test');
9
10var chrome = requireNative('chrome').GetChrome();
11var GetExtensionAPIDefinitionsForTest =
12    requireNative('apiDefinitions').GetExtensionAPIDefinitionsForTest;
13var GetAvailability = requireNative('v8_context').GetAvailability;
14var GetAPIFeatures = requireNative('test_features').GetAPIFeatures;
15var uncaughtExceptionHandler = require('uncaught_exception_handler');
16var userGestures = requireNative('user_gestures');
17
18binding.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
353exports.binding = binding.generate();
354