import { Request } from "../../Source/Cesium.js"; import { RequestScheduler } from "../../Source/Cesium.js"; import { RequestState } from "../../Source/Cesium.js"; import { when } from "../../Source/Cesium.js"; describe("Core/RequestScheduler", function () { var originalMaximumRequests; var originalMaximumRequestsPerServer; var originalPriorityHeapLength; var originalRequestsByServer; beforeAll(function () { originalMaximumRequests = RequestScheduler.maximumRequests; originalMaximumRequestsPerServer = RequestScheduler.maximumRequestsPerServer; originalPriorityHeapLength = RequestScheduler.priorityHeapLength; originalRequestsByServer = RequestScheduler.requestsByServer; }); beforeEach(function () { RequestScheduler.clearForSpecs(); RequestScheduler.requestsByServer = {}; }); afterEach(function () { RequestScheduler.maximumRequests = originalMaximumRequests; RequestScheduler.maximumRequestsPerServer = originalMaximumRequestsPerServer; RequestScheduler.priorityHeapLength = originalPriorityHeapLength; RequestScheduler.requestsByServer = originalRequestsByServer; }); it("request throws when request is undefined", function () { expect(function () { RequestScheduler.request(); }).toThrowDeveloperError(); }); it("request throws when request.url is undefined", function () { expect(function () { RequestScheduler.request( new Request({ requestFunction: function (url) { return undefined; }, }) ); }).toThrowDeveloperError(); }); it("request throws when request.requestFunction is undefined", function () { expect(function () { RequestScheduler.request( new Request({ url: "file/path", }) ); }).toThrowDeveloperError(); }); it("getServer throws if url is undefined", function () { expect(function () { RequestScheduler.getServerKey(); }).toThrowDeveloperError(); }); it("getServer with https", function () { var server = RequestScheduler.getServerKey("https://test.invalid/1"); expect(server).toEqual("test.invalid:443"); }); it("getServer with http", function () { var server = RequestScheduler.getServerKey("http://test.invalid/1"); expect(server).toEqual("test.invalid:80"); }); it("honors maximumRequests", function () { RequestScheduler.maximumRequests = 2; var statistics = RequestScheduler.statistics; var deferreds = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } function createRequest() { return new Request({ url: "http://test.invalid/1", requestFunction: requestFunction, throttle: true, }); } var promise1 = RequestScheduler.request(createRequest()); var promise2 = RequestScheduler.request(createRequest()); RequestScheduler.update(); // Scheduler is full, promise3 will be undefined var promise3 = RequestScheduler.request(createRequest()); RequestScheduler.update(); expect(statistics.numberOfActiveRequests).toBe(2); expect(promise1).toBeDefined(); expect(promise2).toBeDefined(); expect(promise3).not.toBeDefined(); // Scheduler now has an empty slot, promise4 goes through deferreds[0].resolve(); RequestScheduler.update(); expect(statistics.numberOfActiveRequests).toBe(1); var promise4 = RequestScheduler.request(createRequest()); RequestScheduler.update(); expect(statistics.numberOfActiveRequests).toBe(2); expect(promise4).toBeDefined(); // Scheduler is full, promise5 will be undefined var promise5 = RequestScheduler.request(createRequest()); RequestScheduler.update(); expect(statistics.numberOfActiveRequests).toBe(2); expect(promise5).not.toBeDefined(); // maximumRequests increases, promise6 goes through RequestScheduler.maximumRequests = 3; var promise6 = RequestScheduler.request(createRequest()); RequestScheduler.update(); expect(statistics.numberOfActiveRequests).toBe(3); expect(promise6).toBeDefined(); var length = deferreds.length; for (var i = 0; i < length; ++i) { deferreds[i].resolve(); } }); it("honors maximumRequestsPerServer", function () { RequestScheduler.maximumRequestsPerServer = 2; var deferreds = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } var url = "http://test.invalid/1"; var server = RequestScheduler.getServerKey(url); function createRequest() { return new Request({ url: url, requestFunction: requestFunction, throttleByServer: true, }); } var promise1 = RequestScheduler.request(createRequest()); var promise2 = RequestScheduler.request(createRequest()); RequestScheduler.update(); // Scheduler is full, promise3 will be undefined var promise3 = RequestScheduler.request(createRequest()); RequestScheduler.update(); expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(2); expect(promise1).toBeDefined(); expect(promise2).toBeDefined(); expect(promise3).not.toBeDefined(); // Scheduler now has an empty slot, promise4 goes through deferreds[0].resolve(); RequestScheduler.update(); expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(1); var promise4 = RequestScheduler.request(createRequest()); RequestScheduler.update(); expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(2); expect(promise4).toBeDefined(); // Scheduler is full, promise5 will be undefined var promise5 = RequestScheduler.request(createRequest()); RequestScheduler.update(); expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(2); expect(promise5).not.toBeDefined(); // maximumRequests increases, promise6 goes through RequestScheduler.maximumRequestsPerServer = 3; var promise6 = RequestScheduler.request(createRequest()); RequestScheduler.update(); expect(RequestScheduler.numberOfActiveRequestsByServer(server)).toBe(3); expect(promise6).toBeDefined(); var length = deferreds.length; for (var i = 0; i < length; ++i) { deferreds[i].resolve(); } }); it("honors priorityHeapLength", function () { var deferreds = []; var requests = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } function createRequest(priority) { var request = new Request({ url: "http://test.invalid/1", requestFunction: requestFunction, throttle: true, priority: priority, }); requests.push(request); return request; } RequestScheduler.priorityHeapLength = 1; var firstRequest = createRequest(0.0); var promise = RequestScheduler.request(firstRequest); expect(promise).toBeDefined(); promise = RequestScheduler.request(createRequest(1.0)); expect(promise).toBeUndefined(); RequestScheduler.priorityHeapLength = 3; promise = RequestScheduler.request(createRequest(2.0)); promise = RequestScheduler.request(createRequest(3.0)); expect(promise).toBeDefined(); promise = RequestScheduler.request(createRequest(4.0)); expect(promise).toBeUndefined(); // A request is cancelled to accommodate the new heap length RequestScheduler.priorityHeapLength = 2; expect(firstRequest.state).toBe(RequestState.CANCELLED); var length = deferreds.length; for (var i = 0; i < length; ++i) { deferreds[i].resolve(); } }); function testImmediateRequest(url, dataOrBlobUri) { var statistics = RequestScheduler.statistics; var deferreds = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } var request = new Request({ url: url, requestFunction: requestFunction, }); var promise = RequestScheduler.request(request); expect(promise).toBeDefined(); if (dataOrBlobUri) { expect(request.serverKey).toBeUndefined(); expect(statistics.numberOfActiveRequests).toBe(0); } else { expect(statistics.numberOfActiveRequests).toBe(1); expect( RequestScheduler.numberOfActiveRequestsByServer(request.serverKey) ).toBe(1); } deferreds[0].resolve(); return promise.then(function () { expect(request.state).toBe(RequestState.RECEIVED); expect(statistics.numberOfActiveRequests).toBe(0); if (!dataOrBlobUri) { expect( RequestScheduler.numberOfActiveRequestsByServer(request.serverKey) ).toBe(0); } }); } it("data uri goes through immediately", function () { var dataUri = "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D"; testImmediateRequest(dataUri, true); }); it("blob uri goes through immediately", function () { var uint8Array = new Uint8Array(4); var blob = new Blob([uint8Array], { type: "application/octet-stream", }); var blobUrl = window.URL.createObjectURL(blob); testImmediateRequest(blobUrl, true); }); it("request goes through immediately when throttle is false", function () { var url = "https://test.invalid/1"; testImmediateRequest(url, false); }); it("makes a throttled request", function () { var deferreds = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } var request = new Request({ throttle: true, url: "https://test.invalid/1", requestFunction: requestFunction, }); expect(request.state).toBe(RequestState.UNISSUED); var promise = RequestScheduler.request(request); expect(promise).toBeDefined(); expect(request.state).toBe(RequestState.ISSUED); RequestScheduler.update(); expect(request.state).toBe(RequestState.ACTIVE); deferreds[0].resolve(); expect(request.state).toBe(RequestState.RECEIVED); }); it("cancels an issued request", function () { var statistics = RequestScheduler.statistics; function requestFunction() { return when.resolve(); } var request = new Request({ throttle: true, url: "https://test.invalid/1", requestFunction: requestFunction, }); var promise = RequestScheduler.request(request); expect(request.state).toBe(RequestState.ISSUED); request.cancel(); RequestScheduler.update(); expect(request.state).toBe(RequestState.CANCELLED); expect(statistics.numberOfCancelledRequests).toBe(1); expect(statistics.numberOfCancelledActiveRequests).toBe(0); return promise .then(function () { fail("should not be called"); }) .otherwise(function (error) { expect(request.state).toBe(RequestState.CANCELLED); }); }); it("cancels an active request", function () { var statistics = RequestScheduler.statistics; var cancelFunction = jasmine.createSpy("cancelFunction"); function requestFunction() { return when.defer().promise; } var request = new Request({ throttle: true, url: "https://test.invalid/1", requestFunction: requestFunction, cancelFunction: cancelFunction, }); var promise = RequestScheduler.request(request); RequestScheduler.update(); expect(request.state).toBe(RequestState.ACTIVE); request.cancel(); RequestScheduler.update(); expect(request.state).toBe(RequestState.CANCELLED); expect(statistics.numberOfCancelledRequests).toBe(1); expect(statistics.numberOfCancelledActiveRequests).toBe(1); expect( RequestScheduler.numberOfActiveRequestsByServer(request.serverKey) ).toBe(0); expect(cancelFunction).toHaveBeenCalled(); return promise .then(function () { fail("should not be called"); }) .otherwise(function (error) { expect(request.state).toBe(RequestState.CANCELLED); }); }); it("handles request failure", function () { var statistics = RequestScheduler.statistics; var deferreds = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } var request = new Request({ url: "https://test.invalid/1", requestFunction: requestFunction, }); var promise = RequestScheduler.request(request); expect(request.state).toBe(RequestState.ACTIVE); expect(statistics.numberOfActiveRequests).toBe(1); deferreds[0].reject("Request failed"); RequestScheduler.update(); expect(statistics.numberOfActiveRequests).toBe(0); return promise .then(function () { fail("should not be called"); }) .otherwise(function (error) { expect(error).toBe("Request failed"); }); }); it("prioritizes requests", function () { var currentPriority = 0.0; function getRequestFunction(priority) { return function () { expect(priority).toBeGreaterThan(currentPriority); currentPriority = priority; return when.resolve(); }; } function createRequest(priority) { return new Request({ throttle: true, url: "https://test.invalid/1", requestFunction: getRequestFunction(priority), priority: priority, }); } var length = RequestScheduler.priorityHeapLength; for (var i = 0; i < length; ++i) { var priority = Math.random(); RequestScheduler.request(createRequest(priority)); } RequestScheduler.update(); expect(currentPriority).toBeGreaterThan(0.0); // Ensures that the expect in getRequestFunction is actually called }); it("updates priority", function () { var invertPriority = false; function getPriorityFunction(priority) { return function () { if (invertPriority) { return 1.0 - priority; } return priority; }; } function requestFunction() { return when.resolve(); } function createRequest(priority) { return new Request({ throttle: true, url: "https://test.invalid/1", requestFunction: requestFunction, priorityFunction: getPriorityFunction(priority), }); } var i; var request; var length = RequestScheduler.priorityHeapLength; for (i = 0; i < length; ++i) { var priority = i / (length - 1); request = createRequest(priority); request.testId = i; RequestScheduler.request(request); } // Update priorities while not letting any requests go through RequestScheduler.maximumRequests = 0; RequestScheduler.update(); var requestHeap = RequestScheduler.requestHeap; var requests = []; var currentTestId = 0; while (requestHeap.length > 0) { request = requestHeap.pop(); requests.push(request); expect(request.testId).toBeGreaterThanOrEqualTo(currentTestId); currentTestId = request.testId; } for (i = 0; i < length; ++i) { requestHeap.insert(requests[i]); } invertPriority = true; RequestScheduler.update(); while (requestHeap.length > 0) { request = requestHeap.pop(); expect(request.testId).toBeLessThanOrEqualTo(currentTestId); currentTestId = request.testId; } }); it("handles low priority requests", function () { function requestFunction() { return when.resolve(); } function createRequest(priority) { return new Request({ throttle: true, url: "https://test.invalid/1", requestFunction: requestFunction, priority: priority, }); } var highPriority = 0.0; var mediumPriority = 0.5; var lowPriority = 1.0; var length = RequestScheduler.priorityHeapLength; for (var i = 0; i < length; ++i) { RequestScheduler.request(createRequest(mediumPriority)); } // Heap is full so low priority request is not even issued var promise = RequestScheduler.request(createRequest(lowPriority)); expect(promise).toBeUndefined(); expect(RequestScheduler.statistics.numberOfCancelledRequests).toBe(0); // Heap is full so high priority request bumps off lower priority request promise = RequestScheduler.request(createRequest(highPriority)); expect(promise).toBeDefined(); expect(RequestScheduler.statistics.numberOfCancelledRequests).toBe(1); }); it("unthrottled requests starve throttled requests", function () { var deferreds = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } function createRequest(throttle) { return new Request({ url: "http://test.invalid/1", requestFunction: requestFunction, throttle: throttle, }); } var throttledRequest = createRequest(true); RequestScheduler.request(throttledRequest); for (var i = 0; i < RequestScheduler.maximumRequests; ++i) { RequestScheduler.request(createRequest(false)); } RequestScheduler.update(); expect(throttledRequest.state).toBe(RequestState.ISSUED); // Resolve one of the unthrottled requests deferreds[0].resolve(); RequestScheduler.update(); expect(throttledRequest.state).toBe(RequestState.ACTIVE); var length = deferreds.length; for (var j = 0; j < length; ++j) { deferreds[j].resolve(); } }); it("request throttled by server is cancelled", function () { var deferreds = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } function createRequest(throttleByServer) { return new Request({ url: "http://test.invalid/1", requestFunction: requestFunction, throttle: throttleByServer, throttleByServer: throttleByServer, }); } for (var i = 0; i < RequestScheduler.maximumRequestsPerServer - 1; ++i) { RequestScheduler.request(createRequest(false)); } var throttledRequest = createRequest(true); RequestScheduler.request(throttledRequest); RequestScheduler.request(createRequest(false)); RequestScheduler.update(); expect(throttledRequest.state).toBe(RequestState.CANCELLED); var length = deferreds.length; for (var j = 0; j < length; ++j) { deferreds[j].resolve(); } }); it("does not throttle requests when throttleRequests is false", function () { RequestScheduler.maximumRequests = 0; function requestFunction() { return when.resolve(); } RequestScheduler.throttleRequests = true; var request = new Request({ throttle: true, url: "https://test.invalid/1", requestFunction: requestFunction, }); var promise = RequestScheduler.request(request); expect(promise).toBeUndefined(); RequestScheduler.throttleRequests = false; request = new Request({ throttle: true, url: "https://test.invalid/1", requestFunction: requestFunction, }); promise = RequestScheduler.request(request); expect(promise).toBeDefined(); RequestScheduler.throttleRequests = true; }); it("does not throttle requests by server when throttleRequests is false", function () { RequestScheduler.maximumRequestsPerServer = 0; function requestFunction() { return when.resolve(); } RequestScheduler.throttleRequests = true; var request = new Request({ throttleByServer: true, url: "https://test.invalid/1", requestFunction: requestFunction, }); var promise = RequestScheduler.request(request); expect(promise).toBeUndefined(); RequestScheduler.throttleRequests = false; request = new Request({ throttleByServer: true, url: "https://test.invalid/1", requestFunction: requestFunction, }); promise = RequestScheduler.request(request); expect(promise).toBeDefined(); RequestScheduler.throttleRequests = true; }); it("debugShowStatistics", function () { spyOn(console, "log"); RequestScheduler.debugShowStatistics = true; var deferreds = []; function requestFunction() { var deferred = when.defer(); deferreds.push(deferred); return deferred.promise; } function createRequest() { return new Request({ url: "https://test.invalid/1", requestFunction: requestFunction, }); } var requests = [createRequest(), createRequest(), createRequest()]; RequestScheduler.request(requests[0]); RequestScheduler.request(requests[1]); RequestScheduler.request(requests[2]); RequestScheduler.update(); deferreds[0].reject(); requests[0].cancel(); requests[1].cancel(); requests[2].cancel(); RequestScheduler.update(); expect(console.log).toHaveBeenCalledWith("Number of attempted requests: 3"); expect(console.log).toHaveBeenCalledWith("Number of cancelled requests: 3"); expect(console.log).toHaveBeenCalledWith( "Number of cancelled active requests: 2" ); expect(console.log).toHaveBeenCalledWith("Number of failed requests: 1"); var length = deferreds.length; for (var i = 0; i < length; ++i) { deferreds[i].resolve(); } RequestScheduler.debugShowStatistics = false; }); it("successful request causes requestCompletedEvent to be raised", function () { var deferred; function requestFunction() { deferred = when.defer(); return deferred.promise; } var request = new Request({ url: "https://test.invalid/1", requestFunction: requestFunction, }); var promise = RequestScheduler.request(request); expect(promise).toBeDefined(); var eventRaised = false; var removeListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener( function () { eventRaised = true; } ); deferred.resolve(); return promise .then(function () { expect(eventRaised).toBe(true); }) .always(function () { removeListenerCallback(); }); }); it("successful data request causes requestCompletedEvent to be raised", function () { var deferred; function requestFunction() { deferred = when.defer(); return deferred.promise; } var request = new Request({ url: "data:text/plain;base64,SGVsbG8sIFdvcmxkIQ%3D%3D", requestFunction: requestFunction, }); var eventRaised = false; var removeListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener( function () { eventRaised = true; } ); var promise = RequestScheduler.request(request); expect(promise).toBeDefined(); deferred.resolve(); RequestScheduler.update(); return promise .then(function () { expect(eventRaised).toBe(true); }) .always(function () { removeListenerCallback(); }); }); it("successful blob request causes requestCompletedEvent to be raised", function () { var deferred; function requestFunction() { deferred = when.defer(); return deferred.promise; } var uint8Array = new Uint8Array(4); var blob = new Blob([uint8Array], { type: "application/octet-stream", }); var blobUrl = window.URL.createObjectURL(blob); var request = new Request({ url: blobUrl, requestFunction: requestFunction, }); var eventRaised = false; var removeListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener( function () { eventRaised = true; } ); var promise = RequestScheduler.request(request); expect(promise).toBeDefined(); deferred.resolve(); RequestScheduler.update(); return promise .then(function () { expect(eventRaised).toBe(true); }) .always(function () { removeListenerCallback(); }); }); it("unsuccessful requests raise requestCompletedEvent with error", function () { var deferred; function requestFunction() { deferred = when.defer(); return deferred.promise; } var request = new Request({ url: "https://test.invalid/1", requestFunction: requestFunction, }); var eventRaised = false; var removeListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener( function (error) { eventRaised = true; expect(error).toBeDefined(); } ); var promise = RequestScheduler.request(request); expect(promise).toBeDefined(); deferred.reject({ error: "error", }); RequestScheduler.update(); return promise .then(function () { expect(eventRaised).toBe(true); }) .always(function () { removeListenerCallback(); }); }); it("canceled requests do not cause requestCompletedEvent to be raised", function () { var cancelDeferred; function requestCancelFunction() { cancelDeferred = when.defer(); return cancelDeferred.promise; } var requestToCancel = new Request({ url: "https://test.invalid/1", requestFunction: requestCancelFunction, }); RequestScheduler.request(requestToCancel); var removeListenerCallback = RequestScheduler.requestCompletedEvent.addEventListener( function () { fail("should not be called"); } ); requestToCancel.cancel(); RequestScheduler.update(); cancelDeferred.resolve(); removeListenerCallback(); }); it("RequestScheduler.requestsByServer allows for custom maximum requests", function () { var promise; RequestScheduler.requestsByServer["test.invalid:80"] = 23; for (var i = 0; i < 23; i++) { promise = RequestScheduler.request( new Request({ url: "http://test.invalid/1", throttle: true, throttleByServer: true, requestFunction: function () { return when.defer(); }, }) ); RequestScheduler.update(); expect(promise).toBeDefined(); } promise = RequestScheduler.request( new Request({ url: "http://test.invalid/1", throttle: true, throttleByServer: true, requestFunction: function () { return when.defer(); }, }) ); expect(promise).toBeUndefined(); }); });