• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 */
10function GoogleNowUtilityUnitTest () {
11  testing.Test.call(this);
12}
13
14GoogleNowUtilityUnitTest.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
25TEST_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
92TEST_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
151TEST_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
175TEST_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
226TEST_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
266TEST_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
295TEST_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
378TEST_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
427TEST_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
469var taskNameA = 'TASK A';
470var taskNameB = 'TASK B';
471var taskNameC = 'TASK C';
472
473function 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
480function 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
505TEST_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
526TEST_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
557TEST_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
591TEST_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
634TEST_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
663TEST_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
690TEST_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
727TEST_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
761var testAttemptAlarmName = 'attempt-scheduler-testAttempts';
762var testAttemptStorageKey = 'current-delay-testAttempts';
763var testInitialDelaySeconds = 239;
764var 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.
769var testRandomValue = 0.31415926;
770
771/**
772 * Creates a default storage current delay object.
773 */
774function 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 */
784function createTestAttemptStorageEntry(delaySeconds) {
785  var storageObject = {};
786  storageObject[testAttemptStorageKey] = delaySeconds;
787  return storageObject;
788}
789
790function 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
818TEST_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
877TEST_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
901TEST_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
939TEST_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
976TEST_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});
996