blob: 64ba1d84b5a9166b0716144599e35a2dbc7c1f65 [file] [log] [blame]
// 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});
});