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 var userGestures = requireNative('user_gestures');
     16 
     17 binding.registerCustomHook(function(api) {
     18   var chromeTest = api.compiledApi;
     19   var apiFunctions = api.apiFunctions;
     20 
     21   chromeTest.tests = chromeTest.tests || [];
     22 
     23   var currentTest = null;
     24   var lastTest = null;
     25   var testsFailed = 0;
     26   var testCount = 1;
     27   var failureException = 'chrome.test.failure';
     28 
     29   // Helper function to get around the fact that function names in javascript
     30   // are read-only, and you can't assign one to anonymous functions.
     31   function testName(test) {
     32     return test ? (test.name || test.generatedName) : "(no test)";
     33   }
     34 
     35   function testDone() {
     36     // Use setTimeout here to allow previous test contexts to be
     37     // eligible for garbage collection.
     38     setTimeout(chromeTest.runNextTest, 0);
     39   }
     40 
     41   function allTestsDone() {
     42     if (testsFailed == 0) {
     43       chromeTest.notifyPass();
     44     } else {
     45       chromeTest.notifyFail('Failed ' + testsFailed + ' of ' +
     46                              testCount + ' tests');
     47     }
     48 
     49     // Try to get the script to stop running immediately.
     50     // This isn't an error, just an attempt at saying "done".
     51     throw "completed";
     52   }
     53 
     54   var pendingCallbacks = 0;
     55 
     56   apiFunctions.setHandleRequest('callbackAdded', function() {
     57     pendingCallbacks++;
     58 
     59     var called = null;
     60     return function() {
     61       if (called != null) {
     62         var redundantPrefix = 'Error\n';
     63         chrome.test.fail(
     64           'Callback has already been run. ' +
     65           'First call:\n' +
     66           $String.slice(called, redundantPrefix.length) + '\n' +
     67           'Second call:\n' +
     68           $String.slice(new Error().stack, redundantPrefix.length));
     69       }
     70       called = new Error().stack;
     71 
     72       pendingCallbacks--;
     73       if (pendingCallbacks == 0) {
     74         chromeTest.succeed();
     75       }
     76     };
     77   });
     78 
     79   apiFunctions.setHandleRequest('runNextTest', function() {
     80     // There may have been callbacks which were interrupted by failure
     81     // exceptions.
     82     pendingCallbacks = 0;
     83 
     84     lastTest = currentTest;
     85     currentTest = chromeTest.tests.shift();
     86 
     87     if (!currentTest) {
     88       allTestsDone();
     89       return;
     90     }
     91 
     92     try {
     93       chromeTest.log("( RUN      ) " + testName(currentTest));
     94       currentTest.call();
     95     } catch (e) {
     96       if (e !== failureException)
     97         chromeTest.fail('uncaught exception: ' + 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         $Function.apply(func, null, 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       if (func) {
    276         safeFunctionApply(func, arguments);
    277       }
    278 
    279       callbackCompleted();
    280     };
    281   });
    282 
    283   apiFunctions.setHandleRequest('listenOnce', function(event, func) {
    284     var callbackCompleted = chromeTest.callbackAdded();
    285     var listener = function() {
    286       event.removeListener(listener);
    287       safeFunctionApply(func, arguments);
    288       callbackCompleted();
    289     };
    290     event.addListener(listener);
    291   });
    292 
    293   apiFunctions.setHandleRequest('listenForever', function(event, func) {
    294     var callbackCompleted = chromeTest.callbackAdded();
    295 
    296     var listener = function() {
    297       safeFunctionApply(func, arguments);
    298     };
    299 
    300     var done = function() {
    301       event.removeListener(listener);
    302       callbackCompleted();
    303     };
    304 
    305     event.addListener(listener);
    306     return done;
    307   });
    308 
    309   apiFunctions.setHandleRequest('callbackPass', function(func) {
    310     return chromeTest.callback(func);
    311   });
    312 
    313   apiFunctions.setHandleRequest('callbackFail', function(expectedError, func) {
    314     return chromeTest.callback(func, expectedError);
    315   });
    316 
    317   apiFunctions.setHandleRequest('runTests', function(tests) {
    318     chromeTest.tests = tests;
    319     testCount = chromeTest.tests.length;
    320     chromeTest.runNextTest();
    321   });
    322 
    323   apiFunctions.setHandleRequest('getApiDefinitions', function() {
    324     return GetExtensionAPIDefinitionsForTest();
    325   });
    326 
    327   apiFunctions.setHandleRequest('getApiFeatures', function() {
    328     return GetAPIFeatures();
    329   });
    330 
    331   apiFunctions.setHandleRequest('isProcessingUserGesture', function() {
    332     return userGestures.IsProcessingUserGesture();
    333   });
    334 
    335   apiFunctions.setHandleRequest('runWithUserGesture', function(callback) {
    336     chromeTest.assertEq(typeof(callback), 'function');
    337     return userGestures.RunWithUserGesture(callback);
    338   });
    339 
    340   apiFunctions.setHandleRequest('runWithoutUserGesture', function(callback) {
    341     chromeTest.assertEq(typeof(callback), 'function');
    342     return userGestures.RunWithoutUserGesture(callback);
    343   });
    344 });
    345 
    346 exports.binding = binding.generate();
    347