Home | History | Annotate | Download | only in resources
      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