// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License.txt in the project root for license information.
//
///
///
///
module CorsicaTests {
"use strict";
var Node = WinJS.Class.define(function (data) {
this.data = data;
});
WinJS.Class.mix(Node, WinJS.Utilities._linkedListMixin("Node"));
function assertSequenceEquals(a, b) {
LiveUnit.Assert.areEqual(a.length, b.length);
var i, len;
for (i = 0, len = a.length; i < len; i++) {
LiveUnit.Assert.isTrue(a[i] === b[i], "Element at index '" + i + "' expected to be the same");
}
}
function toArray(list) {
var array = [];
array.push(list.data);
while (list = list._nextNode) {
array.push(list.data);
}
return array;
}
export class LinkedListMixin {
testBasic() {
var list = new Node(1);
list._insertNodeAfter(new Node(2));
list._insertNodeAfter(new Node(3));
list._insertNodeAfter(new Node(4));
assertSequenceEquals(toArray(list), [1, 4, 3, 2]);
}
testBasicTwo() {
var current, list = current = new Node(1);
current = current._insertNodeAfter(new Node(2));
current = current._insertNodeAfter(new Node(3));
current = current._insertNodeAfter(new Node(4));
assertSequenceEquals(toArray(list), [1, 2, 3, 4]);
}
testInsertBefore() {
var current, list = current = new Node(1);
current = current._insertNodeBefore(new Node(2));
current = current._insertNodeBefore(new Node(3));
current = current._insertNodeBefore(new Node(4));
assertSequenceEquals(toArray(list._prevNode._prevNode._prevNode), [4, 3, 2, 1]);
}
testRemove() {
var current, list = current = new Node(1);
current = current._insertNodeAfter(new Node(2));
current = current._insertNodeAfter(new Node(3));
current = current._insertNodeAfter(new Node(4));
assertSequenceEquals(toArray(list), [1, 2, 3, 4]);
list._nextNode._nextNode._removeNode();
assertSequenceEquals(toArray(list), [1, 2, 4]);
list._nextNode._nextNode._removeNode();
assertSequenceEquals(toArray(list), [1, 2]);
}
}
}
module CorsicaTests {
"use strict";
var Promise = WinJS.Promise;
var S = WinJS.Utilities.Scheduler;
function assertSequenceEquals(a, b) {
LiveUnit.Assert.areEqual(a.length, b.length);
var i, len;
for (i = 0, len = a.length; i < len; i++) {
LiveUnit.Assert.isTrue(a[i] === b[i], "Element at index '" + i + "' expected to be the same");
}
}
// Convert an Array-like object to an array. Useful when you want to treat
// the "arguments" local variable as an Array.
function toArray(list) {
return Array.prototype.slice.call(list);
}
function async(test, expectedExceptions?) {
var wrappedTest = function (complete) {
var p = test();
if (p) {
p
.then(null, function (msg) {
try {
LiveUnit.Assert.fail('There was an unhandled error in your test: ' + msg);
} catch (ex) {
// purposefully empty
}
})
.then(function () {
complete();
});
} else {
complete();
}
};
if (expectedExceptions) {
wrappedTest["LiveUnit.ExpectedException"] = expectedExceptions;
}
return wrappedTest;
}
function schedulePromise(priority) {
var s = new WinJS._Signal();
S.schedule(s.complete.bind(s), priority, null, "Scheduled Promise");
return s.promise;
}
function immediate() {
return new WinJS.Promise(function (c) {
WinJS.Utilities._setImmediate(c);
});
}
// Repeatedly calls fn until msecs have elapsed
//
function repeatForDuration(msecs, fn?) {
var start = WinJS.Utilities._now(),
end = start + msecs;
while (WinJS.Utilities._now() < end) {
if (fn) { fn(); }
}
}
// Ensure that the constants for the WWA scheduler are available on MSApp in contexts
// where the WWA scheduler isn't available. They are needed for the mimicSchedulePump
// function.
//
var MSApp = (window['MSApp'] && window['MSApp'].HIGH ? window['MSApp'] : {
HIGH: "high",
NORMAL: "normal",
IDLE: "idle"
});
var wwaPriorityToInt = {};
wwaPriorityToInt[MSApp.IDLE] = 1;
wwaPriorityToInt[MSApp.NORMAL] = 2;
wwaPriorityToInt[MSApp.HIGH] = 3;
function isEqualOrHigherWwaPriority(priority1, priority2) {
return wwaPriorityToInt[priority1] >= wwaPriorityToInt[priority2];
}
// Useful for enumerating the WWA to WinJS priority boundaries. Calls callback
// with two parameters: a WinJS priority and the corresponding WWA priority.
// If callback returns a promise, waits for the promise to complete before
// calling callback again. Returns a promise which completes after the last
// call to callback has completed.
//
function forEachPriorityBoundary(callback) {
return WinJS.Promise.wrap().then(function () {
return callback(S.Priority.aboveNormal + 1, MSApp.HIGH);
}).then(function () {
return callback(S.Priority.aboveNormal, MSApp.NORMAL);
}).then(function () {
return callback(S.Priority.belowNormal, MSApp.NORMAL);
}).then(function () {
return callback(S.Priority.belowNormal - 1, MSApp.IDLE);
});
}
// When the WWA scheduler is disabled, same as forEachPriorityBoundary. When
// it is enabled, only test WWA high priority. We do this because when using
// lower priority jobs, we cannot guarantee that the WinJS scheduler will not
// yield to high priority platform jobs that are out of our control such as layout.
//
function forEachPriorityBoundaryLimited(callback) {
if (S._usingWwaScheduler) {
return callback(S.Priority.aboveNormal + 1, MSApp.HIGH);
} else {
return forEachPriorityBoundary(callback);
}
}
// Mimics the method that the scheduler uses for scheduling its pump with
// the platform. Useful for getting code to run in between calls to the
// scheduler pump to verify that the pump yields.
//
var scheduleWithHost = window.setImmediate ? window.setImmediate.bind(window) : function (callback) {
setTimeout(callback, 16);
};
function mimicSchedulePump(fn, wwaPriority) {
var ran = false;
var runner = function () {
if (!ran) {
ran = true;
fn();
}
};
if (S._usingWwaScheduler) {
MSApp.execAsyncAtPriority(runner, wwaPriority);
} else {
if (wwaPriority === MSApp.HIGH) {
setTimeout(runner, 0);
}
scheduleWithHost(runner);
}
}
// Useful for hooking the WWA scheduler's APIs which are on the MSApp object.
// When the WWA scheduler is enabled, calls fn and returns. Otherwise, provides
// the below semantics for hooking MSApp.
//
// This only affects the scheduler's private version of the MSApp object. Parameters:
// - hooks: an object mapping MSApp function names to hook functions. Hook functions
// are called instead of their MSApp counterparts. Each hook function has the
// following signature:
//
// hookFunction(originalFunction, originalArgs, ...)
// - originalFunction: the unhooked function
// - originalArgs: an Array containing the arguments with which originalFunction
// would have been called if it hadn't been hooked
// - ...: the arguments with which originalFunction would have been called
//
// If you'd like your hook function to have the same behavior as its MSApp
// counterpart, end it with this: return originalFunction.apply(this, originalArgs);
//
// - fn is the function that should be executed with the MSApp hooks in effect. If
// fn returns a promise, then the hooks will be removed when that promise completes.
// Otherwise, the hooks will be removed as soon as fn returns.
//
// - options supports a forceHooksInWwa flag which, when true, will cause the hooks
// to be applied even when the WWA scheduler is enabled.
//
// Returns the return value of fn.
//
function withMSAppHooks(hooks, fn, options?) {
options = options || {};
var forceHooksInWwa = options.forceHooksInWwa;
if (S._usingWwaScheduler && !forceHooksInWwa) {
return fn();
} else {
var originalMSApp = S._MSApp,
hookedMSApp = Object.create(originalMSApp),
result;
Object.keys(hooks).forEach(function (key) {
var originalFn = originalMSApp[key];
hookedMSApp[key] = function () {
var args = toArray(arguments),
allArgs = [originalFn, args].concat(args);
return hooks[key].apply(hookedMSApp, allArgs);
};
});
try {
S._MSApp = hookedMSApp;
result = fn();
} finally {
return WinJS.Promise.as(result).then(function (value) {
S._MSApp = originalMSApp;
return value;
}, function (error) {
S._MSApp = originalMSApp;
return WinJS.Promise.wrapError(error);
});
}
}
}
function verifyDump(description, expected) {
LiveUnit.Assert.areEqual(expected, S.retrieveState(), "S.retrieveState() returned unexpected string: " + description);
}
var origUsingWwaScheduler;
// @TODO: tests of .completed
// @TODO: tests for exception scenario - verify that the queue doesn't stall
// @TODO: tests for verifying that a yield occurs within a reasonable timeframe (how to test this under varying conditions)?
// @TODO: tests for illegal priorities - should clamp
// @TODO: tests for passing a non-function as a work item - should we eagerly throw or blow up later or ignore?
// @TODO: Test scenarios where pieces of the scheduler are re-entered. We can do this by doing an action in the 2nd list
// from within the context of an item in the 1st list:
// 1. User code that is called into from the scheduler:
// - work functions
// - drain request promise callbacks (success and cancelation handlers)
// 2. How can user code enter the scheduler?:
// - calling S.schedule
// - calling S.requestDrain
// - canceling a drain request
//
export class Scheduler {
setUp() {
origUsingWwaScheduler = S._usingWwaScheduler;
}
tearDown() {
S._usingWwaScheduler = origUsingWwaScheduler;
}
testBasic = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testJobMetaData = async(function () {
var count = 0;
var job = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
LiveUnit.Assert.areEqual(10, this.x, "incorrect thisArg");
LiveUnit.Assert.areEqual("TestJob", job.name, "incorrect job name");
LiveUnit.Assert.areEqual(winjs, job.owner, "incorrect job owner");
}, S.Priority.normal, { x: 10 }, "");
var winjs = S.createOwnerToken();
job.owner = winjs;
job.name = "TestJob";
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that a job info's properties throw when they are used after the job info
// object is no longer valid. A job info object becomes invalid as soon as the job's
// work function returns.
//
testJobInfoOutOfContext = async(function () {
var signal = new WinJS._Signal();
var count = 0;
function verifyThrowsWhenOutOfContext(work) {
S.schedule(function (jobInfo) {
WinJS.Promise.timeout().then(function () {
try {
work(jobInfo);
LiveUnit.Assert.fail("The work function should have thrown due to using jobInfo out of context");
} catch (error) {
LiveUnit.Assert.areEqual("WinJS.Utilities.Scheduler.JobInfoIsNoLongerValid", error.name);
count++;
}
});
});
}
function moreWork() {
LiveUnit.Assert.fail("This function shouldn't have run");
}
verifyThrowsWhenOutOfContext(function (jobInfo) {
jobInfo.job;
});
verifyThrowsWhenOutOfContext(function (jobInfo) {
jobInfo.shouldYield;
});
verifyThrowsWhenOutOfContext(function (jobInfo) {
jobInfo.setWork(moreWork);
});
verifyThrowsWhenOutOfContext(function (jobInfo) {
S.schedule(function () {
signal.complete();
});
jobInfo.setPromise(WinJS.Promise.timeout().then(function () {
return moreWork;
}));
});
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
});
});
testMultipleJobsAtSameLevel = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBasicPriorityLevels = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal);
S.schedule(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
}, S.Priority.idle);
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, S.Priority.normal);
S.schedule(function () {
LiveUnit.Assert.areEqual(5, count);
count++;
}, S.Priority.idle);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(6, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testAdvancedPriorityLevels = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal);
S.schedule(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
}, S.Priority.idle);
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.high - 1);
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, S.Priority.normal - 1);
S.schedule(function () {
LiveUnit.Assert.areEqual(5, count);
count++;
}, S.Priority.idle - 1);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(6, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBasicPausing = async(function () {
var count = 0;
var first = S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
first.resume();
});
first.pause();
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isTrue(first.completed);
});
});
testBasicYielding = async(function () {
var count = 0;
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// Since this is scheduled at the same priority as the yielding job in which
// it is scheduled it will run after.
//
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
LiveUnit.Assert.areEqual(1, count);
info.setWork(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testChangePriorityWhileYielding = async(function () {
var count = 0;
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// this should be executed after setWork of current job
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.high);
// make yourself high priority
info.job.priority = S.Priority.max;
info.setWork(function (info1) {
LiveUnit.Assert.areEqual(1, count);
count++;
// change priority to normal
info1.job.priority = S.Priority.normal;
info1.setWork(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
});
});
});
S.schedule(function (info) {
LiveUnit.Assert.areEqual(3, count);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(5, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testYieldingWithSetWork = async(function () {
var count = 0;
var iter = 0;
WinJS.Utilities.startLog();
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// Since this is scheduled at the same priority as the yielding job in which
// it is scheduled it will run after.
//
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.areEqual(10, iter)
count++;
});
LiveUnit.Assert.areEqual(1, count);
var work = function (jobInfo) {
if (iter < 10) {
iter++;
jobInfo.setWork(work);
}
}
info.setWork(work);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.areEqual(10, iter);
LiveUnit.Assert.isTrue(S._isEmpty);
WinJS.Utilities.stopLog();
});
});
testStopAfterExceptionWhileYielding = async(function () {
var count = 0;
var iter = 0;
WinJS.Utilities.startLog();
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// Since this is scheduled at the same priority as the yielding job in which
// it is scheduled it will run after.
//
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.areEqual(2, iter)
count++;
});
LiveUnit.Assert.areEqual(1, count);
var work = function (jobInfo) {
if (iter === 2) {
jobInfo.setWork(work);
throw new Error("Exception from yielding job");
}
if (iter < 10) {
// this shouldn't be hit after exception
if (iter >= 2)
LiveUnit.Assert.fail("This shouldn't have been hit");
iter++;
jobInfo.setWork(work);
}
}
info.setWork(work);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.areEqual(2, iter);
LiveUnit.Assert.isTrue(S._isEmpty);
WinJS.Utilities.stopLog();
});
}, [{ message: "Exception from yielding job" }]);
testBasicYieldingAndPauseTheYieldingJob = async(function () {
var count = 0;
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// Normally this would run after the yielded job since it is at the same priority level, however
// we are going to pause the yielding job so this will end up running first. We will then resume
// the job and we can see that it gets put back at the end of the queue.
//
S.schedule(function () {
j.resume();
LiveUnit.Assert.areEqual(1, count);
count++;
});
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
LiveUnit.Assert.areEqual(1, count);
// Pause the job which will mean this continuation won't run. Then resume it in the other job.
// We will verify that it is added to the end of the queue by adding two jobs, resuming in the
// first and verifying that the continuation runs in the second.
//
j.pause();
info.setWork(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
});
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isTrue(j.completed);
});
});
testBasicCancelation = async(function () {
var count = 0;
var j = S.schedule(function () {
LiveUnit.Assert.fail("Should not be here");
});
j.cancel();
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(0, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isFalse(j.completed);
});
});
testCancelationOnceQueueStartsRunning = async(function () {
var count = 0;
var j;
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
j.cancel();
});
j = S.schedule(function () {
LiveUnit.Assert.fail("Should not be here");
count++;
});
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isFalse(j.completed);
});
});
testNoOpAfterCancelation = async(function () {
var count = 0;
var j = S.schedule(function () {
LiveUnit.Assert.fail("Should not be here");
});
j.cancel();
j.resume();
j.pause();
j.cancel();
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(0, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isFalse(j.completed);
});
});
testShouldYieldAfterHighPriJobSchedule = async(function () {
var count = 0;
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// schedule high pri job and this should set shouldYield to true
var j1 = S.schedule(function (info1) {
LiveUnit.Assert.areEqual(1, count);
count++;
LiveUnit.Assert.isFalse(info1.shouldYield);
}, S.Priority.high);
LiveUnit.Assert.isTrue(info.shouldYield);
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testShouldYieldAfterLowPriJobSchedule = async(function () {
var count = 0;
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// schedule low pri job and this shouldn't change shouldYield
var j1 = S.schedule(function (info) {
LiveUnit.Assert.areEqual(1, count);
count++;
LiveUnit.Assert.isFalse(info.shouldYield);
});
LiveUnit.Assert.isFalse(info.shouldYield);
}, S.Priority.high);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testHighPriJobBetweenLowPriJobs = async(function () {
var count = 0;
var signal = new WinJS._Signal();
// schedule max job between two high pri jobs and verify max job runs after first high job
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// schedule max winjs job
S.schedule(function (info) {
LiveUnit.Assert.areEqual(1, count);
count++;
LiveUnit.Assert.isFalse(info.shouldYield);
}, S.Priority.max);
LiveUnit.Assert.isTrue(info.shouldYield);
}, S.Priority.high);
S.schedule(function (info) {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.high);
// schedule platform job but this would be running below winjs max priority
mimicSchedulePump(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
signal.complete();
}, "high");
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testHighPriJobBetweenLowPriYieldingJobs = async(function () {
var count = 0;
// schedule max winjs job and exhaust its time slice to yield to platform job
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// schedule high pri job and this should set shouldYield to true
var j1 = S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.max);
LiveUnit.Assert.isTrue(info.shouldYield);
// exhaust its time slice so that it would yield to platform
repeatForDuration(S._TIME_SLICE + 10);
}, S.Priority.high);
S.schedule(function (info) {
LiveUnit.Assert.areEqual(3, count);
count++;
}, S.Priority.high);
mimicSchedulePump(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, "high");
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testGetRightArgumentsInWorkFunction = async(function () {
var count = 0;
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
LiveUnit.Assert.areEqual("boolean", typeof info.shouldYield);
LiveUnit.Assert.areEqual(j, info.job);
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testShouldYieldReturnsTrueAfterSelfCancel = async(function () {
var count = 0;
var job = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
LiveUnit.Assert.isFalse(info.shouldYield);
info.job.cancel();
LiveUnit.Assert.isTrue(info.shouldYield);
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isFalse(job.completed);
});
});
testShouldYieldReturnsTrueAfterSelfPause = async(function () {
var count = 0;
var job = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
LiveUnit.Assert.isFalse(info.shouldYield);
// Note that pausing a job and then letting go of it has the same effect as
// canceling it since you no longer have the token with which to resume.
//
info.job.pause();
LiveUnit.Assert.isTrue(info.shouldYield);
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isTrue(job.completed);
});
});
testThrottling = async(function () {
var count = 0;
var job = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
var iters = 0;
while (!info.shouldYield) {
iters++;
}
// Should eventually reach here and complete. Failure mode is test timeout.
//
LiveUnit.Assert.isTrue(iters > 0);
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isTrue(job.completed);
});
});
testYieldingAndAlsoSchedulingHigherPriorityJob = async(function () {
var count = 0;
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// Since this is scheduled at a higher priority from the yielding job in which
// it is scheduled it will run before the yielded function
//
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.high);
LiveUnit.Assert.areEqual(1, count);
info.setWork(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testPriorityRelations = function () {
LiveUnit.Assert.isTrue(S.Priority.max > S.Priority.high);
LiveUnit.Assert.isTrue(S.Priority.high > S.Priority.aboveNormal);
LiveUnit.Assert.isTrue(S.Priority.aboveNormal > S.Priority.normal);
LiveUnit.Assert.isTrue(S.Priority.normal > S.Priority.belowNormal);
LiveUnit.Assert.isTrue(S.Priority.belowNormal > S.Priority.idle);
LiveUnit.Assert.isTrue(S.Priority.idle > S.Priority.min);
}
testReprioritization = async(function () {
var count = 0;
var j0 = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
var j1 = S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
var j2 = S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
// Should NOT affect ordering
j0.priority = j0.priority;
// Should move this to the back of the queue;
j1.priority = S.Priority.high;
j1.priority = S.Priority.normal;
// End result is we expect j0, j2, j1
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testReprioritizationBetweenStates = async(function () {
var count = 0;
var j0 = S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
var j1 = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
var j2 = S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
j1.pause();
j1.priority = S.Priority.high;
j1.resume();
// End result is we expect j1, j0, j2
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that the scheduler doesn't schedule its pump while pumping.
// Instead, it schedules its pump immediately before yielding if necessary.
// Regression test for WinBlue#143089.
//
testDontSchedulePumpWhilePumping = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
// After running this job, the WinJS scheduler should yield
// giving the "mimicSchedulePump" callback a chance to run.
//
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
repeatForDuration(S._TIME_SLICE + 10);
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
}, S.Priority.high);
mimicSchedulePump(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, MSApp.HIGH);
}, S.Priority.normal);
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(5, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that execHigh:
// - calls its callback thru execAtPriority with priority=MSApp.HIGH
// exactly 1 time
// - returns the return value of its callback
//
testExecHigh = async(function () {
S._usingWwaScheduler = false;
function return4() {
LiveUnit.Assert.areEqual(0, return4Called);
return4Called++;
return 4;
}
var return4Called = 0;
var execAtPriorityCalled = 0;
return withMSAppHooks({
execAtPriority: function (origFn, args, callback, priority) {
if (callback === return4) {
LiveUnit.Assert.areEqual(0, execAtPriorityCalled);
execAtPriorityCalled++;
LiveUnit.Assert.areEqual(MSApp.HIGH, priority);
}
return origFn.apply(this, args);
}
}, function () {
var returnValue = S.execHigh(return4);
LiveUnit.Assert.areEqual(4, returnValue,
"execHigh should have returned the return value of the callback");
LiveUnit.Assert.areEqual(1, return4Called, "return4 should have been called exactly 1 time");
LiveUnit.Assert.areEqual(1, execAtPriorityCalled,
"execAtPriority should have been called exactly 1 time for return4");
LiveUnit.Assert.isTrue(S._isEmpty);
})
});
// Verifies that scheduled jobs get executed thru execAtPriority at the
// appropriate priority. The workFunction and execAtPriority should get
// called exactly 1 time.
//
testPriorityContextOfScheduledJob = async(function () {
S._usingWwaScheduler = false;
function test(winjsPriority, wwaPriority) {
function workFunction() {
LiveUnit.Assert.areEqual(0, workFunctionCalled);
workFunctionCalled++;
}
var workFunctionCalled = 0;
var execAtPriorityCalled = 0;
return withMSAppHooks({
execAtPriority: function (origFn, args, callback, priority) {
if (execAtPriorityCalled === 0) {
// This should be the workFunction
//
LiveUnit.Assert.areEqual(0, execAtPriorityCalled);
execAtPriorityCalled++;
LiveUnit.Assert.areEqual(wwaPriority, priority);
} else {
// This should be the function scheduled at Priority.min
// which does the final test verification.
//
LiveUnit.Assert.areEqual(1, execAtPriorityCalled);
execAtPriorityCalled++;
LiveUnit.Assert.areEqual(MSApp.IDLE, priority);
}
return origFn.apply(this, args);
}
}, function () {
S.schedule(workFunction, winjsPriority);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, workFunctionCalled);
LiveUnit.Assert.areEqual(2, execAtPriorityCalled);
LiveUnit.Assert.isTrue(S._isEmpty);
});
})
}
return forEachPriorityBoundary(test);
});
// Verifies that when the WinJS scheduler and the WWA scheduler have equally
// important jobs to run, the WinJS scheduler does not yield to the platform.
//
testDontYieldToPlatformJob = async(function () {
// When converted to a WWA priority, priorityOfWinjsJob
// should equal priorityOfPlatformJob.
//
function test(priorityOfWinjsJob, priorityOfPlatformJob) {
var platformJobQueued = false;
var signal = new WinJS._Signal();
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
return platformJobQueued && isEqualOrHigherWwaPriority(priorityOfPlatformJob, priority);
}
}, function () {
var count = 0;
// This outer job is needed to ensure that the WinJS scheduler
// gets to run before the platform job.
//
S.schedule(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, priorityOfWinjsJob);
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(2, count);
count++;
signal.complete();
}, priorityOfPlatformJob);
platformJobQueued = true;
LiveUnit.Assert.areEqual(0, count);
count++;
}, priorityOfWinjsJob);
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
}
return forEachPriorityBoundaryLimited(test);
});
// Same as testDontYieldToPlatformJob but scheduler is run due to a drain
// started from outside of the scheduler.
//
testDontYieldToPlatformJobInExternalDrain = async(function () {
// When converted to a WWA priority, priorityOfWinjsJob
// should equal priorityOfPlatformJob.
//
function test(priorityOfWinjsJob, priorityOfPlatformJob) {
var platformJobQueued = false;
var signal = new WinJS._Signal();
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
return platformJobQueued && isEqualOrHigherWwaPriority(priorityOfPlatformJob, priority);
}
}, function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, priorityOfWinjsJob);
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(3, count);
count++;
signal.complete();
}, priorityOfPlatformJob);
platformJobQueued = true;
S.requestDrain(priorityOfWinjsJob).then(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
}
return forEachPriorityBoundaryLimited(test);
});
// Same as testDontYieldToPlatformJob but scheduler is run due to a drain
// started from inside of a job.
//
testDontYieldToPlatformJobInInternalDrain = async(function () {
// When converted to a WWA priority, priorityOfWinjsJob
// should equal priorityOfPlatformJob.
//
function test(priorityOfWinjsJob, priorityOfPlatformJob) {
var platformJobQueued = false;
var signal = new WinJS._Signal();
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
return platformJobQueued && isEqualOrHigherWwaPriority(priorityOfPlatformJob, priority);
}
}, function () {
var count = 0;
S.schedule(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, priorityOfWinjsJob);
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(3, count);
count++;
signal.complete();
}, priorityOfPlatformJob);
platformJobQueued = true;
S.requestDrain(priorityOfWinjsJob).then(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, priorityOfWinjsJob);
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
}
return forEachPriorityBoundaryLimited(test);
});
// Verifies that when a platform job is scheduled within a WinJS job at
// the same priority as the WinJS job, the WinJS scheduler does not yield.
//
testSchedulePlatformJobInJobDontYield = async(function () {
// When converted to a WWA priority, priorityOfWinjsJob
// should equal priorityOfPlatformJob.
//
function test(priorityOfWinjsJob, priorityOfPlatformJob) {
var platformJobQueued = false;
var signal = new WinJS._Signal();
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
return platformJobQueued && isEqualOrHigherWwaPriority(priorityOfPlatformJob, priority);
}
}, function () {
var count = 0;
// A WinJS job that schedules a platform job at the same priority
//
S.schedule(function (info) {
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(3, count);
count++;
signal.complete();
}, priorityOfPlatformJob);
platformJobQueued = true;
LiveUnit.Assert.isFalse(info.shouldYield);
LiveUnit.Assert.areEqual(1, count);
count++;
}, priorityOfWinjsJob);
// A WinJS job that should run before the platform job
//
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, priorityOfWinjsJob);
LiveUnit.Assert.areEqual(0, count);
count++;
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
}
return forEachPriorityBoundaryLimited(test);
});
// Verifies that the WinJS scheduler yields to the platform when the WWA
// scheduler has a more important job to run.
//
testYieldToPlatformJob = async(function () {
var platformJobQueued = false;
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
// The platform job is high priority so as long as it is queued,
// this function should return true.
//
return platformJobQueued;
}
}, function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal);
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(1, count);
count++;
}, MSApp.HIGH);
platformJobQueued = true;
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
});
// Same as testYieldToPlatformJob but scheduler is run due to a drain
// started from outside of the scheduler.
//
testYieldToPlatformJobInExternalDrain = async(function () {
var platformJobQueued = false;
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
// The platform job is high priority so as long as it is queued,
// this function should return true.
//
return platformJobQueued;
}
}, function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal);
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(1, count);
count++;
}, MSApp.HIGH);
platformJobQueued = true;
S.requestDrain(S.Priority.normal).then(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
});
// Same as testYieldToPlatformJob but scheduler is run due to a drain
// started from inside of a job.
//
testYieldToPlatformJobInInternalDrain = async(function () {
var platformJobQueued = false;
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
// The platform job is high priority so as long as it is queued,
// this function should return true.
//
return platformJobQueued;
}
}, function () {
var count = 0;
S.schedule(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal);
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(1, count);
count++;
}, MSApp.HIGH);
platformJobQueued = true;
S.requestDrain(S.Priority.normal).then(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
});
// Verifies that when a platform job is scheduled within a WinJS job at
// a higher priority than the WinJS job, the WinJS scheduler yields.
//
testSchedulePlatformJobInJobYield = async(function () {
var platformJobQueued = false;
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
// The platform job is high priority so as long as it is queued,
// this function should return true.
//
return platformJobQueued;
}
}, function () {
var count = 0;
// A WinJS job that schedules a platform job at a higher priority
//
S.schedule(function (info) {
LiveUnit.Assert.isFalse(info.shouldYield);
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(2, count);
count++;
}, MSApp.HIGH);
platformJobQueued = true;
LiveUnit.Assert.isTrue(info.shouldYield);
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.normal);
// A WinJS job that should run after the platform job
//
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, S.Priority.normal);
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
});
// Verifies that when the WinJS scheduler and the WWA scheduler start off with
// equally important jobs but the high priority WinJS job gets canceled, the
// WinJS scheduler yields to the platform.
//
testYieldToPlatformJobWithCancel = async(function () {
var platformJobQueued = false;
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
// The platform job is high priority so as long as it is queued,
// this function should return true.
//
return platformJobQueued;
}
}, function () {
var count = 0;
var jobHigh = S.schedule(function () {
LiveUnit.Assert.fail("This job shouldn't run due to cancellation");
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal);
mimicSchedulePump(function () {
platformJobQueued = false;
LiveUnit.Assert.areEqual(1, count);
count++;
}, MSApp.HIGH);
platformJobQueued = true;
jobHigh.cancel();
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
});
// Verifies that when the WinJS scheduler and the WWA scheduler each have
// multiple jobs at different priorities, they get run in the proper order.
//
testYieldToPlatformJobMultiple = async(function () {
var priorityOfPlatformJob;
var signal = new WinJS._Signal();
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
// The platform job is high priority so as long as it is queued,
// this function should return true.
//
return priorityOfPlatformJob && isEqualOrHigherWwaPriority(priorityOfPlatformJob, priority);
}
}, function () {
var count = 0;
// We cannot guarantee the relative ordering of WinJS jobs and WWA jobs
// of equal priority. Consequently, we just verify that jobs at different
// priorities run in the correct order.
//
function assertHigh() {
LiveUnit.Assert.isTrue(count >= 1 && count <= 2);
}
function assertNormal() {
LiveUnit.Assert.isTrue(count >= 3 && count <= 5);
}
function assertIdle() {
LiveUnit.Assert.isTrue(count >= 6 && count <= 8);
}
// Maps to MSApp.HIGH
//
S.schedule(function () {
assertHigh();
count++;
}, S.Priority.aboveNormal + 1);
// These map to MSApp.NORMAL
//
S.schedule(function () {
assertNormal();
count++;
}, S.Priority.aboveNormal);
S.schedule(function () {
assertNormal();
count++;
}, S.Priority.belowNormal);
// These map to MSApp.IDLE
//
S.schedule(function () {
assertIdle();
count++;
if (count === 9) {
signal.complete();
}
}, S.Priority.belowNormal - 1);
S.schedule(function () {
assertIdle();
count++;
if (count === 9) {
signal.complete();
}
}, S.Priority.min);
// 1 platform job for each WWA priority. Rather than scheduling all of the
// platform jobs up front, each platform job schedules the next platform
// job. This is to ensure that the WinJS scheduler's pump runs in
// between platform jobs.
//
function scheduleHighPlatformJob() {
mimicSchedulePump(function () {
scheduleNormalPlatformJob();
priorityOfPlatformJob = MSApp.NORMAL;
assertHigh();
count++;
}, MSApp.HIGH);
}
function scheduleNormalPlatformJob() {
mimicSchedulePump(function () {
scheduleIdlePlatformJob();
priorityOfPlatformJob = MSApp.IDLE;
assertNormal();
count++;
}, MSApp.NORMAL);
}
function scheduleIdlePlatformJob() {
mimicSchedulePump(function () {
priorityOfPlatformJob = null;
assertIdle();
count++;
if (count === 9) {
signal.complete();
}
}, MSApp.IDLE);
}
scheduleHighPlatformJob();
priorityOfPlatformJob = MSApp.HIGH;
LiveUnit.Assert.areEqual(0, count);
count++;
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(9, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
});
//
// Scheduler.createOwnerToken and cancelAll tests
//
// Verifies cancel by owner when called from outside of any job
//
testCancelByOwnerOutsideJob = async(function () {
var count = 0;
var runMeToken = S.createOwnerToken();
var cancelMeToken = S.createOwnerToken();
var j0 = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
j0.owner = runMeToken;
var j1 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j1.owner = cancelMeToken;
var j2 = S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
j2.owner = runMeToken;
var j3 = S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.idle);
j3.owner = runMeToken;
var j4 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j4.owner = cancelMeToken;
cancelMeToken.cancelAll();
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies cancel by owner when called from within a job that does not belong to owner
//
testCancelByOwnerInsideUnownedJob = async(function () {
var count = 0;
var runMeToken = S.createOwnerToken();
var cancelMeToken = S.createOwnerToken();
var j0 = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
LiveUnit.Assert.isFalse(info.shouldYield);
cancelMeToken.cancelAll();
LiveUnit.Assert.isFalse(info.shouldYield, "Job should not have been canceled and shouldn't have to yield");
});
j0.owner = runMeToken;
var j1 = S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
j1.owner = runMeToken;
var j2 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j2.owner = cancelMeToken;
var j3 = S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
j3.owner = runMeToken;
var j4 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j4.owner = cancelMeToken;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies cancel by owner when called from within a job that belongs to owner. The running
// job should be asked to yield.
//
testCancelByOwnerInsideOwnedJob = async(function () {
var count = 0;
var runMeToken = S.createOwnerToken();
var cancelMeToken = S.createOwnerToken();
var j0 = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
LiveUnit.Assert.isFalse(info.shouldYield);
cancelMeToken.cancelAll();
LiveUnit.Assert.isTrue(info.shouldYield, "Job should have been canceled and should be asked to yield");
});
j0.owner = cancelMeToken;
var j1 = S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
j1.owner = runMeToken;
var j2 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j2.owner = cancelMeToken;
var j3 = S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
j3.owner = runMeToken;
var j4 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j4.owner = cancelMeToken;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testCancelMoreThanOneJobOfOwner = async(function () {
var count = 0;
var rneela = S.createOwnerToken();
WinJS.Utilities.startLog();
var j1 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j1.owner = rneela;
var j2 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j2.owner = rneela;
var j3 = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setWork(function () {
rneela.cancelAll();
});
}, S.Priority.high);
j3.owner = rneela;
var j4 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
}, S.Priority.high);
j4.owner = rneela;
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.idle);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
WinJS.Utilities.stopLog();
});
});
testCancelOwnerWithoutJobs = async(function () {
var emptyOwner = S.createOwnerToken();
var rneela = S.createOwnerToken();
var j1 = S.schedule(function () { }); // job with no owner
var j2 = S.schedule(function () { });
j2.owner = rneela;
var j3 = S.schedule(function (info) {
info.setWork(function () { });
}, S.Priority.high);
j3.owner = rneela;
var j4 = S.schedule(function () {
}, S.Priority.high);
S.schedule(function () {
}, S.Priority.idle);
// This owner doesn't have any jobs associated with it so
// no jobs should be canceled.
//
emptyOwner.cancelAll();
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isTrue(j1.completed && j2.completed && j3.completed && j4.completed);
});
});
testScheduleAfterCancelWithSameOwnerToken = async(function () {
var count = 0;
var cancelMeToken = S.createOwnerToken();
var j1 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j1.owner = cancelMeToken;
var j2 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j2.owner = cancelMeToken;
cancelMeToken.cancelAll();
j1 = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
j1.owner = cancelMeToken;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testCancelByOwnerOnJobs = async(function () {
var count = 0;
var cancelMeToken = S.createOwnerToken();
var j1 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j1.owner = cancelMeToken;
j1.cancel();
var j2 = S.schedule(function () {
LiveUnit.Assert.fail("This job should have been canceled");
});
j2.owner = cancelMeToken;
j2.pause();
//TODO: add blocked job too
cancelMeToken.cancelAll();
// verify resumming cancled jobs
j2.resume();
j1.resume();
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(0, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
//
// requestDrain tests
// An external request drain is one that is called from outside of any scheduler jobs.
// An internal request drain is one that is called from within a scheduler job.
//
// Verifies that:
// - requestDrain is asyncronous when called from outside the scheduler
//
testExternalDrain = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.high);
S.requestDrain(S.Priority.high).then(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testExternalDrainWithDefaultPriority = async(function () {
var count = 0;
var s = new WinJS._Signal();
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.min);
// Default drain priority should be S.Priority.min
//
S.requestDrain().then(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
s.complete();
});
LiveUnit.Assert.areEqual(0, count);
count++;
return s.promise.then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testInternalDrain = async(function () {
var count = 0;
S.schedule(function () {
S.requestDrain(S.Priority.normal).then(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
});
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that a drain request made from outside of the scheduler completes
// asynchronously when there aren't any jobs scheduled.
//
testOutsideSchedulerDrainWithEmptyQueue = async(function () {
var count = 0;
var signal = new WinJS._Signal();
S.requestDrain(S.Priority.aboveNormal).then(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
signal.complete();
});
LiveUnit.Assert.areEqual(0, count);
count++;
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Like testOutsideSchedulerDrainWithEmptyQueue but there are jobs scheduled
// that have a lower priority than the drain request.
//
testOutsideSchedulerNoJobsToDrain = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal);
S.requestDrain(S.Priority.aboveNormal).then(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that making a drain request and then scheduling work to
// be completed within that drain request causes the work to be
// executed before completing the drain request.
//
testOutsideSchedulerDrainThenSchedule = async(function () {
var count = 0;
S.requestDrain(S.Priority.aboveNormal).then(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.aboveNormal);
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that a drain request made from within the scheduler completes
// asynchronously when there aren't any jobs scheduled.
//
testWithinSchedulerDrainWithEmptyQueue = async(function () {
function test(drainPriority) {
var count = 0;
var signal = new WinJS._Signal();
S.schedule(function () {
S.requestDrain(drainPriority).then(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
signal.complete();
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.normal);
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
}
// Test with drain request priorities that are higher than, equal to, and
// lower than that of the job from within which the drain request is made.
//
return WinJS.Promise.wrap().then(function () {
return test(S.Priority.aboveNormal);
}).then(function () {
return test(S.Priority.normal);
}).then(function () {
return test(S.Priority.belowNormal);
});
});
// Like testWithinSchedulerDrainWithEmptyQueue but there are jobs scheduled
// that have a lower priority than the drain request.
//
testWithinSchedulerNoJobsToDrain = async(function () {
var count = 0;
S.schedule(function () {
S.requestDrain(S.Priority.aboveNormal).then(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.normal);
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that making a drain request and then scheduling work to
// be completed within that drain request causes the work to be
// executed before completing the drain request.
//
testWithinSchedulerDrainThenSchedule = async(function () {
function test(drainPriority) {
var count = 0;
var signal = new WinJS._Signal();
S.schedule(function () {
S.requestDrain(drainPriority).then(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
signal.complete();
});
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, drainPriority);
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.normal);
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
}
// Test with drain request priorities that are higher than, equal to, and
// lower than that of the job from within which the drain request is made.
//
return WinJS.Promise.wrap().then(function () {
return test(S.Priority.aboveNormal);
}).then(function () {
return test(S.Priority.normal);
}).then(function () {
return test(S.Priority.belowNormal);
});
});
// Verifies that the scheduler's yielding policy uses an infinite timeslice
// when in drain mode.
//
testDrainNoTimeSlice = async(function () {
var count = 0;
S.schedule(function (info) {
LiveUnit.Assert.areEqual(1, count);
count++;
repeatForDuration(S._TIME_SLICE + 10, function () { LiveUnit.Assert.isFalse(info.shouldYield); });
}, S.Priority.high);
S.schedule(function (info) {
LiveUnit.Assert.areEqual(2, count);
count++;
repeatForDuration(S._TIME_SLICE + 10, function () { LiveUnit.Assert.isFalse(info.shouldYield); });
}, S.Priority.high);
S.schedule(function (info) {
LiveUnit.Assert.areEqual(3, count);
count++;
repeatForDuration(S._TIME_SLICE + 10, function () { LiveUnit.Assert.isFalse(info.shouldYield); });
}, S.Priority.high);
S.requestDrain(S.Priority.high);
mimicSchedulePump(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
}, MSApp.HIGH);
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(5, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that the scheduler yielding to high proirity job
// when in drain mode.
//
testYeildingInDrain = async(function () {
var count = 0;
var h1 = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
repeatForDuration(S._TIME_SLICE + 10, function () { LiveUnit.Assert.isFalse(info.shouldYield); });
h2.cancel();
// schedule max job
var max = S.schedule(function (info) {
LiveUnit.Assert.areEqual(1, count);
count++;
repeatForDuration(S._TIME_SLICE + 10, function () { LiveUnit.Assert.isFalse(info.shouldYield); });
}, S.Priority.max);
}, S.Priority.high);
var h2 = S.schedule(function (info) {
LiveUnit.Assert.fail("This job should have been canceled");
}, S.Priority.high);
var h3 = S.schedule(function (info) {
LiveUnit.Assert.areEqual(2, count);
count++;
repeatForDuration(S._TIME_SLICE + 10, function () { LiveUnit.Assert.isFalse(info.shouldYield); });
}, S.Priority.high);
S.requestDrain(S.Priority.high);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that the scheduler's yielding policy once again uses a finite
// timeslice when the drain request is canceled.
//
testDrainNoTimeSliceWithCancel = async(function () {
var count = 0,
drainRequest;
S.schedule(function () {
S.schedule(function (info) {
mimicSchedulePump(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, MSApp.HIGH);
LiveUnit.Assert.areEqual(1, count);
count++;
repeatForDuration(S._TIME_SLICE + 10, function () { LiveUnit.Assert.isFalse(info.shouldYield); });
}, S.Priority.high);
// Cancels the drain request moving the scheduler out of drain mode.
// The pump should yield after this job.
//
S.schedule(function (info) {
LiveUnit.Assert.areEqual(2, count);
count++;
drainRequest.cancel();
while (!info.shouldYield) {
// Scheduler is in normal mode so shouldYield===true when timeslice is exhausted
//
}
}, S.Priority.high);
S.schedule(function (info) {
// Verifies that the pump yields after this job.
//
mimicSchedulePump(function () {
LiveUnit.Assert.areEqual(5, count);
count++;
}, MSApp.HIGH);
LiveUnit.Assert.areEqual(4, count);
count++;
while (!info.shouldYield) {
// Scheduler is in normal mode so shouldYield===true when timeslice is exhausted
//
}
}, S.Priority.high);
drainRequest = S.requestDrain(S.Priority.high);
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(6, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that if the last drain request notification handler (i.e. it moves
// the scheduler out of drain mode) is slow, the scheduler properly
// reschedules itself to complete the rest of its jobs.
//
testDrainSlowHandler = async(function () {
var count = 0,
drainRequest;
// Requests a normal drain with a slow drain request notification handler
//
S.schedule(function () {
drainRequest = S.requestDrain(S.Priority.aboveNormal).then(function () {
repeatForDuration(S._TIME_SLICE + 10);
LiveUnit.Assert.areEqual(1, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that when there are multiple drain requests, the drain request promises
// are completed in FIFO order (even when there aren't any jobs associated with
// the drain request).
//
testDrainMultiple = async(function () {
var count = 0;
S.schedule(function () {
S.requestDrain(S.Priority.aboveNormal).then(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
S.requestDrain(S.Priority.high).then(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
});
S.requestDrain(S.Priority.normal).then(function () {
LiveUnit.Assert.areEqual(5, count);
count++;
});
S.requestDrain(S.Priority.high).then(function () {
LiveUnit.Assert.areEqual(6, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
}, S.Priority.normal);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(7, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testDrainCancelation = async(function () {
var count = 0,
drainRequest;
// Requests a high drain
//
S.schedule(function () {
drainRequest = S.requestDrain(S.Priority.high).then(null, function (error) {
LiveUnit.Assert.areEqual("Canceled", error.name, "Drain promise should have been canceled");
LiveUnit.Assert.areEqual(2, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
// Cancels the drain request
//
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
drainRequest.cancel();
}, S.Priority.high);
return schedulePromise(S.Priority.high).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that canceling one drain request doesn't cancel other drain requests
// at the same priority.
//
testDrainMultipleAtSamePriWithCancelation = async(function () {
var count = 0,
drainNormal1,
drainNormal2;
// Requests 2 normal draines
//
S.schedule(function () {
drainNormal1 = S.requestDrain(S.Priority.normal).then(null, function (error) {
LiveUnit.Assert.areEqual("Canceled", error.name, "Drain promise should have been canceled");
LiveUnit.Assert.areEqual(2, count);
count++;
});
drainNormal2 = S.requestDrain(S.Priority.normal).then(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
// Cancels 1 normal drain
//
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
drainNormal1.cancel();
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, S.Priority.normal);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(5, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that canceling one drain request doesn't cancel other drain requests
// at lower priorities.
// Specifically, verifies that when you request a high drain and a normal drain
// and cancel the normal drain, the high drain still happens.
//
testDrainMultipleAtDiffPrisWithCancelation = async(function () {
var count = 0,
drainHigh,
drainNormal;
// Requests high and normal draines
//
S.schedule(function () {
drainHigh = S.requestDrain(S.Priority.high).then(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
});
drainNormal = S.requestDrain(S.Priority.normal).then(null, function (error) {
LiveUnit.Assert.areEqual("Canceled", error.name, "Drain promise should have been canceled");
LiveUnit.Assert.areEqual(2, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
// Cancels the normal drain
//
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
drainNormal.cancel();
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
drainNormal.cancel();
}, S.Priority.high);
return schedulePromise(S.Priority.normal).then(function () {
LiveUnit.Assert.areEqual(5, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that scheduling a job in a drain request notification handler
// works properly. Specifically, this test has an aboveNormal and a normal
// drain request handler. Without the side effects of the drain request
// handlers, the code flow should be:
// - aboveNormal drain request handler
// - normal drain request handler
// - belowNormal job
// However, the aboveNormal drain request handler schedules an aboveNormal
// job so the code flow should acutally be:
// - aboveNormal drain request handler (schedules an aboveNormal job)
// - aboveNormal job
// - normal drain request handler
// - belowNormal job
testDrainScheduleInNotify = async(function () {
var count = 0,
drainNormal,
drainAboveNormal;
// Requests aboveNormal and normal draines
//
S.schedule(function () {
drainAboveNormal = S.requestDrain(S.Priority.aboveNormal).then(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.aboveNormal);
LiveUnit.Assert.areEqual(1, count);
count++;
});
drainNormal = S.requestDrain(S.Priority.normal).then(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
});
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
}, S.Priority.belowNormal);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(5, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
//
// tests for exception scenario - verify that the queue doesn't stall
//
// verify scheduler doesn't stall after job throws exception
testWithException = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
throw new Error("error in HIGH 1");
count++;
}, S.Priority.high, null, "HIGH 1");
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
throw new Error("error in NORMAL 1");
count++;
}, S.Priority.normal, null, "NORMAL 1");
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high, null, "HIGH 2");
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, S.Priority.normal, null, "NORMAL 2");
LiveUnit.Assert.areEqual(0, count);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
}, [{ message: "error in HIGH 1" }, { message: "error in NORMAL 1" }]);
// verify there for no errors when job that was failed with exception is resumed
testResumeJobAfterException = async(function () {
var count = 0;
var deadJob = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
throw new Error("error in HIGH 1");
count++;
}, S.Priority.high, null, "HIGH 1");
S.schedule(function () {
deadJob.pause();
deadJob.resume(); // this shouldn't throw exception
LiveUnit.Assert.areEqual(0, count);
count++;
}, S.Priority.high, null, "HIGH 2");
LiveUnit.Assert.areEqual(0, count);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
}, [{ message: "error in HIGH 1" }]);
testCurrentPriority = async(function () {
var count = 0;
LiveUnit.Assert.areEqual(S.Priority.normal, S.currentPriority);
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
LiveUnit.Assert.areEqual(S.Priority.normal, S.currentPriority);
});
S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
LiveUnit.Assert.areEqual(S.Priority.high, S.currentPriority);
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
LiveUnit.Assert.areEqual(S.Priority.aboveNormal, S.currentPriority);
}, S.Priority.aboveNormal);
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
LiveUnit.Assert.areEqual(S.Priority.normal, S.currentPriority);
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlocking = async(function () {
var count = 0;
var s = new WinJS._Signal();
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setPromise(Promise.timeout().then(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
s.complete();
});
return function () {
LiveUnit.Assert.areEqual(1, count);
count++;
};
}));
});
return s.promise.then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockingHighPriJob = async(function () {
var count = 0;
var s = new WinJS._Signal();
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// block high pri job for 2 seconds to allow execute low pri jobs
info.setPromise(Promise.timeout(2000).then(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
s.complete();
});
return function () {
LiveUnit.Assert.areEqual(2, count);
count++;
};
}));
}, S.Priority.high);
// this should be executed before high pri blocked job
S.schedule(function (info) {
LiveUnit.Assert.areEqual(1, count);
count++;
});
return s.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockingYieldsForHighJob = async(function () {
var count = 0;
var s = new WinJS._Signal();
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setPromise(Promise.timeout().then(function () {
// this job should be run before blocked
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.high);
return function () {
LiveUnit.Assert.areEqual(3, count);
count++;
s.complete();
};
}));
});
S.schedule(function (info) {
LiveUnit.Assert.areEqual(1, count);
count++;
});
return s.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockingCompletes = async(function () {
var count = 0;
var s = new WinJS._Signal();
S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
// promise doesn't return anything
info.setPromise(Promise.timeout().then(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
s.complete();
});
}));
});
return s.promise.then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockingCanceled = async(function () {
var count = 0;
var j = S.schedule(function (info) {
info.setPromise(new Promise(function () { /* promise will never complete */ }, function () {
// Assert that we get cancel called on this promise when we cancel the job which is blocked
//
LiveUnit.Assert.areEqual(1, count);
count++;
}));
});
var j2 = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
j.cancel();
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.isTrue(j2.completed);
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockAfterCancelation = async(function () {
var count = 0;
var j = S.schedule(function (info) {
j.cancel();
count++;
info.setPromise(Promise.timeout().then(function () {
return function () {
LiveUnit.Assert.fail("Should not be here");
}
}));
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, count);
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.isFalse(j.completed);
});
});
testBlockedThrowsException = async(function () {
WinJS.Utilities.startLog();
var count = 0;
var s = new WinJS._Signal();
var j = S.schedule(function (info) {
info.setPromise(Promise.timeout().then(function () {
count++;
throw new Error("error from blocked");
}).then(null, function (e) { s.complete(); }));
});
var j2 = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
return s.promise.then(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.isTrue(j2.completed);
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty);
WinJS.Utilities.stopLog();
});
});
testCancelThenBlock = async(function () {
var count = 0;
WinJS.Utilities.startLog();
var j = S.schedule(function (info) {
j.cancel();
info.setPromise(new Promise(function () { /* promise will never complete */ }, function () {
// Assert that we get cancel called on this promise when we cancel the job which is blocked
//
LiveUnit.Assert.areEqual(0, count);
count++;
}));
});
var j2 = S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.isTrue(j2.completed);
LiveUnit.Assert.isTrue(S._isEmpty);
WinJS.Utilities.stopLog();
});
});
testBlockingPausedSelf = async(function () {
var count = 0;
var s = new WinJS._Signal();
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setPromise(WinJS.Promise.timeout().then(function () {
return function () {
LiveUnit.Assert.areEqual(2, count);
count++;
// Need to complete async so that we can test the "completed" bit of this job
//
WinJS.Promise.timeout().then(function () {
s.complete();
});
}
}));
// We pause the job before the promise is completed. This means that the next scheduled job should
// run which can then resume the job.
//
j.pause();
});
var j2 = S.schedule(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.areEqual(1, count);
count++;
j.resume();
});
return s.promise.then(function () {
LiveUnit.Assert.isTrue(j.completed);
LiveUnit.Assert.isTrue(j2.completed);
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockedPausedThenCancel = async(function () {
var count = 0;
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setPromise(new Promise(function () { }, function (e) {
LiveUnit.Assert.areEqual(2, count);
count++;
}));
j.pause();
});
var j2 = S.schedule(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.areEqual(1, count);
count++;
j.cancel();
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.isTrue(j2.completed);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockingPausedByOtherJob = async(function () {
var count = 0;
var s = new WinJS._Signal();
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setPromise(WinJS.Promise.timeout().then(function () {
return function () {
LiveUnit.Assert.areEqual(3, count);
count++;
// Need to complete async so that we can test the "completed" bit of this job
//
WinJS.Promise.timeout().then(function () {
s.complete();
});
}
}));
});
S.schedule(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.areEqual(1, count);
count++;
// We pause the job before the promise is completed. This means that the next scheduled job should
// run which can then resume the job.
//
j.pause();
});
WinJS.Promise.timeout().then(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
j.resume();
});
});
return s.promise.then(function () {
LiveUnit.Assert.isTrue(j.completed);
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockingPausedByOtherJobWithNoPromise = async(function () {
var count = 0;
var s = new WinJS._Signal();
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setPromise(WinJS.Promise.timeout().then(function () {
// Need to complete async so that we can test the "completed" bit of this job
WinJS.Promise.timeout().then(function () {
s.complete();
});
return; // returning no promise should lead to job complete
}));
});
S.schedule(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.areEqual(1, count);
count++;
// We pause the job before the promise is completed. This means that the next scheduled job should
// run which can then resume the job.
//
j.pause();
});
WinJS.Promise.timeout().then(function () {
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
j.resume();
});
});
return s.promise.then(function () {
LiveUnit.Assert.isTrue(j.completed);
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testBlockingPausedSelfWithSyncPromise = async(function () {
var count = 0;
var s = new WinJS._Signal();
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setPromise(WinJS.Promise.wrap().then(function () {
return function () {
LiveUnit.Assert.areEqual(2, count);
count++;
// Need to complete async so that we can test the "completed" bit of this job
//
WinJS.Promise.timeout().then(function () {
s.complete();
});
}
}));
// We pause the job before the promise is completed. This means that the next scheduled job should
// run which can then resume the job.
//
j.pause();
});
WinJS.Promise.timeout().then(function () {
S.schedule(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.areEqual(1, count);
count++;
j.resume();
});
});
return s.promise.then(function () {
LiveUnit.Assert.isTrue(j.completed);
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
testCancelPausedBlockedJobByOtherJob = async(function () {
var count = 0;
WinJS.Utilities.startLog();
var s = new WinJS._Signal();
var complete;
var j = S.schedule(function (info) {
LiveUnit.Assert.areEqual(0, count);
count++;
info.setPromise(new WinJS.Promise(
function (c) {
complete = c;
}, function () {
complete();
s.complete();
}));
LiveUnit.Assert.isFalse(S._isEmpty, "Job queue shouldn't be empty");
});
S.schedule(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.areEqual(1, count);
count++;
WinJS.Promise.timeout(500).then(function () {
j.cancel();
});
});
return s.promise.then(function () {
LiveUnit.Assert.isFalse(j.completed);
LiveUnit.Assert.areEqual(2, count);
LiveUnit.Assert.isTrue(S._isEmpty, "Job queue should be empty");
WinJS.Utilities.stopLog();
});
});
//
// Promise helper tests
//
// Verifies that each promise helper:
// - executes its promise under the correct priority context
// - completes its promise with the correct value
//
testPromiseHelperPriorityContexts = async(function () {
var count = 0;
function forEachPromiseHelper(fn) {
[
{ f: S.schedulePromiseHigh.bind(S), p: S.Priority.high },
{ f: S.schedulePromiseAboveNormal.bind(S), p: S.Priority.aboveNormal },
{ f: S.schedulePromiseNormal.bind(S), p: S.Priority.normal },
{ f: S.schedulePromiseBelowNormal.bind(S), p: S.Priority.belowNormal },
{ f: S.schedulePromiseIdle.bind(S), p: S.Priority.idle }
].forEach(function (helper) {
fn(helper.f, helper.p);
});
}
forEachPromiseHelper(function (promiseHelper, helperPriority) {
// Promise helper usage pattern 1
//
promiseHelper(45).done(function (value) {
LiveUnit.Assert.areEqual(helperPriority, S.currentPriority, "Promise helper executed in wrong priority context");
LiveUnit.Assert.areEqual(45, value, "Promise helper's promise completed with wrong value");
count++;
});
// Promise helper usage pattern 2
//
immediate().then(function () {
return 36;
}).then(promiseHelper).done(function (value) {
LiveUnit.Assert.areEqual(helperPriority, S.currentPriority, "Promise helper executed in wrong priority context");
LiveUnit.Assert.areEqual(36, value, "Promise helper's promise completed with wrong value");
count++;
});
});
return immediate().then(function () {
return schedulePromise(S.Priority.min);
}).then(function () {
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.areEqual(10, count);
});
});
// Verifies that callbacks associated with the promise helpers get executed in
// the proper order (order of descending priority).
//
testPromiseHelperPriorityOrdering = async(function () {
var count = 0;
S.schedulePromiseNormal().done(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
S.schedulePromiseHigh().done(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
S.schedulePromiseNormal().done(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
});
S.schedulePromiseBelowNormal().done(function () {
LiveUnit.Assert.areEqual(4, count);
count++;
});
S.schedulePromiseIdle().done(function () {
LiveUnit.Assert.areEqual(5, count);
count++;
});
S.schedulePromiseAboveNormal().done(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.areEqual(6, count);
});
});
// Verifies that callbacks associated with canceled promise helpers
// do not run.
//
testPromiseHelperCancellation = async(function () {
var count = 0;
S.schedulePromiseNormal().done(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
});
var high = S.schedulePromiseHigh().then(function () {
LiveUnit.Assert.fail("Job should have been canceled");
count++;
});
var normal2 = S.schedulePromiseNormal();
normal2.done(function () {
LiveUnit.Assert.fail("Job should have been canceled");
count++;
});
S.schedulePromiseBelowNormal().done(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
});
S.schedulePromiseIdle().done(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
});
S.schedulePromiseAboveNormal().done(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
high.cancel();
normal2.cancel();
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.areEqual(4, count);
});
});
//
// retrieveState tests
//
// Verifies retrieveState when there aren't any jobs or drain requests.
//
testRetrieveStateEmpty = function () {
verifyDump("retrieveState with no jobs and no drain requests",
"Jobs:\n" +
" None\n" +
"Drain requests:\n" +
" None\n"
);
};
// Verifies retrieveState for jobs. Specifically, verifies:
// - id, priority, and name are printed for jobs
// - priority name rather than number is printed when possible
// - name is left out of dump for jobs without a name
// - when retrieveState is called while a job is running, it is marked with an asterisk
// - the jobs are printed in the order in which they're expected to run
//
testRetrieveStateWithJobs = async(function () {
var count = 0;
var finalJob;
var unnamedJob = S.schedule(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
});
var anotherJob = S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
verifyDump("retrieveState inside running job",
"Jobs:\n" +
" *id: " + anotherJob.id + ", priority: normal, name: Verify retrieveState while I'm running\n" +
" id: " + unnamedPriority.id + ", priority: -1, name: My priority has no name\n" +
" id: " + namedJob.id + ", priority: belowNormal, name: A job with a name\n" +
" id: " + finalJob.id + ", priority: min\n" +
"Drain requests:\n" +
" None\n"
);
}, S.Priority.normal, null, "Verify retrieveState while I'm running");
var namedJob = S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, S.Priority.belowNormal, null, "A job with a name");
var unnamedPriority = S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, S.Priority.normal - 1, null, "My priority has no name");
verifyDump("retrieveState with a variety of jobs",
"Jobs:\n" +
" id: " + unnamedJob.id + ", priority: normal\n" +
" id: " + anotherJob.id + ", priority: normal, name: Verify retrieveState while I'm running\n" +
" id: " + unnamedPriority.id + ", priority: -1, name: My priority has no name\n" +
" id: " + namedJob.id + ", priority: belowNormal, name: A job with a name\n" +
"Drain requests:\n" +
" None\n"
);
return new WinJS.Promise(function (c) {
finalJob = S.schedule(function () {
LiveUnit.Assert.isTrue(S._isEmpty);
LiveUnit.Assert.areEqual(4, count);
c();
}, S.Priority.min);
});
});
// Verifies retrieveState for drain requests. Specifically, verifies:
// - priority and name are printed for each drain request
// - priority name rather than number is printed when possible
// - the current drain request is marked with an asterisk
// (the first drain request is always the current one)
// - the drain requests are printed in the order in which they will be processed
//
testRetrieveStateWithDrainRequests = async(function () {
var finalJob;
var j1 = S.schedule(function () {
}, S.Priority.high);
var j2 = S.schedule(function () {
}, S.Priority.normal);
var j3 = S.schedule(function () {
}, S.Priority.normal - 1);
S.requestDrain(S.Priority.normal, "First drain").then(function () {
verifyDump("retrieveState after 1 drain request completed",
"Jobs:\n" +
" id: " + j3.id + ", priority: -1\n" +
" id: " + finalJob.id + ", priority: min\n" +
"Drain requests:\n" +
" *priority: high, name: Drain with named priority\n" +
" priority: -1, name: Drain with unnamed priority\n"
);
});
S.requestDrain(S.Priority.high, "Drain with named priority");
S.requestDrain(S.Priority.normal - 1, "Drain with unnamed priority");
verifyDump("retrieveState with a variety of drain requests",
"Jobs:\n" +
" id: " + j1.id + ", priority: high\n" +
" id: " + j2.id + ", priority: normal\n" +
" id: " + j3.id + ", priority: -1\n" +
"Drain requests:\n" +
" *priority: normal, name: First drain\n" +
" priority: high, name: Drain with named priority\n" +
" priority: -1, name: Drain with unnamed priority\n"
);
return new WinJS.Promise(function (c) {
finalJob = S.schedule(function () {
LiveUnit.Assert.isTrue(S._isEmpty);
c();
}, S.Priority.min);
});
});
//
// test yielding at WWA priority boundaries
//
// Verifies that the WinJS scheduler schedules itself on the WWA scheduler at the
// priority of its most important job before yielding (rather than at a priority
// that is >= the priority of its most important job).
//
testUpdatingOfHighWaterMarkBeforeYielding = async(function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
mimicSchedulePump(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, MSApp.NORMAL);
repeatForDuration(S._TIME_SLICE + 10);
}, S.Priority.high);
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, S.Priority.aboveNormal);
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Verifies that the WinJS scheduler yields at WWA priority boundaries.
//
testYieldWhenDecreasingWwaPriority = async(function () {
function test(winjsHigherPri, winjsLowerPri, wwaLowerPri) {
var platformJobScheduled = false;
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
return WinJS.Utilities.hasWinRT && platformJobScheduled && isEqualOrHigherWwaPriority(wwaLowerPri, priority);
}
}, function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, winjsHigherPri);
mimicSchedulePump(function () {
platformJobScheduled = false;
LiveUnit.Assert.areEqual(2, count);
count++;
}, wwaLowerPri);
platformJobScheduled = true;
S.schedule(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
}, winjsLowerPri);
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
}, { forceHooksInWwa: true });
}
return WinJS.Promise.wrap().then(function () {
return test(S.Priority.aboveNormal + 1, S.Priority.aboveNormal, MSApp.NORMAL);
}).then(function () {
return test(S.Priority.belowNormal, S.Priority.belowNormal - 1, MSApp.IDLE);
});
});
// Verifies that the WinJS scheduler yields at WWA priority boundaries even when
// it didn't execute any jobs during the timeslice.
//
testYieldWhenDecreasingWwaPriorityWithoutExecutingAJob = async(function () {
function test(winjsHigherPri, winjsLowerPri, wwaLowerPri) {
var platformJobScheduled = false;
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
return WinJS.Utilities.hasWinRT && platformJobScheduled && isEqualOrHigherWwaPriority(wwaLowerPri, priority);
}
}, function () {
var count = 0;
var j1 = S.schedule(function () {
LiveUnit.Assert.fail("job should not have run");
}, winjsHigherPri);
mimicSchedulePump(function () {
platformJobScheduled = false;
LiveUnit.Assert.areEqual(1, count);
count++;
}, wwaLowerPri);
platformJobScheduled = true;
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, winjsLowerPri);
j1.cancel();
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
}, { forceHooksInWwa: true });
}
return WinJS.Promise.wrap().then(function () {
return test(S.Priority.aboveNormal + 1, S.Priority.aboveNormal, MSApp.NORMAL);
}).then(function () {
return test(S.Priority.belowNormal, S.Priority.belowNormal - 1, MSApp.IDLE);
});
});
// Verifies that the WinJS scheduler can run jobs of 2 different WWA priorities
// in a single timeslice as long as the second WWA priority is higher than the
// first.
//
testDontYieldWhenIncreasingWwaPriority = async(function () {
function test(winjsHigherPri, winjsLowerPri, wwaHigherPri) {
var platformJobScheduled = false;
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
return WinJS.Utilities.hasWinRT && platformJobScheduled && isEqualOrHigherWwaPriority(wwaHigherPri, priority);
}
}, function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
mimicSchedulePump(function () {
platformJobScheduled = false;
LiveUnit.Assert.areEqual(3, count);
count++;
}, wwaHigherPri);
platformJobScheduled = true;
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, winjsHigherPri);
}, winjsLowerPri);
LiveUnit.Assert.areEqual(0, count);
count++;
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
}, { forceHooksInWwa: true });
}
return WinJS.Promise.wrap().then(function () {
return test(S.Priority.aboveNormal + 1, S.Priority.aboveNormal, MSApp.HIGH);
}).then(function () {
return test(S.Priority.belowNormal, S.Priority.belowNormal - 1, MSApp.NORMAL);
});
});
// Verifies that the WinJS scheduler can run jobs of different WinJS priorties
// but of the same WWA priority in the same timeslice.
//
testDontYieldBetweenSameWwaPriority = async(function () {
function test(winjsHigherPri, winjsLowerPri, wwaPri) {
var platformJobScheduled = false;
var signal = new WinJS._Signal();
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
return WinJS.Utilities.hasWinRT && platformJobScheduled && isEqualOrHigherWwaPriority(wwaPri, priority);
}
}, function () {
var count = 0;
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, winjsHigherPri);
mimicSchedulePump(function () {
platformJobScheduled = false;
LiveUnit.Assert.areEqual(3, count);
count++;
signal.complete();
}, wwaPri);
platformJobScheduled = true;
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, winjsLowerPri);
LiveUnit.Assert.areEqual(0, count);
count++;
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
return WinJS.Promise.timeout();
});
}, { forceHooksInWwa: true });
}
return WinJS.Promise.wrap().then(function () {
return test(S.Priority.aboveNormal + 2, S.Priority.aboveNormal + 1, MSApp.HIGH);
}).then(function () {
return test(S.Priority.aboveNormal, S.Priority.aboveNormal - 1, MSApp.NORMAL);
}).then(function () {
return test(S.Priority.belowNormal + 1, S.Priority.belowNormal, MSApp.NORMAL);
}).then(function () {
return test(S.Priority.belowNormal - 1, S.Priority.belowNormal - 2, MSApp.IDLE);
});
});
};
export class SchedulerLocalContext {
//
// Test usage of WWA scheduler APIs
//
testUsingWwaScheduler = function () {
LiveUnit.Assert.isTrue(S._usingWwaScheduler,
"When running in the local context, the WinJS scheduler should be using the WWA scheduler");
};
// WWA scheduler integration test for testExecHigh
//
testWwaExecHigh = function () {
function return4() {
LiveUnit.Assert.areEqual(0, return4Called);
return4Called++;
LiveUnit.Assert.areEqual(S.currentPriority, S.Priority.high,
"Expected to be in high WinJS priority context");
LiveUnit.Assert.areEqual(MSApp.getCurrentPriority(), MSApp.HIGH,
"Expected to be in high WWA priority context");
return 4;
}
var return4Called = 0;
var returnValue = S.execHigh(return4);
LiveUnit.Assert.areEqual(4, returnValue,
"execHigh should have returned the return value of the callback");
LiveUnit.Assert.areEqual(1, return4Called, "return4 should have been called exactly 1 time");
LiveUnit.Assert.isTrue(S._isEmpty);
};
// WWA scheduler integration test for testPriorityContextOfScheduledJob
//
testWwaPriorityContextOfScheduledJob = async(function () {
function test(winjsPriority, wwaPriority) {
function workFunction() {
LiveUnit.Assert.areEqual(0, workFunctionCalled);
workFunctionCalled++;
LiveUnit.Assert.areEqual(winjsPriority, S.currentPriority);
LiveUnit.Assert.areEqual(wwaPriority, MSApp.getCurrentPriority());
}
var workFunctionCalled = 0;
S.schedule(workFunction, winjsPriority);
return schedulePromise(S.Priority.min).then(function () {
LiveUnit.Assert.areEqual(1, workFunctionCalled);
LiveUnit.Assert.areEqual(S.Priority.min, S.currentPriority);
LiveUnit.Assert.areEqual(MSApp.IDLE, MSApp.getCurrentPriority());
LiveUnit.Assert.isTrue(S._isEmpty);
});
}
return forEachPriorityBoundary(test);
});
// Verifies that when the WinJS scheduler is not pumping, currentPriority
// returns the current WWA priority context. WWA scheduler integration test.
//
testWwaCurrentPriority = async(function () {
var count = 0;
var s = new WinJS._Signal();
LiveUnit.Assert.areEqual(S.Priority.normal, S.currentPriority);
MSApp.execAsyncAtPriority(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
LiveUnit.Assert.areEqual(S.Priority.normal, S.currentPriority);
}, MSApp.NORMAL);
MSApp.execAsyncAtPriority(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
LiveUnit.Assert.areEqual(S.Priority.idle, S.currentPriority);
s.complete();
}, MSApp.IDLE);
MSApp.execAsyncAtPriority(function () {
LiveUnit.Assert.areEqual(0, count);
count++;
LiveUnit.Assert.areEqual(S.Priority.high, S.currentPriority);
}, MSApp.HIGH);
return s.promise.then(function () {
LiveUnit.Assert.areEqual(3, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
});
// Local context only. Verifies that the WinJS scheduler doesn't yield at WWA
// priority boundaries when the WWA scheduler doesn't have any work that is
// at least as important as the scheduled WinJS work.
//
// In the web context, we cannot determine whether or not the host has any work
// so we always yield at WWA priority boundaries. This behavior is verified by
// the other WWA priority boundary unit tests.
//
testDontYieldWhenDecreasingWwaPriorityWhenHostHasNoWork = async(function () {
function test(winjsHigherPri, winjsLowerPri, wwaLowerPri) {
return withMSAppHooks({
isTaskScheduledAtPriorityOrHigher: function (origFn, args, priority) {
// WWA scheduler doesn't have any work that is at
// least as important as ours.
//
return false;
}
}, function () {
var count = 0;
var signal = new WinJS._Signal();
S.schedule(function () {
LiveUnit.Assert.areEqual(1, count);
count++;
}, winjsHigherPri);
// This job is not meant to simulate WWA host work. It's just
// here to ensure that the WinJS scheduler runs its two jobs
// without yielding.
//
mimicSchedulePump(function () {
LiveUnit.Assert.areEqual(3, count);
count++;
signal.complete();
}, wwaLowerPri);
S.schedule(function () {
LiveUnit.Assert.areEqual(2, count);
count++;
}, winjsLowerPri);
LiveUnit.Assert.areEqual(0, count);
count++;
return signal.promise.then(function () {
LiveUnit.Assert.areEqual(4, count);
LiveUnit.Assert.isTrue(S._isEmpty);
});
}, { forceHooksInWwa: true });
}
return WinJS.Promise.wrap().then(function () {
return test(S.Priority.aboveNormal + 1, S.Priority.aboveNormal, MSApp.NORMAL);
}).then(function () {
return test(S.Priority.belowNormal, S.Priority.belowNormal - 1, MSApp.IDLE);
});
});
}
}
LiveUnit.registerTestClass("CorsicaTests.LinkedListMixin");
LiveUnit.registerTestClass("CorsicaTests.Scheduler");
if (WinJS.Utilities.hasWinRT) {
LiveUnit.registerTestClass("CorsicaTests.SchedulerLocalContext");
}