Home | History | Annotate | Download | only in google_now
      1 // Copyright 2013 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 /**
      6  * Test fixture for utility.js.
      7  * @constructor
      8  * @extends {testing.Test}
      9  */
     10 function GoogleNowUtilityUnitTest () {
     11   testing.Test.call(this);
     12 }
     13 
     14 GoogleNowUtilityUnitTest.prototype = {
     15   __proto__: testing.Test.prototype,
     16 
     17   /** @override */
     18   extraLibraries: [
     19     'common_test_util.js',
     20     'utility_test_util.js',
     21     'utility.js'
     22   ]
     23 };
     24 
     25 TEST_F('GoogleNowUtilityUnitTest', 'SendErrorReport1', function() {
     26   // Test sending report for an error with a message that can be sent to server.
     27 
     28   // Setup and expectations.
     29   var testStack = 'Error: TEST ERROR MESSAGE\n    ' +
     30       'at buildErrorWithMessageForServer ' +
     31       '(chrome-extension://ext_id/utility.js:29:15)\n' +
     32       '    at <anonymous>:2:16\n    ' +
     33       'at Object.InjectedScript._evaluateOn (<anonymous>:580:39)\n    ' +
     34       'at Object.InjectedScript._evaluateAndWrap (<anonymous>:539:52)\n    ' +
     35       'at Object.InjectedScript.evaluate (<anonymous>:458:21)';
     36 
     37   var testError = {
     38     canSendMessageToServer: true,
     39     stack: testStack,
     40     name: 'TEST ERROR NAME',
     41     message: 'TEST ERROR MESSAGE'
     42   };
     43 
     44   var testIdentityToken = 'test identity token';
     45 
     46   this.makeAndRegisterMockGlobals(['buildServerRequest']);
     47   this.makeMockLocalFunctions(['sendRequest', 'setRequestHeader']);
     48   this.makeAndRegisterMockApis(['chrome.identity.getAuthToken']);
     49 
     50   var mockRequest = {
     51     send: this.mockLocalFunctions.functions().sendRequest,
     52     setRequestHeader: this.mockLocalFunctions.functions().setRequestHeader
     53   };
     54 
     55   var expectedRequestObject = {
     56     message: 'TEST ERROR NAME: TEST ERROR MESSAGE',
     57     file: '//ext_id/utility.js',
     58     line: '29',
     59     trace: 'Error: TEST ERROR MESSAGE\n    ' +
     60            'at buildErrorWithMessageForServer (chrome-extension://ext_id/util' +
     61            'ity.js:29:15)\n    ' +
     62            'at <anonymous>:2:16\n    ' +
     63            'at Object.InjectedScript._evaluateOn (<anonymous>:580:39)\n    ' +
     64            'at Object.InjectedScript._evaluateAndWrap (<anonymous>:539:52)\n' +
     65            '    at Object.InjectedScript.evaluate (<anonymous>:458:21)'
     66   };
     67 
     68   this.mockGlobals.expects(once()).
     69       buildServerRequest('POST', 'jserrors', 'application/json').
     70       will(returnValue(mockRequest));
     71 
     72   var chromeIdentityGetAuthTokenSavedArgs = new SaveMockArguments();
     73   this.mockApis.expects(once()).
     74       chrome_identity_getAuthToken(
     75           chromeIdentityGetAuthTokenSavedArgs.match(
     76               eqJSON({interactive: false})),
     77           chromeIdentityGetAuthTokenSavedArgs.match(ANYTHING)).
     78       will(invokeCallback(
     79           chromeIdentityGetAuthTokenSavedArgs,
     80           1,
     81           testIdentityToken));
     82 
     83   this.mockLocalFunctions.expects(once()).setRequestHeader(
     84       'Authorization', 'Bearer test identity token');
     85   this.mockLocalFunctions.expects(once()).sendRequest(
     86       JSON.stringify(expectedRequestObject));
     87 
     88   // Invoking the tested function.
     89   sendErrorReport(testError);
     90 });
     91 
     92 TEST_F('GoogleNowUtilityUnitTest', 'SendErrorReport2', function() {
     93   // Test sending report for an error with a message that should not be sent to
     94   // server, with an error generated in an anonymous function.
     95 
     96   // Setup and expectations.
     97   var testStack = 'TypeError: Property \'processPendingDismissals\' of ' +
     98       'object [object Object] is not a function\n    ' +
     99       'at chrome-extension://ext_id/background.js:444:11\n    ' +
    100       'at chrome-extension://ext_id/utility.js:509:7';
    101 
    102   var testError = {
    103     stack: testStack,
    104     name: 'TypeError'
    105   };
    106 
    107   var testIdentityToken = 'test identity token';
    108 
    109   this.makeAndRegisterMockGlobals(['buildServerRequest']);
    110   this.makeMockLocalFunctions(['sendRequest', 'setRequestHeader']);
    111   this.makeAndRegisterMockApis(['chrome.identity.getAuthToken']);
    112 
    113   var mockRequest = {
    114     send: this.mockLocalFunctions.functions().sendRequest,
    115     setRequestHeader: this.mockLocalFunctions.functions().setRequestHeader
    116   };
    117 
    118   var expectedRequestObject = {
    119     message: 'TypeError',
    120     file: '//ext_id/background.js',
    121     line: '444',
    122     trace: '(message removed)\n    ' +
    123            'at chrome-extension://ext_id/background.js:444:11\n    ' +
    124            'at chrome-extension://ext_id/utility.js:509:7'
    125   };
    126 
    127   this.mockGlobals.expects(once()).
    128       buildServerRequest('POST', 'jserrors', 'application/json').
    129       will(returnValue(mockRequest));
    130 
    131   var chromeIdentityGetAuthTokenSavedArgs = new SaveMockArguments();
    132   this.mockApis.expects(once()).
    133       chrome_identity_getAuthToken(
    134           chromeIdentityGetAuthTokenSavedArgs.match(
    135               eqJSON({interactive: false})),
    136           chromeIdentityGetAuthTokenSavedArgs.match(ANYTHING)).
    137       will(invokeCallback(
    138           chromeIdentityGetAuthTokenSavedArgs,
    139           1,
    140           testIdentityToken));
    141 
    142   this.mockLocalFunctions.expects(once()).setRequestHeader(
    143       'Authorization', 'Bearer test identity token');
    144   this.mockLocalFunctions.expects(once()).sendRequest(
    145       JSON.stringify(expectedRequestObject));
    146 
    147   // Invoking the tested function.
    148   sendErrorReport(testError);
    149 });
    150 
    151 TEST_F('GoogleNowUtilityUnitTest', 'WrapperCheckInWrappedCallback', function() {
    152   // Test generating an error when calling wrapper.checkInWrappedCallback from a
    153   // non-instrumented code.
    154 
    155   // Setup and expectations.
    156   var testError = {
    157     testField: 'TEST VALUE'
    158   };
    159 
    160   this.makeAndRegisterMockGlobals([
    161     'buildErrorWithMessageForServer',
    162     'reportError'
    163   ]);
    164 
    165   this.mockGlobals.expects(once()).
    166       buildErrorWithMessageForServer('Not in instrumented callback').
    167       will(returnValue(testError));
    168   this.mockGlobals.expects(once()).
    169       reportError(eqJSON(testError));
    170 
    171   // Invoking the tested function.
    172   wrapper.checkInWrappedCallback();
    173 });
    174 
    175 TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackEvent', function() {
    176   // Tests wrapping event handler and that the handler code counts as an
    177   // instrumented callback.
    178 
    179   // Setup.
    180   var testError = {
    181     testField: 'TEST VALUE'
    182   };
    183 
    184   this.makeAndRegisterMockGlobals([
    185     'buildErrorWithMessageForServer',
    186     'reportError'
    187   ]);
    188   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
    189 
    190   this.makeMockLocalFunctions(['callback']);
    191 
    192   // Step 1. Wrap a callback.
    193   var wrappedCallback =
    194     wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true);
    195   Mock4JS.verifyAllMocks();
    196 
    197   // Step 2. Invoke wrapped callback.
    198   // Expectations.
    199   this.mockLocalFunctions.expects(once()).
    200       callback('test string', 239).
    201       will(callFunction(function() {
    202         wrapper.checkInWrappedCallback(); // it should succeed
    203       }));
    204 
    205   // Invoking tested function.
    206   wrappedCallback('test string', 239);
    207   Mock4JS.verifyAllMocks();
    208 
    209   // Step 3. Check that after the callback we are again in non-instrumented
    210   // code. 
    211   // Expectations.
    212   this.mockGlobals.expects(once()).
    213       buildErrorWithMessageForServer('Not in instrumented callback').
    214       will(returnValue(testError));
    215   this.mockGlobals.expects(once()).
    216       reportError(eqJSON(testError));
    217 
    218   // Invocation.
    219   wrapper.checkInWrappedCallback();
    220 
    221   // Step 4. Check that there won't be errors whe the page unloads.
    222   assertEquals(1, onSuspendHandlerContainer.length);
    223   onSuspendHandlerContainer[0]();
    224 });
    225 
    226 TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackPlugin', function() {
    227   // Tests calling plugin's prologue and epilogue.
    228 
    229   // Setup.
    230   this.makeMockLocalFunctions([
    231     'callback',
    232     'pluginFactory',
    233     'prologue',
    234     'epilogue'
    235   ]);
    236 
    237   // Step 1. Register plugin factory.
    238   wrapper.registerWrapperPluginFactory(
    239       this.mockLocalFunctions.functions().pluginFactory);
    240   Mock4JS.verifyAllMocks();
    241 
    242   // Step 2. Wrap a callback.
    243   // Expectations.
    244   this.mockLocalFunctions.expects(once()).
    245       pluginFactory().
    246       will(returnValue({
    247         prologue: this.mockLocalFunctions.functions().prologue,
    248         epilogue: this.mockLocalFunctions.functions().epilogue
    249       }));
    250 
    251   // Invoking tested function.
    252   var wrappedCallback =
    253     wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true);
    254   Mock4JS.verifyAllMocks();
    255 
    256   // Step 3. Call the wrapped callback.
    257   // Expectations.
    258   this.mockLocalFunctions.expects(once()).prologue();
    259   this.mockLocalFunctions.expects(once()).callback();
    260   this.mockLocalFunctions.expects(once()).epilogue();
    261 
    262   // Invoking wrapped callback.
    263   wrappedCallback();
    264 });
    265 
    266 TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackCatchError', function() {
    267   // Tests catching and sending errors by a wrapped callback.
    268 
    269   // Setup.
    270   this.makeAndRegisterMockGlobals([
    271     'reportError'
    272   ]);
    273   this.makeMockLocalFunctions(['callback']);
    274 
    275   // Step 1. Wrap a callback.
    276   var wrappedCallback =
    277     wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true);
    278   Mock4JS.verifyAllMocks();
    279 
    280   // Step 2. Invoke wrapped callback.
    281   // Expectations.
    282   this.mockLocalFunctions.expects(once()).
    283       callback().
    284       will(callFunction(function() {
    285         undefined.x = 5;
    286       }));
    287   this.mockGlobals.expects(once()).
    288       reportError(
    289           eqToString('TypeError: Cannot set property \'x\' of undefined'));
    290 
    291   // Invoking tested function.
    292   wrappedCallback();
    293 });
    294 
    295 TEST_F('GoogleNowUtilityUnitTest',
    296        'WrapperInstrumentChromeApiFunction',
    297        function() {
    298   // Tests wrapper.instrumentChromeApiFunction().
    299 
    300   // Setup.
    301   this.makeMockLocalFunctions([
    302     'apiFunction1',
    303     'apiFunction2',
    304     'callback1',
    305     'callback2',
    306     'pluginFactory',
    307     'prologue',
    308     'epilogue'
    309   ]);
    310   chrome.testApi = {
    311       addListener: this.mockLocalFunctions.functions().apiFunction1
    312   };
    313 
    314   // Step 1. Instrument the listener.
    315   wrapper.instrumentChromeApiFunction('testApi.addListener', 1);
    316   Mock4JS.verifyAllMocks();
    317 
    318   // Step 2. Invoke the instrumented API call.
    319   // Expectations.
    320   var function1SavedArgs = new SaveMockArguments();
    321   this.mockLocalFunctions.expects(once()).
    322       apiFunction1(
    323           function1SavedArgs.match(eq(239)),
    324           function1SavedArgs.match(ANYTHING));
    325 
    326   // Invocation.
    327   instrumented.testApi.addListener(
    328       239, this.mockLocalFunctions.functions().callback1);
    329   Mock4JS.verifyAllMocks();
    330 
    331   // Step 3. Invoke the callback that was passed by the instrumented function
    332   // to the original one.
    333   // Expectations.
    334   this.mockLocalFunctions.expects(once()).callback1(237);
    335 
    336   // Invocation.
    337   function1SavedArgs.arguments[1](237);
    338   Mock4JS.verifyAllMocks();
    339 
    340   // Step 4. Register plugin factory.
    341   wrapper.registerWrapperPluginFactory(
    342       this.mockLocalFunctions.functions().pluginFactory);
    343   Mock4JS.verifyAllMocks();
    344 
    345   // Step 5. Bind the API to another function.
    346   chrome.testApi.addListener = this.mockLocalFunctions.functions().apiFunction2;
    347 
    348   // Step 6. Invoke the API with another callback.
    349   // Expectations.
    350   this.mockLocalFunctions.expects(once()).
    351       pluginFactory().
    352       will(returnValue({
    353         prologue: this.mockLocalFunctions.functions().prologue,
    354         epilogue: this.mockLocalFunctions.functions().epilogue
    355       }));
    356   var function2SavedArgs = new SaveMockArguments();
    357   this.mockLocalFunctions.expects(once()).
    358       apiFunction2(
    359           function2SavedArgs.match(eq(239)),
    360           function2SavedArgs.match(ANYTHING));
    361 
    362   // Invocation.
    363   instrumented.testApi.addListener(
    364       239, this.mockLocalFunctions.functions().callback2);
    365   Mock4JS.verifyAllMocks();
    366 
    367   // Step 7. Invoke the callback that was passed by the instrumented function
    368   // to the original one.
    369   // Expectations.
    370   this.mockLocalFunctions.expects(once()).prologue();
    371   this.mockLocalFunctions.expects(once()).callback2(237);
    372   this.mockLocalFunctions.expects(once()).epilogue();
    373 
    374   // Invocation.
    375   function2SavedArgs.arguments[1](237);
    376 });
    377 
    378 TEST_F('GoogleNowUtilityUnitTest', 'WrapperOnSuspendListenerFail', function() {
    379   // Tests that upon unloading event page, we get an error if there are pending
    380   // required callbacks.
    381 
    382   // Setup.
    383   var testError = {
    384     testField: 'TEST VALUE'
    385   };
    386   this.makeAndRegisterMockGlobals([
    387     'buildErrorWithMessageForServer',
    388     'reportError'
    389   ]);
    390   this.makeMockLocalFunctions(['listener', 'callback']);
    391   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
    392 
    393   // Step 1. Wrap event listener.
    394   var wrappedListener =
    395     wrapper.wrapCallback(this.mockLocalFunctions.functions().listener, true);
    396   Mock4JS.verifyAllMocks();
    397 
    398   // Step 2. Invoke event listener, which will wrap a required callback.
    399   // Setup and expectations.
    400   var wrappedCallback;
    401   var testFixture = this;
    402   this.mockLocalFunctions.expects(once()).
    403       listener().
    404       will(callFunction(function() {
    405         wrappedCallback = wrapper.wrapCallback(
    406             testFixture.mockLocalFunctions.functions().callback);
    407       }));
    408 
    409   // Invocation.
    410   wrappedListener();
    411   Mock4JS.verifyAllMocks();
    412 
    413   // Step 3. Fire runtime.onSuspend event.
    414   // Expectations.
    415   this.mockGlobals.expects(once()).
    416       buildErrorWithMessageForServer(stringContains(
    417           'ASSERT: Pending callbacks when unloading event page')).
    418       will(returnValue(testError));
    419   this.mockGlobals.expects(once()).
    420       reportError(eqJSON(testError));
    421 
    422   // Invocation.
    423   assertEquals(1, onSuspendHandlerContainer.length);
    424   onSuspendHandlerContainer[0]();
    425 });
    426 
    427 TEST_F('GoogleNowUtilityUnitTest',
    428        'WrapperOnSuspendListenerSuccess',
    429        function() {
    430   // Tests that upon unloading event page, we don't get an error if there are no
    431   // pending required callbacks.
    432 
    433   // Setup.
    434   this.makeMockLocalFunctions(['listener', 'callback']);
    435   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
    436 
    437   // Step 1. Wrap event listener.
    438   var wrappedListener =
    439     wrapper.wrapCallback(this.mockLocalFunctions.functions().listener, true);
    440   Mock4JS.verifyAllMocks();
    441 
    442   // Step 2. Invoke event listener, which will wrap a required callback.
    443   // Setup and expectations.
    444   var wrappedCallback;
    445   var testFixture = this;
    446   this.mockLocalFunctions.expects(once()).
    447       listener().
    448       will(callFunction(function() {
    449         wrappedCallback = wrapper.wrapCallback(
    450             testFixture.mockLocalFunctions.functions().callback);
    451       }));
    452 
    453   // Invocation.
    454   wrappedListener();
    455   Mock4JS.verifyAllMocks();
    456 
    457   // Step 3. Call wrapped callback.
    458   // Expectations.
    459   this.mockLocalFunctions.expects(once()).callback();
    460 
    461   // Invocation.
    462   wrappedCallback();
    463 
    464   // Step 4. Fire runtime.onSuspend event.
    465   assertEquals(1, onSuspendHandlerContainer.length);
    466   onSuspendHandlerContainer[0]();
    467 });
    468 
    469 var taskNameA = 'TASK A';
    470 var taskNameB = 'TASK B';
    471 var taskNameC = 'TASK C';
    472 
    473 function areTasksConflicting(newTaskName, scheduledTaskName) {
    474   // Task B is conflicting with Task A. This means that if Task B is added when
    475   // Task A is running, Task B will be ignored (but not vice versa). No other
    476   // pair is conflicting.
    477   return newTaskName == taskNameB && scheduledTaskName == taskNameA;
    478 }
    479 
    480 function setUpTaskManagerTest(fixture) {
    481   fixture.makeAndRegisterMockApis([
    482     'wrapper.checkInWrappedCallback',
    483     'wrapper.registerWrapperPluginFactory',
    484     'wrapper.debugGetStateString'
    485   ]);
    486   fixture.makeMockLocalFunctions(['task1', 'task2', 'task3']);
    487   fixture.makeAndRegisterMockGlobals(['reportError']);
    488 
    489   fixture.mockApis.stubs().wrapper_checkInWrappedCallback();
    490   fixture.mockApis.stubs().wrapper_debugGetStateString().
    491       will(returnValue('testWrapperDebugState'));
    492 
    493   var registerWrapperPluginFactorySavedArgs = new SaveMockArguments();
    494   fixture.mockApis.expects(once()).wrapper_registerWrapperPluginFactory(
    495       registerWrapperPluginFactorySavedArgs.match(ANYTHING));
    496   var tasks = buildTaskManager(areTasksConflicting);
    497   Mock4JS.verifyAllMocks();
    498 
    499   return {
    500     tasks: tasks,
    501     pluginFactory: registerWrapperPluginFactorySavedArgs.arguments[0]
    502   };
    503 }
    504 
    505 TEST_F('GoogleNowUtilityUnitTest', 'TaskManager2Sequential', function() {
    506   // Tests that 2 tasks get successfully executed consecutively, even if the
    507   // second one conflicts with the first.
    508   
    509   // Setup.
    510   var test = setUpTaskManagerTest(this);
    511 
    512   // Step 1. Add 1st task that doesn't create pending callbacks.
    513   // Expectations.
    514   this.mockLocalFunctions.expects(once()).task1();
    515   // Invocation.
    516   test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1);
    517   Mock4JS.verifyAllMocks();
    518 
    519   // Step 2. Add 2nd task.
    520   // Expectations.
    521   this.mockLocalFunctions.expects(once()).task2();
    522   // Invocation.
    523   test.tasks.add(taskNameB, this.mockLocalFunctions.functions().task2);
    524 });
    525 
    526 TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerConflicting', function() {
    527   // Tests that adding a task while a conflicting task is being executed causes
    528   // the second one to be ignored.
    529 
    530   // Setup.
    531   var test = setUpTaskManagerTest(this);
    532   var task1PluginInstance;
    533 
    534   // Step 1. Add 1st task that creates a pending callback.
    535   // Expectations.
    536   this.mockLocalFunctions.expects(once()).task1().
    537       will(callFunction(function() {
    538         task1PluginInstance = test.pluginFactory();
    539       }));
    540   // Invocation.
    541   test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1);
    542   Mock4JS.verifyAllMocks();
    543 
    544   // Step 2. Add 2nd task. Since it conflicts with currently running task1
    545   // (see areTasksConflicting), it should be ignored.
    546   test.tasks.add(taskNameB, this.mockLocalFunctions.functions().task2);
    547   Mock4JS.verifyAllMocks();
    548 
    549   // Step 3. Enter the callback of task1.
    550   task1PluginInstance.prologue();
    551   Mock4JS.verifyAllMocks();
    552 
    553   // Step 4. Leave the callback of task1.
    554   task1PluginInstance.epilogue();
    555 });
    556 
    557 TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerNestedTaskEnqueue', function() {
    558   // Tests that adding a task while a non-conflicting task is being executed
    559   // causes the second one to be executed after the first one completes.
    560 
    561   // Setup.
    562   var test = setUpTaskManagerTest(this);
    563   var task1PluginInstance;
    564 
    565   // Step 1. Add 1st task that creates a pending callback.
    566   // Expectations.
    567   this.mockLocalFunctions.expects(once()).task1().
    568       will(callFunction(function() {
    569         task1PluginInstance = test.pluginFactory();
    570       }));
    571   // Invocation.
    572   test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1);
    573   Mock4JS.verifyAllMocks();
    574 
    575   // Step 2. Add 2nd task. Since it doesn't conflict with currently running
    576   // task1 (see areTasksConflicting), it should not be ignored.
    577   test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task2);
    578   Mock4JS.verifyAllMocks();
    579 
    580   // Step 3. Enter the callback of task1.
    581   task1PluginInstance.prologue();
    582   Mock4JS.verifyAllMocks();
    583 
    584   // Step 4. Leave the callback of task1.
    585   // Expectations.
    586   this.mockLocalFunctions.expects(once()).task2();
    587   // Invocation.
    588   task1PluginInstance.epilogue();
    589 });
    590 
    591 TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerBranching', function() {
    592   // Tests that task manager correctly detects completion of tasks that create
    593   // branching chains of callbacks (in this test, task1 creates pending
    594   // callbacks 1 and 2, and callback 1 creates pending callback 3).
    595 
    596   // Setup.
    597   var test = setUpTaskManagerTest(this);
    598   var task1PluginInstance1, task1PluginInstance2, task1PluginInstance3;
    599 
    600   // Step 1. Add 1st task that creates a 2 pending callbacks.
    601   // Expectations.
    602   this.mockLocalFunctions.expects(once()).task1().
    603       will(callFunction(function() {
    604         task1PluginInstance1 = test.pluginFactory();
    605         task1PluginInstance2 = test.pluginFactory();
    606       }));
    607   // Invocation.
    608   test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1);
    609   Mock4JS.verifyAllMocks();
    610 
    611   // Step 2. Add 2nd task, which is not conflicting (see areTasksConflicting)
    612   // with task1.
    613   test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task2);
    614   Mock4JS.verifyAllMocks();
    615 
    616   // Step 3. Enter callback 1, create pending callback 3, exit callback 1.
    617   // Enter/exit callback 2. Enter callback 3.
    618   task1PluginInstance1.prologue();
    619   task1PluginInstance3 = test.pluginFactory();
    620   task1PluginInstance1.epilogue();
    621   task1PluginInstance2.prologue();
    622   task1PluginInstance2.epilogue();
    623   task1PluginInstance3.prologue();
    624   Mock4JS.verifyAllMocks();
    625 
    626   // Step 4. Leave 3rd callback of task1. Now task1 is complete, and task2
    627   // should start.
    628   // Expectations.
    629   this.mockLocalFunctions.expects(once()).task2();
    630   // Invocation.
    631   task1PluginInstance3.epilogue();
    632 });
    633 
    634 TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerSuspendError', function() {
    635   // Tests that task manager's onSuspend method reports an error if there are
    636   // pending tasks.
    637 
    638   // Setup.
    639   var test = setUpTaskManagerTest(this);
    640   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
    641 
    642   // Step 1. Add a task that creates a pending callback.
    643   // Expectations.
    644   this.mockLocalFunctions.expects(once()).task1().
    645       will(callFunction(function() {
    646         test.pluginFactory();
    647       }));
    648   // Invocation.
    649   test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1);
    650   Mock4JS.verifyAllMocks();
    651 
    652   // Step 2. Invoke onSuspend event of the task manager.
    653   // Setup and expectations. The 2 callbacks in onSuspendHandlerContainer are
    654   // from the wrapper and the task manager.
    655   assertEquals(2, onSuspendHandlerContainer.length);
    656   this.mockGlobals.expects(once()).reportError(eqToString(
    657       'Error: ASSERT: Incomplete task when unloading event page,' +
    658       ' queue = [{"name":"TASK A"}], testWrapperDebugState'));
    659   // Invocation.
    660   onSuspendHandlerContainer[1]();
    661 });
    662 
    663 TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerSuspendSuccess', function() {
    664   // Tests that task manager's onSuspend method does not report an error if all
    665   // tasks completed.
    666 
    667   // Setup.
    668   var test = setUpTaskManagerTest(this);
    669   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
    670   var task1PluginInstance;
    671 
    672   // Step 1. Add a task that creates a pending callback.
    673   // Expectations.
    674   this.mockLocalFunctions.expects(once()).task1().
    675       will(callFunction(function() {
    676         task1PluginInstance = test.pluginFactory();
    677       }));
    678   // Invocation.
    679   test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1);
    680   Mock4JS.verifyAllMocks();
    681 
    682   // Step 2. Invoke task's callback and the onSuspend event of the task manager.
    683   // The 2 callbacks in onSuspendHandlerContainer are from the wrapper and the
    684   // task manager.
    685   task1PluginInstance.prologue();
    686   task1PluginInstance.epilogue();
    687   onSuspendHandlerContainer[1]();
    688 });
    689 
    690 TEST_F('GoogleNowUtilityUnitTest', 'TaskManager3Tasks', function() {
    691   // Tests that 3 tasks can be executed too. In particular, that if the second 
    692   // task is a single-step task which execution was originally blocked by task1,
    693   // unblocking it causes immediate synchronous execution of both tasks 2 and 3.
    694 
    695   // Setup.
    696   var test = setUpTaskManagerTest(this);
    697   var task1PluginInstance;
    698 
    699   // Step 1. Add 1st task that creates a pending callback.
    700   // Expectations.
    701   this.mockLocalFunctions.expects(once()).task1().
    702       will(callFunction(function() {
    703         task1PluginInstance = test.pluginFactory();
    704       }));
    705   // Invocation.
    706   test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1);
    707   Mock4JS.verifyAllMocks();
    708 
    709   // Step 2. Add 2nd and 3rd tasks, both non-conflicting (see
    710   // areTasksConflicting) with task1.
    711   test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task2);
    712   test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task3);
    713   Mock4JS.verifyAllMocks();
    714 
    715   // Step 3. Enter the callback of task1.
    716   task1PluginInstance.prologue();
    717   Mock4JS.verifyAllMocks();
    718 
    719   // Step 4. Leave the callback of task1.
    720   // Expectations.
    721   this.mockLocalFunctions.expects(once()).task2();
    722   this.mockLocalFunctions.expects(once()).task3();
    723   // Invocation.
    724   task1PluginInstance.epilogue();
    725 });
    726 
    727 TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerNestedNonTask', function() {
    728   // Tests callbacks requested while a task is running, but not from a callback
    729   // belonging to a task, are not counted as a part of the task.
    730 
    731   // Setup.
    732   var test = setUpTaskManagerTest(this);
    733   var task1PluginInstance;
    734 
    735   // Step 1. Add 1st task that creates a pending callback.
    736   // Expectations.
    737   this.mockLocalFunctions.expects(once()).task1().
    738       will(callFunction(function() {
    739         task1PluginInstance = test.pluginFactory();
    740       }));
    741   // Invocation.
    742   test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1);
    743   Mock4JS.verifyAllMocks();
    744 
    745   // Step 2. Create a pending callback from code that is not a part of the task.
    746   test.pluginFactory();
    747   Mock4JS.verifyAllMocks();
    748 
    749   // Step 3. Enter the callback of task1. After this, task1 should be
    750   // finished despite the pending non-task callback.
    751   task1PluginInstance.prologue();
    752   task1PluginInstance.epilogue();
    753   Mock4JS.verifyAllMocks();
    754 
    755   // Step 4. Check that task1 is finished by submitting task2, which should
    756   // be executed immediately.
    757   this.mockLocalFunctions.expects(once()).task2();
    758   test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task2);
    759 });
    760 
    761 var testAttemptAlarmName = 'attempt-scheduler-testAttempts';
    762 var testAttemptStorageKey = 'current-delay-testAttempts';
    763 var testInitialDelaySeconds = 239;
    764 var testMaximumDelaySeconds = 2239;
    765 // Value to be returned by mocked Math.random(). We want the value returned by
    766 // Math.random() to be predictable to be able to check results against expected
    767 // values. A fixed seed would be okay, but fixed seeding isn't possible in JS at
    768 // the moment.
    769 var testRandomValue = 0.31415926;
    770 
    771 function createTestAttempStorageEntry(delaySeconds) {
    772   // Creates a test storage object that attempt manager uses to store current
    773   // delay.
    774   var storageObject = {};
    775   storageObject[testAttemptStorageKey] = delaySeconds;
    776   return storageObject;
    777 }
    778 
    779 function setupAttemptManagerTest(fixture) {
    780   Math.random = function() { return testRandomValue; }
    781 
    782   fixture.makeMockLocalFunctions([
    783     'attempt',
    784     'planForNextCallback',
    785     'isRunningCallback'
    786   ]);
    787   fixture.makeAndRegisterMockApis([
    788     'chrome.alarms.clear',
    789     'chrome.alarms.create',
    790     'chrome.storage.local.remove',
    791     'chrome.storage.local.set',
    792     'instrumented.alarms.get',
    793     'instrumented.storage.local.get'
    794   ]);
    795 
    796   var testAttempts = buildAttemptManager(
    797       'testAttempts',
    798       fixture.mockLocalFunctions.functions().attempt,
    799       testInitialDelaySeconds,
    800       testMaximumDelaySeconds);
    801   Mock4JS.verifyAllMocks();
    802 
    803   return {
    804     attempts: testAttempts
    805   };
    806 }
    807 
    808 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerStartStop', function() {
    809   // Tests starting and stopping an attempt manager.
    810 
    811   // Setup.
    812   var test = setupAttemptManagerTest(this);
    813 
    814   // Step 1. Check that attempt manager is not running.
    815   // Expectations.
    816   var alarmsGetSavedArgs = new SaveMockArguments();
    817   this.mockApis.expects(once()).
    818       instrumented_alarms_get(
    819           alarmsGetSavedArgs.match(eq(testAttemptAlarmName)),
    820           alarmsGetSavedArgs.match(ANYTHING)).
    821       will(invokeCallback(alarmsGetSavedArgs, 1, undefined));
    822   this.mockLocalFunctions.expects(once()).isRunningCallback(false);
    823   // Invocation.
    824   test.attempts.isRunning(
    825       this.mockLocalFunctions.functions().isRunningCallback);
    826   Mock4JS.verifyAllMocks();
    827 
    828   // Step 2. Start attempt manager with no parameters.
    829   // Expectations.
    830   var expectedRetryDelaySeconds =
    831       testInitialDelaySeconds * (1 + testRandomValue * 0.2);
    832   this.mockApis.expects(once()).chrome_alarms_create(
    833     testAttemptAlarmName,
    834     eqJSON({
    835       delayInMinutes: expectedRetryDelaySeconds / 60,
    836       periodInMinutes: testMaximumDelaySeconds / 60
    837     }));
    838   this.mockApis.expects(once()).chrome_storage_local_set(
    839       eqJSON(createTestAttempStorageEntry(expectedRetryDelaySeconds)));
    840   // Invocation.
    841   test.attempts.start();
    842   Mock4JS.verifyAllMocks();
    843 
    844   // Step 3. Check that attempt manager is running.
    845   // Expectations.
    846   alarmsGetSavedArgs = new SaveMockArguments();
    847   this.mockApis.expects(once()).
    848       instrumented_alarms_get(
    849           alarmsGetSavedArgs.match(eq(testAttemptAlarmName)),
    850           alarmsGetSavedArgs.match(ANYTHING)).
    851       will(invokeCallback(alarmsGetSavedArgs, 1, {testField: 'TEST VALUE'}));
    852   this.mockLocalFunctions.expects(once()).isRunningCallback(true);
    853   // Invocation.
    854   test.attempts.isRunning(
    855       this.mockLocalFunctions.functions().isRunningCallback);
    856   Mock4JS.verifyAllMocks();
    857 
    858   // Step 4. Stop task manager.
    859   // Expectations.
    860   this.mockApis.expects(once()).chrome_alarms_clear(testAttemptAlarmName);
    861   this.mockApis.expects(once()).chrome_storage_local_remove(
    862       testAttemptStorageKey);
    863   // Invocation.
    864   test.attempts.stop();
    865 });
    866 
    867 TEST_F(
    868     'GoogleNowUtilityUnitTest',
    869     'AttemptManagerStartWithDelayParam',
    870     function() {
    871   // Tests starting an attempt manager with a delay parameter.
    872 
    873   // Setup.
    874   var test = setupAttemptManagerTest(this);
    875   var testFirstDelaySeconds = 1039;
    876 
    877   // Starting attempt manager with a parameter specifying first delay.
    878   // Expectations.
    879   this.mockApis.expects(once()).chrome_alarms_create(
    880       testAttemptAlarmName,
    881       eqJSON({
    882         delayInMinutes: testFirstDelaySeconds / 60,
    883         periodInMinutes: testMaximumDelaySeconds / 60
    884       }));
    885   this.mockApis.expects(once()).chrome_storage_local_remove(
    886     testAttemptStorageKey);
    887   // Invocation.
    888   test.attempts.start(testFirstDelaySeconds);
    889 });
    890 
    891 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerExponGrowth', function() {
    892   // Tests that retry time grows exponentially. We don't need to check the case
    893   // of growing more than once, since the object doesn't have state, and the
    894   // test checks all its inputs and outputs of the tested code.
    895 
    896   // Setup.
    897   var test = setupAttemptManagerTest(this);
    898   var testStoredRetryDelay = 433;
    899 
    900   // Call planForNext, which prepares next attempt. Current retry time
    901   // is less than 1/2 of the maximum delay.
    902   // Expectations.
    903   var expectedRetryDelaySeconds =
    904       testStoredRetryDelay * 2 * (1 + testRandomValue * 0.2);
    905   var storageGetSavedArgs = new SaveMockArguments();
    906   this.mockApis.expects(once()).instrumented_storage_local_get(
    907           storageGetSavedArgs.match(eq(testAttemptStorageKey)),
    908           storageGetSavedArgs.match(ANYTHING)).
    909       will(invokeCallback(
    910           storageGetSavedArgs,
    911           1,
    912           createTestAttempStorageEntry(testStoredRetryDelay)));
    913   this.mockApis.expects(once()).chrome_alarms_create(
    914       testAttemptAlarmName,
    915       eqJSON({
    916         delayInMinutes: expectedRetryDelaySeconds / 60,
    917         periodInMinutes: testMaximumDelaySeconds / 60}));
    918   this.mockApis.expects(once()).chrome_storage_local_set(
    919       eqJSON(createTestAttempStorageEntry(expectedRetryDelaySeconds)));
    920   this.mockLocalFunctions.expects(once()).planForNextCallback();
    921   // Invocation.
    922   test.attempts.planForNext(
    923       this.mockLocalFunctions.functions().planForNextCallback);
    924 });
    925 
    926 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerGrowthLimit', function() {
    927   // Tests that retry time stops growing at the maximum value.
    928 
    929   // Setup.
    930   var test = setupAttemptManagerTest(this);
    931   var testStoredRetryDelay = 1500;
    932 
    933   // Call planForNext, which prepares next attempt. Current retry time
    934   // is greater than 1/2 of the maximum delay.
    935   // Expectations.
    936   var expectedRetryDelaySeconds = testMaximumDelaySeconds;
    937   var storageGetSavedArgs = new SaveMockArguments();
    938   this.mockApis.expects(once()).
    939       instrumented_storage_local_get(
    940           storageGetSavedArgs.match(eq(testAttemptStorageKey)),
    941           storageGetSavedArgs.match(ANYTHING)).
    942       will(invokeCallback(
    943           storageGetSavedArgs,
    944           1,
    945           createTestAttempStorageEntry(testStoredRetryDelay)));
    946   this.mockApis.expects(once()).chrome_alarms_create(
    947       testAttemptAlarmName,
    948       eqJSON({
    949         delayInMinutes: expectedRetryDelaySeconds / 60,
    950         periodInMinutes: testMaximumDelaySeconds / 60
    951       }));
    952   this.mockApis.expects(once()).chrome_storage_local_set(
    953       eqJSON(createTestAttempStorageEntry(expectedRetryDelaySeconds)));
    954   this.mockLocalFunctions.expects(once()).planForNextCallback();
    955   // Invocation.
    956   test.attempts.planForNext(
    957       this.mockLocalFunctions.functions().planForNextCallback);
    958 });
    959 
    960 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerAlarm', function() {
    961   // Tests that firing the alarm invokes the attempt.
    962 
    963   // Setup.
    964   var test = setupAttemptManagerTest(this);
    965   var onAlarmHandlerContainer = getMockHandlerContainer('alarms.onAlarm');
    966   assertEquals(1, onAlarmHandlerContainer.length);
    967 
    968   // Fire the alarm and check that this invokes the attempt callback.
    969   // Expectations.
    970   var alarmsGetSavedArgs = new SaveMockArguments();
    971   this.mockApis.expects(once()).
    972       instrumented_alarms_get(
    973           alarmsGetSavedArgs.match(eq(testAttemptAlarmName)),
    974           alarmsGetSavedArgs.match(ANYTHING)).
    975       will(invokeCallback(alarmsGetSavedArgs, 1, {testField: 'TEST VALUE'}));
    976   this.mockLocalFunctions.expects(once()).attempt();
    977   // Invocation.
    978   onAlarmHandlerContainer[0]({name: testAttemptAlarmName});
    979 });
    980