Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / resources / google_now / utility_unittest.gtestjs
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 /**
772  * Creates a default storage current delay object.
773  */
774 function createTestAttemptStorageEntryRequest() {
775   var storageObject = {};
776   storageObject[testAttemptStorageKey] = undefined;
777   return storageObject;
778 }
779
780 /**
781  * Creates a test storage object that attempt manager uses to store current
782  * delay.
783  */
784 function createTestAttemptStorageEntry(delaySeconds) {
785   var storageObject = {};
786   storageObject[testAttemptStorageKey] = delaySeconds;
787   return storageObject;
788 }
789
790 function setupAttemptManagerTest(fixture) {
791   Math.random = function() { return testRandomValue; }
792
793   fixture.makeMockLocalFunctions([
794     'attempt',
795     'isRunningCallback'
796   ]);
797   fixture.makeAndRegisterMockApis([
798     'chrome.alarms.clear',
799     'chrome.alarms.create',
800     'chrome.storage.local.remove',
801     'chrome.storage.local.set',
802     'fillFromChromeLocalStorage',
803     'instrumented.alarms.get'
804   ]);
805
806   var testAttempts = buildAttemptManager(
807       'testAttempts',
808       fixture.mockLocalFunctions.functions().attempt,
809       testInitialDelaySeconds,
810       testMaximumDelaySeconds);
811   Mock4JS.verifyAllMocks();
812
813   return {
814     attempts: testAttempts
815   };
816 }
817
818 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerStartStop', function() {
819   // Tests starting and stopping an attempt manager.
820
821   // Setup.
822   var test = setupAttemptManagerTest(this);
823
824   // Step 1. Check that attempt manager is not running.
825   // Expectations.
826   var alarmsGetSavedArgs = new SaveMockArguments();
827   this.mockApis.expects(once()).
828       instrumented_alarms_get(
829           alarmsGetSavedArgs.match(eq(testAttemptAlarmName)),
830           alarmsGetSavedArgs.match(ANYTHING)).
831       will(invokeCallback(alarmsGetSavedArgs, 1, undefined));
832   this.mockLocalFunctions.expects(once()).isRunningCallback(false);
833   // Invocation.
834   test.attempts.isRunning(
835       this.mockLocalFunctions.functions().isRunningCallback);
836   Mock4JS.verifyAllMocks();
837
838   // Step 2. Start attempt manager with no parameters.
839   // Expectations.
840   var expectedRetryDelaySeconds =
841       testInitialDelaySeconds * (1 + testRandomValue * 0.2);
842   this.mockApis.expects(once()).chrome_alarms_create(
843     testAttemptAlarmName,
844     eqJSON({
845       delayInMinutes: expectedRetryDelaySeconds / 60,
846       periodInMinutes: testMaximumDelaySeconds / 60
847     }));
848   this.mockApis.expects(once()).chrome_storage_local_set(
849       eqJSON(createTestAttemptStorageEntry(expectedRetryDelaySeconds)));
850   // Invocation.
851   test.attempts.start();
852   Mock4JS.verifyAllMocks();
853
854   // Step 3. Check that attempt manager is running.
855   // Expectations.
856   alarmsGetSavedArgs = new SaveMockArguments();
857   this.mockApis.expects(once()).
858       instrumented_alarms_get(
859           alarmsGetSavedArgs.match(eq(testAttemptAlarmName)),
860           alarmsGetSavedArgs.match(ANYTHING)).
861       will(invokeCallback(alarmsGetSavedArgs, 1, {testField: 'TEST VALUE'}));
862   this.mockLocalFunctions.expects(once()).isRunningCallback(true);
863   // Invocation.
864   test.attempts.isRunning(
865       this.mockLocalFunctions.functions().isRunningCallback);
866   Mock4JS.verifyAllMocks();
867
868   // Step 4. Stop task manager.
869   // Expectations.
870   this.mockApis.expects(once()).chrome_alarms_clear(testAttemptAlarmName);
871   this.mockApis.expects(once()).chrome_storage_local_remove(
872       testAttemptStorageKey);
873   // Invocation.
874   test.attempts.stop();
875 });
876
877 TEST_F(
878     'GoogleNowUtilityUnitTest',
879     'AttemptManagerStartWithDelayParam',
880     function() {
881   // Tests starting an attempt manager with a delay parameter.
882
883   // Setup.
884   var test = setupAttemptManagerTest(this);
885   var testFirstDelaySeconds = 1039;
886
887   // Starting attempt manager with a parameter specifying first delay.
888   // Expectations.
889   this.mockApis.expects(once()).chrome_alarms_create(
890       testAttemptAlarmName,
891       eqJSON({
892         delayInMinutes: testFirstDelaySeconds / 60,
893         periodInMinutes: testMaximumDelaySeconds / 60
894       }));
895   this.mockApis.expects(once()).chrome_storage_local_remove(
896     testAttemptStorageKey);
897   // Invocation.
898   test.attempts.start(testFirstDelaySeconds);
899 });
900
901 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerExponGrowth', function() {
902   // Tests that retry time grows exponentially. We don't need to check the case
903   // of growing more than once, since the object doesn't have state, and the
904   // test checks all its inputs and outputs of the tested code.
905
906   // Setup.
907   var test = setupAttemptManagerTest(this);
908   var testStoredRetryDelay = 433;
909
910   // Call scheduleRetry, which schedules a retry.
911   // Current retry time is less than 1/2 of the maximum delay.
912   // Expectations.
913   var expectedRetryDelaySeconds =
914       testStoredRetryDelay * 2 * (1 + testRandomValue * 0.2);
915   expectChromeLocalStorageGet(
916       this,
917       createTestAttemptStorageEntryRequest(),
918       createTestAttemptStorageEntry(testStoredRetryDelay),
919       true);
920   this.mockApis.expects(once()).chrome_alarms_create(
921       testAttemptAlarmName,
922       eqJSON({
923         delayInMinutes: expectedRetryDelaySeconds / 60,
924         periodInMinutes: testMaximumDelaySeconds / 60}));
925   this.mockApis.expects(once()).chrome_storage_local_set(
926       eqJSON(createTestAttemptStorageEntry(expectedRetryDelaySeconds)));
927   // Invocation.
928   var thenCalled = false;
929   var catchCalled = false;
930   test.attempts.scheduleRetry().then(function(request) {
931     thenCalled = true;
932   }).catch(function(request) {
933     catchCalled = true;
934   });
935   assertTrue(thenCalled);
936   assertFalse(catchCalled);
937 });
938
939 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerGrowthLimit', function() {
940   // Tests that retry time stops growing at the maximum value.
941
942   // Setup.
943   var test = setupAttemptManagerTest(this);
944   var testStoredRetryDelay = 1500;
945
946   // Call scheduleRetry, which schedules a retry.
947   // Current retry time is greater than 1/2 of the maximum delay.
948   // Expectations.
949   var expectedRetryDelaySeconds =
950       testMaximumDelaySeconds * (1 + testRandomValue * 0.2);
951   expectChromeLocalStorageGet(
952       this,
953       createTestAttemptStorageEntryRequest(),
954       createTestAttemptStorageEntry(testStoredRetryDelay),
955       true);
956   this.mockApis.expects(once()).chrome_alarms_create(
957       testAttemptAlarmName,
958       eqJSON({
959         delayInMinutes: expectedRetryDelaySeconds / 60,
960         periodInMinutes: testMaximumDelaySeconds / 60
961       }));
962   this.mockApis.expects(once()).chrome_storage_local_set(
963       eqJSON(createTestAttemptStorageEntry(expectedRetryDelaySeconds)));
964   // Invocation.
965   var thenCalled = false;
966   var catchCalled = false;
967   test.attempts.scheduleRetry().then(function(request) {
968     thenCalled = true;
969   }).catch(function(request) {
970     catchCalled = true;
971   });
972   assertTrue(thenCalled);
973   assertFalse(catchCalled);
974 });
975
976 TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerAlarm', function() {
977   // Tests that firing the alarm invokes the attempt.
978
979   // Setup.
980   var test = setupAttemptManagerTest(this);
981   var onAlarmHandlerContainer = getMockHandlerContainer('alarms.onAlarm');
982   assertEquals(1, onAlarmHandlerContainer.length);
983
984   // Fire the alarm and check that this invokes the attempt callback.
985   // Expectations.
986   var alarmsGetSavedArgs = new SaveMockArguments();
987   this.mockApis.expects(once()).
988       instrumented_alarms_get(
989           alarmsGetSavedArgs.match(eq(testAttemptAlarmName)),
990           alarmsGetSavedArgs.match(ANYTHING)).
991       will(invokeCallback(alarmsGetSavedArgs, 1, {testField: 'TEST VALUE'}));
992   this.mockLocalFunctions.expects(once()).attempt();
993   // Invocation.
994   onAlarmHandlerContainer[0]({name: testAttemptAlarmName});
995 });