| // Copyright 2013 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| /** |
| * Test fixture for utility.js. |
| * @constructor |
| * @extends {testing.Test} |
| */ |
| function GoogleNowUtilityUnitTest () { |
| testing.Test.call(this); |
| } |
| |
| GoogleNowUtilityUnitTest.prototype = { |
| __proto__: testing.Test.prototype, |
| |
| /** @override */ |
| extraLibraries: [ |
| 'common_test_util.js', |
| 'utility_test_util.js', |
| 'utility.js' |
| ] |
| }; |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'SendErrorReport1', function() { |
| // Test sending report for an error with a message that can be sent to server. |
| |
| // Setup and expectations. |
| var testStack = 'Error: TEST ERROR MESSAGE\n ' + |
| 'at buildErrorWithMessageForServer ' + |
| '(chrome-extension://ext_id/utility.js:29:15)\n' + |
| ' at <anonymous>:2:16\n ' + |
| 'at Object.InjectedScript._evaluateOn (<anonymous>:580:39)\n ' + |
| 'at Object.InjectedScript._evaluateAndWrap (<anonymous>:539:52)\n ' + |
| 'at Object.InjectedScript.evaluate (<anonymous>:458:21)'; |
| |
| var testError = { |
| canSendMessageToServer: true, |
| stack: testStack, |
| name: 'TEST ERROR NAME', |
| message: 'TEST ERROR MESSAGE' |
| }; |
| |
| var testIdentityToken = 'test identity token'; |
| |
| this.makeAndRegisterMockGlobals(['buildServerRequest']); |
| this.makeMockLocalFunctions(['sendRequest', 'setRequestHeader']); |
| this.makeAndRegisterMockApis(['chrome.identity.getAuthToken']); |
| |
| var mockRequest = { |
| send: this.mockLocalFunctions.functions().sendRequest, |
| setRequestHeader: this.mockLocalFunctions.functions().setRequestHeader |
| }; |
| |
| var expectedRequestObject = { |
| message: 'TEST ERROR NAME: TEST ERROR MESSAGE', |
| file: '//ext_id/utility.js', |
| line: '29', |
| trace: 'Error: TEST ERROR MESSAGE\n ' + |
| 'at buildErrorWithMessageForServer (chrome-extension://ext_id/util' + |
| 'ity.js:29:15)\n ' + |
| 'at <anonymous>:2:16\n ' + |
| 'at Object.InjectedScript._evaluateOn (<anonymous>:580:39)\n ' + |
| 'at Object.InjectedScript._evaluateAndWrap (<anonymous>:539:52)\n' + |
| ' at Object.InjectedScript.evaluate (<anonymous>:458:21)' |
| }; |
| |
| this.mockGlobals.expects(once()). |
| buildServerRequest('POST', 'jserrors', 'application/json'). |
| will(returnValue(mockRequest)); |
| |
| var chromeIdentityGetAuthTokenSavedArgs = new SaveMockArguments(); |
| this.mockApis.expects(once()). |
| chrome_identity_getAuthToken( |
| chromeIdentityGetAuthTokenSavedArgs.match( |
| eqJSON({interactive: false})), |
| chromeIdentityGetAuthTokenSavedArgs.match(ANYTHING)). |
| will(invokeCallback( |
| chromeIdentityGetAuthTokenSavedArgs, |
| 1, |
| testIdentityToken)); |
| |
| this.mockLocalFunctions.expects(once()).setRequestHeader( |
| 'Authorization', 'Bearer test identity token'); |
| this.mockLocalFunctions.expects(once()).sendRequest( |
| JSON.stringify(expectedRequestObject)); |
| |
| // Invoking the tested function. |
| sendErrorReport(testError); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'SendErrorReport2', function() { |
| // Test sending report for an error with a message that should not be sent to |
| // server, with an error generated in an anonymous function. |
| |
| // Setup and expectations. |
| var testStack = 'TypeError: Property \'processPendingDismissals\' of ' + |
| 'object [object Object] is not a function\n ' + |
| 'at chrome-extension://ext_id/background.js:444:11\n ' + |
| 'at chrome-extension://ext_id/utility.js:509:7'; |
| |
| var testError = { |
| stack: testStack, |
| name: 'TypeError' |
| }; |
| |
| var testIdentityToken = 'test identity token'; |
| |
| this.makeAndRegisterMockGlobals(['buildServerRequest']); |
| this.makeMockLocalFunctions(['sendRequest', 'setRequestHeader']); |
| this.makeAndRegisterMockApis(['chrome.identity.getAuthToken']); |
| |
| var mockRequest = { |
| send: this.mockLocalFunctions.functions().sendRequest, |
| setRequestHeader: this.mockLocalFunctions.functions().setRequestHeader |
| }; |
| |
| var expectedRequestObject = { |
| message: 'TypeError', |
| file: '//ext_id/background.js', |
| line: '444', |
| trace: '(message removed)\n ' + |
| 'at chrome-extension://ext_id/background.js:444:11\n ' + |
| 'at chrome-extension://ext_id/utility.js:509:7' |
| }; |
| |
| this.mockGlobals.expects(once()). |
| buildServerRequest('POST', 'jserrors', 'application/json'). |
| will(returnValue(mockRequest)); |
| |
| var chromeIdentityGetAuthTokenSavedArgs = new SaveMockArguments(); |
| this.mockApis.expects(once()). |
| chrome_identity_getAuthToken( |
| chromeIdentityGetAuthTokenSavedArgs.match( |
| eqJSON({interactive: false})), |
| chromeIdentityGetAuthTokenSavedArgs.match(ANYTHING)). |
| will(invokeCallback( |
| chromeIdentityGetAuthTokenSavedArgs, |
| 1, |
| testIdentityToken)); |
| |
| this.mockLocalFunctions.expects(once()).setRequestHeader( |
| 'Authorization', 'Bearer test identity token'); |
| this.mockLocalFunctions.expects(once()).sendRequest( |
| JSON.stringify(expectedRequestObject)); |
| |
| // Invoking the tested function. |
| sendErrorReport(testError); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'WrapperCheckInWrappedCallback', function() { |
| // Test generating an error when calling wrapper.checkInWrappedCallback from a |
| // non-instrumented code. |
| |
| // Setup and expectations. |
| var testError = { |
| testField: 'TEST VALUE' |
| }; |
| |
| this.makeAndRegisterMockGlobals([ |
| 'buildErrorWithMessageForServer', |
| 'reportError' |
| ]); |
| |
| this.mockGlobals.expects(once()). |
| buildErrorWithMessageForServer('Not in instrumented callback'). |
| will(returnValue(testError)); |
| this.mockGlobals.expects(once()). |
| reportError(eqJSON(testError)); |
| |
| // Invoking the tested function. |
| wrapper.checkInWrappedCallback(); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackEvent', function() { |
| // Tests wrapping event handler and that the handler code counts as an |
| // instrumented callback. |
| |
| // Setup. |
| var testError = { |
| testField: 'TEST VALUE' |
| }; |
| |
| this.makeAndRegisterMockGlobals([ |
| 'buildErrorWithMessageForServer', |
| 'reportError' |
| ]); |
| var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend'); |
| |
| this.makeMockLocalFunctions(['callback']); |
| |
| // Step 1. Wrap a callback. |
| var wrappedCallback = |
| wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Invoke wrapped callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()). |
| callback('test string', 239). |
| will(callFunction(function() { |
| wrapper.checkInWrappedCallback(); // it should succeed |
| })); |
| |
| // Invoking tested function. |
| wrappedCallback('test string', 239); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Check that after the callback we are again in non-instrumented |
| // code. |
| // Expectations. |
| this.mockGlobals.expects(once()). |
| buildErrorWithMessageForServer('Not in instrumented callback'). |
| will(returnValue(testError)); |
| this.mockGlobals.expects(once()). |
| reportError(eqJSON(testError)); |
| |
| // Invocation. |
| wrapper.checkInWrappedCallback(); |
| |
| // Step 4. Check that there won't be errors whe the page unloads. |
| assertEquals(1, onSuspendHandlerContainer.length); |
| onSuspendHandlerContainer[0](); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackPlugin', function() { |
| // Tests calling plugin's prologue and epilogue. |
| |
| // Setup. |
| this.makeMockLocalFunctions([ |
| 'callback', |
| 'pluginFactory', |
| 'prologue', |
| 'epilogue' |
| ]); |
| |
| // Step 1. Register plugin factory. |
| wrapper.registerWrapperPluginFactory( |
| this.mockLocalFunctions.functions().pluginFactory); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Wrap a callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()). |
| pluginFactory(). |
| will(returnValue({ |
| prologue: this.mockLocalFunctions.functions().prologue, |
| epilogue: this.mockLocalFunctions.functions().epilogue |
| })); |
| |
| // Invoking tested function. |
| var wrappedCallback = |
| wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Call the wrapped callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).prologue(); |
| this.mockLocalFunctions.expects(once()).callback(); |
| this.mockLocalFunctions.expects(once()).epilogue(); |
| |
| // Invoking wrapped callback. |
| wrappedCallback(); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'WrapperWrapCallbackCatchError', function() { |
| // Tests catching and sending errors by a wrapped callback. |
| |
| // Setup. |
| this.makeAndRegisterMockGlobals([ |
| 'reportError' |
| ]); |
| this.makeMockLocalFunctions(['callback']); |
| |
| // Step 1. Wrap a callback. |
| var wrappedCallback = |
| wrapper.wrapCallback(this.mockLocalFunctions.functions().callback, true); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Invoke wrapped callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()). |
| callback(). |
| will(callFunction(function() { |
| undefined.x = 5; |
| })); |
| this.mockGlobals.expects(once()). |
| reportError( |
| eqToString('TypeError: Cannot set property \'x\' of undefined')); |
| |
| // Invoking tested function. |
| wrappedCallback(); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', |
| 'WrapperInstrumentChromeApiFunction', |
| function() { |
| // Tests wrapper.instrumentChromeApiFunction(). |
| |
| // Setup. |
| this.makeMockLocalFunctions([ |
| 'apiFunction1', |
| 'apiFunction2', |
| 'callback1', |
| 'callback2', |
| 'pluginFactory', |
| 'prologue', |
| 'epilogue' |
| ]); |
| chrome.testApi = { |
| addListener: this.mockLocalFunctions.functions().apiFunction1 |
| }; |
| |
| // Step 1. Instrument the listener. |
| wrapper.instrumentChromeApiFunction('testApi.addListener', 1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Invoke the instrumented API call. |
| // Expectations. |
| var function1SavedArgs = new SaveMockArguments(); |
| this.mockLocalFunctions.expects(once()). |
| apiFunction1( |
| function1SavedArgs.match(eq(239)), |
| function1SavedArgs.match(ANYTHING)); |
| |
| // Invocation. |
| instrumented.testApi.addListener( |
| 239, this.mockLocalFunctions.functions().callback1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Invoke the callback that was passed by the instrumented function |
| // to the original one. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).callback1(237); |
| |
| // Invocation. |
| function1SavedArgs.arguments[1](237); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 4. Register plugin factory. |
| wrapper.registerWrapperPluginFactory( |
| this.mockLocalFunctions.functions().pluginFactory); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 5. Bind the API to another function. |
| chrome.testApi.addListener = this.mockLocalFunctions.functions().apiFunction2; |
| |
| // Step 6. Invoke the API with another callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()). |
| pluginFactory(). |
| will(returnValue({ |
| prologue: this.mockLocalFunctions.functions().prologue, |
| epilogue: this.mockLocalFunctions.functions().epilogue |
| })); |
| var function2SavedArgs = new SaveMockArguments(); |
| this.mockLocalFunctions.expects(once()). |
| apiFunction2( |
| function2SavedArgs.match(eq(239)), |
| function2SavedArgs.match(ANYTHING)); |
| |
| // Invocation. |
| instrumented.testApi.addListener( |
| 239, this.mockLocalFunctions.functions().callback2); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 7. Invoke the callback that was passed by the instrumented function |
| // to the original one. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).prologue(); |
| this.mockLocalFunctions.expects(once()).callback2(237); |
| this.mockLocalFunctions.expects(once()).epilogue(); |
| |
| // Invocation. |
| function2SavedArgs.arguments[1](237); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'WrapperOnSuspendListenerFail', function() { |
| // Tests that upon unloading event page, we get an error if there are pending |
| // required callbacks. |
| |
| // Setup. |
| var testError = { |
| testField: 'TEST VALUE' |
| }; |
| this.makeAndRegisterMockGlobals([ |
| 'buildErrorWithMessageForServer', |
| 'reportError' |
| ]); |
| this.makeMockLocalFunctions(['listener', 'callback']); |
| var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend'); |
| |
| // Step 1. Wrap event listener. |
| var wrappedListener = |
| wrapper.wrapCallback(this.mockLocalFunctions.functions().listener, true); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Invoke event listener, which will wrap a required callback. |
| // Setup and expectations. |
| var wrappedCallback; |
| var testFixture = this; |
| this.mockLocalFunctions.expects(once()). |
| listener(). |
| will(callFunction(function() { |
| wrappedCallback = wrapper.wrapCallback( |
| testFixture.mockLocalFunctions.functions().callback); |
| })); |
| |
| // Invocation. |
| wrappedListener(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Fire runtime.onSuspend event. |
| // Expectations. |
| this.mockGlobals.expects(once()). |
| buildErrorWithMessageForServer(stringContains( |
| 'ASSERT: Pending callbacks when unloading event page')). |
| will(returnValue(testError)); |
| this.mockGlobals.expects(once()). |
| reportError(eqJSON(testError)); |
| |
| // Invocation. |
| assertEquals(1, onSuspendHandlerContainer.length); |
| onSuspendHandlerContainer[0](); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', |
| 'WrapperOnSuspendListenerSuccess', |
| function() { |
| // Tests that upon unloading event page, we don't get an error if there are no |
| // pending required callbacks. |
| |
| // Setup. |
| this.makeMockLocalFunctions(['listener', 'callback']); |
| var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend'); |
| |
| // Step 1. Wrap event listener. |
| var wrappedListener = |
| wrapper.wrapCallback(this.mockLocalFunctions.functions().listener, true); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Invoke event listener, which will wrap a required callback. |
| // Setup and expectations. |
| var wrappedCallback; |
| var testFixture = this; |
| this.mockLocalFunctions.expects(once()). |
| listener(). |
| will(callFunction(function() { |
| wrappedCallback = wrapper.wrapCallback( |
| testFixture.mockLocalFunctions.functions().callback); |
| })); |
| |
| // Invocation. |
| wrappedListener(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Call wrapped callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).callback(); |
| |
| // Invocation. |
| wrappedCallback(); |
| |
| // Step 4. Fire runtime.onSuspend event. |
| assertEquals(1, onSuspendHandlerContainer.length); |
| onSuspendHandlerContainer[0](); |
| }); |
| |
| var taskNameA = 'TASK A'; |
| var taskNameB = 'TASK B'; |
| var taskNameC = 'TASK C'; |
| |
| function areTasksConflicting(newTaskName, scheduledTaskName) { |
| // Task B is conflicting with Task A. This means that if Task B is added when |
| // Task A is running, Task B will be ignored (but not vice versa). No other |
| // pair is conflicting. |
| return newTaskName == taskNameB && scheduledTaskName == taskNameA; |
| } |
| |
| function setUpTaskManagerTest(fixture) { |
| fixture.makeAndRegisterMockApis([ |
| 'wrapper.checkInWrappedCallback', |
| 'wrapper.registerWrapperPluginFactory', |
| 'wrapper.debugGetStateString' |
| ]); |
| fixture.makeMockLocalFunctions(['task1', 'task2', 'task3']); |
| fixture.makeAndRegisterMockGlobals(['reportError']); |
| |
| fixture.mockApis.stubs().wrapper_checkInWrappedCallback(); |
| fixture.mockApis.stubs().wrapper_debugGetStateString(). |
| will(returnValue('testWrapperDebugState')); |
| |
| var registerWrapperPluginFactorySavedArgs = new SaveMockArguments(); |
| fixture.mockApis.expects(once()).wrapper_registerWrapperPluginFactory( |
| registerWrapperPluginFactorySavedArgs.match(ANYTHING)); |
| var tasks = buildTaskManager(areTasksConflicting); |
| Mock4JS.verifyAllMocks(); |
| |
| return { |
| tasks: tasks, |
| pluginFactory: registerWrapperPluginFactorySavedArgs.arguments[0] |
| }; |
| } |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'TaskManager2Sequential', function() { |
| // Tests that 2 tasks get successfully executed consecutively, even if the |
| // second one conflicts with the first. |
| |
| // Setup. |
| var test = setUpTaskManagerTest(this); |
| |
| // Step 1. Add 1st task that doesn't create pending callbacks. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task1(); |
| // Invocation. |
| test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Add 2nd task. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task2(); |
| // Invocation. |
| test.tasks.add(taskNameB, this.mockLocalFunctions.functions().task2); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerConflicting', function() { |
| // Tests that adding a task while a conflicting task is being executed causes |
| // the second one to be ignored. |
| |
| // Setup. |
| var test = setUpTaskManagerTest(this); |
| var task1PluginInstance; |
| |
| // Step 1. Add 1st task that creates a pending callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task1(). |
| will(callFunction(function() { |
| task1PluginInstance = test.pluginFactory(); |
| })); |
| // Invocation. |
| test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Add 2nd task. Since it conflicts with currently running task1 |
| // (see areTasksConflicting), it should be ignored. |
| test.tasks.add(taskNameB, this.mockLocalFunctions.functions().task2); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Enter the callback of task1. |
| task1PluginInstance.prologue(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 4. Leave the callback of task1. |
| task1PluginInstance.epilogue(); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerNestedTaskEnqueue', function() { |
| // Tests that adding a task while a non-conflicting task is being executed |
| // causes the second one to be executed after the first one completes. |
| |
| // Setup. |
| var test = setUpTaskManagerTest(this); |
| var task1PluginInstance; |
| |
| // Step 1. Add 1st task that creates a pending callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task1(). |
| will(callFunction(function() { |
| task1PluginInstance = test.pluginFactory(); |
| })); |
| // Invocation. |
| test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Add 2nd task. Since it doesn't conflict with currently running |
| // task1 (see areTasksConflicting), it should not be ignored. |
| test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task2); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Enter the callback of task1. |
| task1PluginInstance.prologue(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 4. Leave the callback of task1. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task2(); |
| // Invocation. |
| task1PluginInstance.epilogue(); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerBranching', function() { |
| // Tests that task manager correctly detects completion of tasks that create |
| // branching chains of callbacks (in this test, task1 creates pending |
| // callbacks 1 and 2, and callback 1 creates pending callback 3). |
| |
| // Setup. |
| var test = setUpTaskManagerTest(this); |
| var task1PluginInstance1, task1PluginInstance2, task1PluginInstance3; |
| |
| // Step 1. Add 1st task that creates a 2 pending callbacks. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task1(). |
| will(callFunction(function() { |
| task1PluginInstance1 = test.pluginFactory(); |
| task1PluginInstance2 = test.pluginFactory(); |
| })); |
| // Invocation. |
| test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Add 2nd task, which is not conflicting (see areTasksConflicting) |
| // with task1. |
| test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task2); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Enter callback 1, create pending callback 3, exit callback 1. |
| // Enter/exit callback 2. Enter callback 3. |
| task1PluginInstance1.prologue(); |
| task1PluginInstance3 = test.pluginFactory(); |
| task1PluginInstance1.epilogue(); |
| task1PluginInstance2.prologue(); |
| task1PluginInstance2.epilogue(); |
| task1PluginInstance3.prologue(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 4. Leave 3rd callback of task1. Now task1 is complete, and task2 |
| // should start. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task2(); |
| // Invocation. |
| task1PluginInstance3.epilogue(); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerSuspendError', function() { |
| // Tests that task manager's onSuspend method reports an error if there are |
| // pending tasks. |
| |
| // Setup. |
| var test = setUpTaskManagerTest(this); |
| var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend'); |
| |
| // Step 1. Add a task that creates a pending callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task1(). |
| will(callFunction(function() { |
| test.pluginFactory(); |
| })); |
| // Invocation. |
| test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Invoke onSuspend event of the task manager. |
| // Setup and expectations. The 2 callbacks in onSuspendHandlerContainer are |
| // from the wrapper and the task manager. |
| assertEquals(2, onSuspendHandlerContainer.length); |
| this.mockGlobals.expects(once()).reportError(eqToString( |
| 'Error: ASSERT: Incomplete task when unloading event page,' + |
| ' queue = [{"name":"TASK A"}], testWrapperDebugState')); |
| // Invocation. |
| onSuspendHandlerContainer[1](); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerSuspendSuccess', function() { |
| // Tests that task manager's onSuspend method does not report an error if all |
| // tasks completed. |
| |
| // Setup. |
| var test = setUpTaskManagerTest(this); |
| var onSuspendHandlerContainer = getMockHandlerContainer('runtime.onSuspend'); |
| var task1PluginInstance; |
| |
| // Step 1. Add a task that creates a pending callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task1(). |
| will(callFunction(function() { |
| task1PluginInstance = test.pluginFactory(); |
| })); |
| // Invocation. |
| test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Invoke task's callback and the onSuspend event of the task manager. |
| // The 2 callbacks in onSuspendHandlerContainer are from the wrapper and the |
| // task manager. |
| task1PluginInstance.prologue(); |
| task1PluginInstance.epilogue(); |
| onSuspendHandlerContainer[1](); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'TaskManager3Tasks', function() { |
| // Tests that 3 tasks can be executed too. In particular, that if the second |
| // task is a single-step task which execution was originally blocked by task1, |
| // unblocking it causes immediate synchronous execution of both tasks 2 and 3. |
| |
| // Setup. |
| var test = setUpTaskManagerTest(this); |
| var task1PluginInstance; |
| |
| // Step 1. Add 1st task that creates a pending callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task1(). |
| will(callFunction(function() { |
| task1PluginInstance = test.pluginFactory(); |
| })); |
| // Invocation. |
| test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Add 2nd and 3rd tasks, both non-conflicting (see |
| // areTasksConflicting) with task1. |
| test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task2); |
| test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task3); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Enter the callback of task1. |
| task1PluginInstance.prologue(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 4. Leave the callback of task1. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task2(); |
| this.mockLocalFunctions.expects(once()).task3(); |
| // Invocation. |
| task1PluginInstance.epilogue(); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'TaskManagerNestedNonTask', function() { |
| // Tests callbacks requested while a task is running, but not from a callback |
| // belonging to a task, are not counted as a part of the task. |
| |
| // Setup. |
| var test = setUpTaskManagerTest(this); |
| var task1PluginInstance; |
| |
| // Step 1. Add 1st task that creates a pending callback. |
| // Expectations. |
| this.mockLocalFunctions.expects(once()).task1(). |
| will(callFunction(function() { |
| task1PluginInstance = test.pluginFactory(); |
| })); |
| // Invocation. |
| test.tasks.add(taskNameA, this.mockLocalFunctions.functions().task1); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Create a pending callback from code that is not a part of the task. |
| test.pluginFactory(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Enter the callback of task1. After this, task1 should be |
| // finished despite the pending non-task callback. |
| task1PluginInstance.prologue(); |
| task1PluginInstance.epilogue(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 4. Check that task1 is finished by submitting task2, which should |
| // be executed immediately. |
| this.mockLocalFunctions.expects(once()).task2(); |
| test.tasks.add(taskNameC, this.mockLocalFunctions.functions().task2); |
| }); |
| |
| var testAttemptAlarmName = 'attempt-scheduler-testAttempts'; |
| var testAttemptStorageKey = 'current-delay-testAttempts'; |
| var testInitialDelaySeconds = 239; |
| var testMaximumDelaySeconds = 2239; |
| // Value to be returned by mocked Math.random(). We want the value returned by |
| // Math.random() to be predictable to be able to check results against expected |
| // values. A fixed seed would be okay, but fixed seeding isn't possible in JS at |
| // the moment. |
| var testRandomValue = 0.31415926; |
| |
| function createTestAttempStorageEntry(delaySeconds) { |
| // Creates a test storage object that attempt manager uses to store current |
| // delay. |
| var storageObject = {}; |
| storageObject[testAttemptStorageKey] = delaySeconds; |
| return storageObject; |
| } |
| |
| function setupAttemptManagerTest(fixture) { |
| Math.random = function() { return testRandomValue; } |
| |
| fixture.makeMockLocalFunctions([ |
| 'attempt', |
| 'planForNextCallback', |
| 'isRunningCallback' |
| ]); |
| fixture.makeAndRegisterMockApis([ |
| 'chrome.alarms.clear', |
| 'chrome.alarms.create', |
| 'chrome.storage.local.remove', |
| 'chrome.storage.local.set', |
| 'instrumented.alarms.get', |
| 'instrumented.storage.local.get' |
| ]); |
| |
| var testAttempts = buildAttemptManager( |
| 'testAttempts', |
| fixture.mockLocalFunctions.functions().attempt, |
| testInitialDelaySeconds, |
| testMaximumDelaySeconds); |
| Mock4JS.verifyAllMocks(); |
| |
| return { |
| attempts: testAttempts |
| }; |
| } |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerStartStop', function() { |
| // Tests starting and stopping an attempt manager. |
| |
| // Setup. |
| var test = setupAttemptManagerTest(this); |
| |
| // Step 1. Check that attempt manager is not running. |
| // Expectations. |
| var alarmsGetSavedArgs = new SaveMockArguments(); |
| this.mockApis.expects(once()). |
| instrumented_alarms_get( |
| alarmsGetSavedArgs.match(eq(testAttemptAlarmName)), |
| alarmsGetSavedArgs.match(ANYTHING)). |
| will(invokeCallback(alarmsGetSavedArgs, 1, undefined)); |
| this.mockLocalFunctions.expects(once()).isRunningCallback(false); |
| // Invocation. |
| test.attempts.isRunning( |
| this.mockLocalFunctions.functions().isRunningCallback); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 2. Start attempt manager with no parameters. |
| // Expectations. |
| var expectedRetryDelaySeconds = |
| testInitialDelaySeconds * (1 + testRandomValue * 0.2); |
| this.mockApis.expects(once()).chrome_alarms_create( |
| testAttemptAlarmName, |
| eqJSON({ |
| delayInMinutes: expectedRetryDelaySeconds / 60, |
| periodInMinutes: testMaximumDelaySeconds / 60 |
| })); |
| this.mockApis.expects(once()).chrome_storage_local_set( |
| eqJSON(createTestAttempStorageEntry(expectedRetryDelaySeconds))); |
| // Invocation. |
| test.attempts.start(); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 3. Check that attempt manager is running. |
| // Expectations. |
| alarmsGetSavedArgs = new SaveMockArguments(); |
| this.mockApis.expects(once()). |
| instrumented_alarms_get( |
| alarmsGetSavedArgs.match(eq(testAttemptAlarmName)), |
| alarmsGetSavedArgs.match(ANYTHING)). |
| will(invokeCallback(alarmsGetSavedArgs, 1, {testField: 'TEST VALUE'})); |
| this.mockLocalFunctions.expects(once()).isRunningCallback(true); |
| // Invocation. |
| test.attempts.isRunning( |
| this.mockLocalFunctions.functions().isRunningCallback); |
| Mock4JS.verifyAllMocks(); |
| |
| // Step 4. Stop task manager. |
| // Expectations. |
| this.mockApis.expects(once()).chrome_alarms_clear(testAttemptAlarmName); |
| this.mockApis.expects(once()).chrome_storage_local_remove( |
| testAttemptStorageKey); |
| // Invocation. |
| test.attempts.stop(); |
| }); |
| |
| TEST_F( |
| 'GoogleNowUtilityUnitTest', |
| 'AttemptManagerStartWithDelayParam', |
| function() { |
| // Tests starting an attempt manager with a delay parameter. |
| |
| // Setup. |
| var test = setupAttemptManagerTest(this); |
| var testFirstDelaySeconds = 1039; |
| |
| // Starting attempt manager with a parameter specifying first delay. |
| // Expectations. |
| this.mockApis.expects(once()).chrome_alarms_create( |
| testAttemptAlarmName, |
| eqJSON({ |
| delayInMinutes: testFirstDelaySeconds / 60, |
| periodInMinutes: testMaximumDelaySeconds / 60 |
| })); |
| this.mockApis.expects(once()).chrome_storage_local_remove( |
| testAttemptStorageKey); |
| // Invocation. |
| test.attempts.start(testFirstDelaySeconds); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerExponGrowth', function() { |
| // Tests that retry time grows exponentially. We don't need to check the case |
| // of growing more than once, since the object doesn't have state, and the |
| // test checks all its inputs and outputs of the tested code. |
| |
| // Setup. |
| var test = setupAttemptManagerTest(this); |
| var testStoredRetryDelay = 433; |
| |
| // Call planForNext, which prepares next attempt. Current retry time |
| // is less than 1/2 of the maximum delay. |
| // Expectations. |
| var expectedRetryDelaySeconds = |
| testStoredRetryDelay * 2 * (1 + testRandomValue * 0.2); |
| var storageGetSavedArgs = new SaveMockArguments(); |
| this.mockApis.expects(once()).instrumented_storage_local_get( |
| storageGetSavedArgs.match(eq(testAttemptStorageKey)), |
| storageGetSavedArgs.match(ANYTHING)). |
| will(invokeCallback( |
| storageGetSavedArgs, |
| 1, |
| createTestAttempStorageEntry(testStoredRetryDelay))); |
| this.mockApis.expects(once()).chrome_alarms_create( |
| testAttemptAlarmName, |
| eqJSON({ |
| delayInMinutes: expectedRetryDelaySeconds / 60, |
| periodInMinutes: testMaximumDelaySeconds / 60})); |
| this.mockApis.expects(once()).chrome_storage_local_set( |
| eqJSON(createTestAttempStorageEntry(expectedRetryDelaySeconds))); |
| this.mockLocalFunctions.expects(once()).planForNextCallback(); |
| // Invocation. |
| test.attempts.planForNext( |
| this.mockLocalFunctions.functions().planForNextCallback); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerGrowthLimit', function() { |
| // Tests that retry time stops growing at the maximum value. |
| |
| // Setup. |
| var test = setupAttemptManagerTest(this); |
| var testStoredRetryDelay = 1500; |
| |
| // Call planForNext, which prepares next attempt. Current retry time |
| // is greater than 1/2 of the maximum delay. |
| // Expectations. |
| var expectedRetryDelaySeconds = testMaximumDelaySeconds; |
| var storageGetSavedArgs = new SaveMockArguments(); |
| this.mockApis.expects(once()). |
| instrumented_storage_local_get( |
| storageGetSavedArgs.match(eq(testAttemptStorageKey)), |
| storageGetSavedArgs.match(ANYTHING)). |
| will(invokeCallback( |
| storageGetSavedArgs, |
| 1, |
| createTestAttempStorageEntry(testStoredRetryDelay))); |
| this.mockApis.expects(once()).chrome_alarms_create( |
| testAttemptAlarmName, |
| eqJSON({ |
| delayInMinutes: expectedRetryDelaySeconds / 60, |
| periodInMinutes: testMaximumDelaySeconds / 60 |
| })); |
| this.mockApis.expects(once()).chrome_storage_local_set( |
| eqJSON(createTestAttempStorageEntry(expectedRetryDelaySeconds))); |
| this.mockLocalFunctions.expects(once()).planForNextCallback(); |
| // Invocation. |
| test.attempts.planForNext( |
| this.mockLocalFunctions.functions().planForNextCallback); |
| }); |
| |
| TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerAlarm', function() { |
| // Tests that firing the alarm invokes the attempt. |
| |
| // Setup. |
| var test = setupAttemptManagerTest(this); |
| var onAlarmHandlerContainer = getMockHandlerContainer('alarms.onAlarm'); |
| assertEquals(1, onAlarmHandlerContainer.length); |
| |
| // Fire the alarm and check that this invokes the attempt callback. |
| // Expectations. |
| var alarmsGetSavedArgs = new SaveMockArguments(); |
| this.mockApis.expects(once()). |
| instrumented_alarms_get( |
| alarmsGetSavedArgs.match(eq(testAttemptAlarmName)), |
| alarmsGetSavedArgs.match(ANYTHING)). |
| will(invokeCallback(alarmsGetSavedArgs, 1, {testField: 'TEST VALUE'})); |
| this.mockLocalFunctions.expects(once()).attempt(); |
| // Invocation. |
| onAlarmHandlerContainer[0]({name: testAttemptAlarmName}); |
| }); |