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