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