Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / resources / google_now / utility_unittest.gtestjs
blob64ba1d84b5a9166b0716144599e35a2dbc7c1f65
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.
5 /**
6  * Test fixture for utility.js.
7  * @constructor
8  * @extends {testing.Test}
9  */
10 function GoogleNowUtilityUnitTest () {
11   testing.Test.call(this);
14 GoogleNowUtilityUnitTest.prototype = {
15   __proto__: testing.Test.prototype,
17   /** @override */
18   extraLibraries: [
19     'common_test_util.js',
20     'utility_test_util.js',
21     'utility.js'
22   ]
25 TEST_F('GoogleNowUtilityUnitTest', 'SendErrorReport1', function() {
26   // Test sending report for an error with a message that can be sent to server.
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)';
37   var testError = {
38     canSendMessageToServer: true,
39     stack: testStack,
40     name: 'TEST ERROR NAME',
41     message: 'TEST ERROR MESSAGE'
42   };
44   var testIdentityToken = 'test identity token';
46   this.makeAndRegisterMockGlobals(['buildServerRequest']);
47   this.makeMockLocalFunctions(['sendRequest', 'setRequestHeader']);
48   this.makeAndRegisterMockApis(['chrome.identity.getAuthToken']);
50   var mockRequest = {
51     send: this.mockLocalFunctions.functions().sendRequest,
52     setRequestHeader: this.mockLocalFunctions.functions().setRequestHeader
53   };
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   };
68   this.mockGlobals.expects(once()).
69       buildServerRequest('POST', 'jserrors', 'application/json').
70       will(returnValue(mockRequest));
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));
83   this.mockLocalFunctions.expects(once()).setRequestHeader(
84       'Authorization', 'Bearer test identity token');
85   this.mockLocalFunctions.expects(once()).sendRequest(
86       JSON.stringify(expectedRequestObject));
88   // Invoking the tested function.
89   sendErrorReport(testError);
90 });
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.
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';
102   var testError = {
103     stack: testStack,
104     name: 'TypeError'
105   };
107   var testIdentityToken = 'test identity token';
109   this.makeAndRegisterMockGlobals(['buildServerRequest']);
110   this.makeMockLocalFunctions(['sendRequest', 'setRequestHeader']);
111   this.makeAndRegisterMockApis(['chrome.identity.getAuthToken']);
113   var mockRequest = {
114     send: this.mockLocalFunctions.functions().sendRequest,
115     setRequestHeader: this.mockLocalFunctions.functions().setRequestHeader
116   };
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   };
127   this.mockGlobals.expects(once()).
128       buildServerRequest('POST', 'jserrors', 'application/json').
129       will(returnValue(mockRequest));
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));
142   this.mockLocalFunctions.expects(once()).setRequestHeader(
143       'Authorization', 'Bearer test identity token');
144   this.mockLocalFunctions.expects(once()).sendRequest(
145       JSON.stringify(expectedRequestObject));
147   // Invoking the tested function.
148   sendErrorReport(testError);
151 TEST_F('GoogleNowUtilityUnitTest', 'WrapperCheckInWrappedCallback', function() {
152   // Test generating an error when calling wrapper.checkInWrappedCallback from a
153   // non-instrumented code.
155   // Setup and expectations.
156   var testError = {
157     testField: 'TEST VALUE'
158   };
160   this.makeAndRegisterMockGlobals([
161     'buildErrorWithMessageForServer',
162     'reportError'
163   ]);
165   this.mockGlobals.expects(once()).
166       buildErrorWithMessageForServer('Not in instrumented callback').
167       will(returnValue(testError));
168   this.mockGlobals.expects(once()).
169       reportError(eqJSON(testError));
171   // Invoking the tested function.
172   wrapper.checkInWrappedCallback();
175 TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackEvent', function() {
176   // Tests wrapping event handler and that the handler code counts as an
177   // instrumented callback.
179   // Setup.
180   var testError = {
181     testField: 'TEST VALUE'
182   };
184   this.makeAndRegisterMockGlobals([
185     'buildErrorWithMessageForServer',
186     'reportError'
187   ]);
188   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
190   this.makeMockLocalFunctions(['callback']);
192   // Step 1. Wrap a callback.
193   var wrappedCallback =
194     wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true);
195   Mock4JS.verifyAllMocks();
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       }));
205   // Invoking tested function.
206   wrappedCallback('test string', 239);
207   Mock4JS.verifyAllMocks();
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));
218   // Invocation.
219   wrapper.checkInWrappedCallback();
221   // Step 4. Check that there won't be errors whe the page unloads.
222   assertEquals(1, onSuspendHandlerContainer.length);
223   onSuspendHandlerContainer[0]();
226 TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackPlugin', function() {
227   // Tests calling plugin's prologue and epilogue.
229   // Setup.
230   this.makeMockLocalFunctions([
231     'callback',
232     'pluginFactory',
233     'prologue',
234     'epilogue'
235   ]);
237   // Step 1. Register plugin factory.
238   wrapper.registerWrapperPluginFactory(
239       this.mockLocalFunctions.functions().pluginFactory);
240   Mock4JS.verifyAllMocks();
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       }));
251   // Invoking tested function.
252   var wrappedCallback =
253     wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true);
254   Mock4JS.verifyAllMocks();
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();
262   // Invoking wrapped callback.
263   wrappedCallback();
266 TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackCatchError', function() {
267   // Tests catching and sending errors by a wrapped callback.
269   // Setup.
270   this.makeAndRegisterMockGlobals([
271     'reportError'
272   ]);
273   this.makeMockLocalFunctions(['callback']);
275   // Step 1. Wrap a callback.
276   var wrappedCallback =
277     wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true);
278   Mock4JS.verifyAllMocks();
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'));
291   // Invoking tested function.
292   wrappedCallback();
295 TEST_F('GoogleNowUtilityUnitTest',
296        'WrapperInstrumentChromeApiFunction',
297        function() {
298   // Tests wrapper.instrumentChromeApiFunction().
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   };
314   // Step 1. Instrument the listener.
315   wrapper.instrumentChromeApiFunction('testApi.addListener', 1);
316   Mock4JS.verifyAllMocks();
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));
326   // Invocation.
327   instrumented.testApi.addListener(
328       239, this.mockLocalFunctions.functions().callback1);
329   Mock4JS.verifyAllMocks();
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);
336   // Invocation.
337   function1SavedArgs.arguments[1](237);
338   Mock4JS.verifyAllMocks();
340   // Step 4. Register plugin factory.
341   wrapper.registerWrapperPluginFactory(
342       this.mockLocalFunctions.functions().pluginFactory);
343   Mock4JS.verifyAllMocks();
345   // Step 5. Bind the API to another function.
346   chrome.testApi.addListener = this.mockLocalFunctions.functions().apiFunction2;
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));
362   // Invocation.
363   instrumented.testApi.addListener(
364       239, this.mockLocalFunctions.functions().callback2);
365   Mock4JS.verifyAllMocks();
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();
374   // Invocation.
375   function2SavedArgs.arguments[1](237);
378 TEST_F('GoogleNowUtilityUnitTest', 'WrapperOnSuspendListenerFail', function() {
379   // Tests that upon unloading event page, we get an error if there are pending
380   // required callbacks.
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');
393   // Step 1. Wrap event listener.
394   var wrappedListener =
395     wrapper.wrapCallback(this.mockLocalFunctions.functions().listener, true);
396   Mock4JS.verifyAllMocks();
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       }));
409   // Invocation.
410   wrappedListener();
411   Mock4JS.verifyAllMocks();
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));
422   // Invocation.
423   assertEquals(1, onSuspendHandlerContainer.length);
424   onSuspendHandlerContainer[0]();
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.
433   // Setup.
434   this.makeMockLocalFunctions(['listener', 'callback']);
435   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
437   // Step 1. Wrap event listener.
438   var wrappedListener =
439     wrapper.wrapCallback(this.mockLocalFunctions.functions().listener, true);
440   Mock4JS.verifyAllMocks();
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       }));
453   // Invocation.
454   wrappedListener();
455   Mock4JS.verifyAllMocks();
457   // Step 3. Call wrapped callback.
458   // Expectations.
459   this.mockLocalFunctions.expects(once()).callback();
461   // Invocation.
462   wrappedCallback();
464   // Step 4. Fire runtime.onSuspend event.
465   assertEquals(1, onSuspendHandlerContainer.length);
466   onSuspendHandlerContainer[0]();
469 var taskNameA = 'TASK A';
470 var taskNameB = 'TASK B';
471 var taskNameC = 'TASK C';
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;
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']);
489   fixture.mockApis.stubs().wrapper_checkInWrappedCallback();
490   fixture.mockApis.stubs().wrapper_debugGetStateString().
491       will(returnValue('testWrapperDebugState'));
493   var registerWrapperPluginFactorySavedArgs = new SaveMockArguments();
494   fixture.mockApis.expects(once()).wrapper_registerWrapperPluginFactory(
495       registerWrapperPluginFactorySavedArgs.match(ANYTHING));
496   var tasks = buildTaskManager(areTasksConflicting);
497   Mock4JS.verifyAllMocks();
499   return {
500     tasks: tasks,
501     pluginFactory: registerWrapperPluginFactorySavedArgs.arguments[0]
502   };
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);
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();
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);
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.
530   // Setup.
531   var test = setUpTaskManagerTest(this);
532   var task1PluginInstance;
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();
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();
549   // Step 3. Enter the callback of task1.
550   task1PluginInstance.prologue();
551   Mock4JS.verifyAllMocks();
553   // Step 4. Leave the callback of task1.
554   task1PluginInstance.epilogue();
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.
561   // Setup.
562   var test = setUpTaskManagerTest(this);
563   var task1PluginInstance;
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();
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();
580   // Step 3. Enter the callback of task1.
581   task1PluginInstance.prologue();
582   Mock4JS.verifyAllMocks();
584   // Step 4. Leave the callback of task1.
585   // Expectations.
586   this.mockLocalFunctions.expects(once()).task2();
587   // Invocation.
588   task1PluginInstance.epilogue();
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).
596   // Setup.
597   var test = setUpTaskManagerTest(this);
598   var task1PluginInstance1, task1PluginInstance2, task1PluginInstance3;
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();
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();
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();
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();
634 TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerSuspendError', function() {
635   // Tests that task manager's onSuspend method reports an error if there are
636   // pending tasks.
638   // Setup.
639   var test = setUpTaskManagerTest(this);
640   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
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();
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]();
663 TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerSuspendSuccess', function() {
664   // Tests that task manager's onSuspend method does not report an error if all
665   // tasks completed.
667   // Setup.
668   var test = setUpTaskManagerTest(this);
669   var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend');
670   var task1PluginInstance;
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();
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]();
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.
695   // Setup.
696   var test = setUpTaskManagerTest(this);
697   var task1PluginInstance;
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();
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();
715   // Step 3. Enter the callback of task1.
716   task1PluginInstance.prologue();
717   Mock4JS.verifyAllMocks();
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();
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.
731   // Setup.
732   var test = setUpTaskManagerTest(this);
733   var task1PluginInstance;
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();
745   // Step 2. Create a pending callback from code that is not a part of the task.
746   test.pluginFactory();
747   Mock4JS.verifyAllMocks();
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();
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);
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;
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;
779 function setupAttemptManagerTest(fixture) {
780   Math.random = function() { return testRandomValue; }
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   ]);
796   var testAttempts = buildAttemptManager(
797       'testAttempts',
798       fixture.mockLocalFunctions.functions().attempt,
799       testInitialDelaySeconds,
800       testMaximumDelaySeconds);
801   Mock4JS.verifyAllMocks();
803   return {
804     attempts: testAttempts
805   };
808 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerStartStop', function() {
809   // Tests starting and stopping an attempt manager.
811   // Setup.
812   var test = setupAttemptManagerTest(this);
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();
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();
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();
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();
867 TEST_F(
868     'GoogleNowUtilityUnitTest',
869     'AttemptManagerStartWithDelayParam',
870     function() {
871   // Tests starting an attempt manager with a delay parameter.
873   // Setup.
874   var test = setupAttemptManagerTest(this);
875   var testFirstDelaySeconds = 1039;
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);
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.
896   // Setup.
897   var test = setupAttemptManagerTest(this);
898   var testStoredRetryDelay = 433;
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);
926 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerGrowthLimit', function() {
927   // Tests that retry time stops growing at the maximum value.
929   // Setup.
930   var test = setupAttemptManagerTest(this);
931   var testStoredRetryDelay = 1500;
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);
960 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerAlarm', function() {
961   // Tests that firing the alarm invokes the attempt.
963   // Setup.
964   var test = setupAttemptManagerTest(this);
965   var onAlarmHandlerContainer = getMockHandlerContainer('alarms.onAlarm');
966   assertEquals(1, onAlarmHandlerContainer.length);
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});