// 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 :2:16\n ' + 'at Object.InjectedScript._evaluateOn (:580:39)\n ' + 'at Object.InjectedScript._evaluateAndWrap (:539:52)\n ' + 'at Object.InjectedScript.evaluate (: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 :2:16\n ' + 'at Object.InjectedScript._evaluateOn (:580:39)\n ' + 'at Object.InjectedScript._evaluateAndWrap (:539:52)\n' + ' at Object.InjectedScript.evaluate (: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; /** * Creates a default storage current delay object. */ function createTestAttemptStorageEntryRequest() { var storageObject = {}; storageObject[testAttemptStorageKey] = undefined; return storageObject; } /** * Creates a test storage object that attempt manager uses to store current * delay. */ function createTestAttemptStorageEntry(delaySeconds) { var storageObject = {}; storageObject[testAttemptStorageKey] = delaySeconds; return storageObject; } function setupAttemptManagerTest(fixture) { Math.random = function() { return testRandomValue; } fixture.makeMockLocalFunctions([ 'attempt', 'isRunningCallback' ]); fixture.makeAndRegisterMockApis([ 'chrome.alarms.clear', 'chrome.alarms.create', 'chrome.storage.local.remove', 'chrome.storage.local.set', 'fillFromChromeLocalStorage', 'instrumented.alarms.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(createTestAttemptStorageEntry(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 scheduleRetry, which schedules a retry. // Current retry time is less than 1/2 of the maximum delay. // Expectations. var expectedRetryDelaySeconds = testStoredRetryDelay * 2 * (1 + testRandomValue * 0.2); expectChromeLocalStorageGet( this, createTestAttemptStorageEntryRequest(), createTestAttemptStorageEntry(testStoredRetryDelay), true); this.mockApis.expects(once()).chrome_alarms_create( testAttemptAlarmName, eqJSON({ delayInMinutes: expectedRetryDelaySeconds / 60, periodInMinutes: testMaximumDelaySeconds / 60})); this.mockApis.expects(once()).chrome_storage_local_set( eqJSON(createTestAttemptStorageEntry(expectedRetryDelaySeconds))); // Invocation. var thenCalled = false; var catchCalled = false; test.attempts.scheduleRetry().then(function(request) { thenCalled = true; }).catch(function(request) { catchCalled = true; }); assertTrue(thenCalled); assertFalse(catchCalled); }); TEST_F('GoogleNowUtilityUnitTest', 'AttemptManagerGrowthLimit', function() { // Tests that retry time stops growing at the maximum value. // Setup. var test = setupAttemptManagerTest(this); var testStoredRetryDelay = 1500; // Call scheduleRetry, which schedules a retry. // Current retry time is greater than 1/2 of the maximum delay. // Expectations. var expectedRetryDelaySeconds = testMaximumDelaySeconds * (1 + testRandomValue * 0.2); expectChromeLocalStorageGet( this, createTestAttemptStorageEntryRequest(), createTestAttemptStorageEntry(testStoredRetryDelay), true); this.mockApis.expects(once()).chrome_alarms_create( testAttemptAlarmName, eqJSON({ delayInMinutes: expectedRetryDelaySeconds / 60, periodInMinutes: testMaximumDelaySeconds / 60 })); this.mockApis.expects(once()).chrome_storage_local_set( eqJSON(createTestAttemptStorageEntry(expectedRetryDelaySeconds))); // Invocation. var thenCalled = false; var catchCalled = false; test.attempts.scheduleRetry().then(function(request) { thenCalled = true; }).catch(function(request) { catchCalled = true; }); assertTrue(thenCalled); assertFalse(catchCalled); }); 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}); });