diff options
Diffstat (limited to 'dom/streams/test/xpcshell')
-rw-r--r-- | dom/streams/test/xpcshell/bug-1387503-1.js | 44 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/bug-1387503-2.js | 52 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/bug-1503406.js | 20 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/bug-1773237.js | 11 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/dom_stream_prototype_test.js | 29 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/fetch.js | 36 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/head.js | 45 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/large-pipeto.js | 101 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/proper-realm-cancel.js | 5 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/proper-realm-pull.js | 6 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/response.js | 30 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/subclassing.js | 123 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/too-big-array-buffer.js | 15 | ||||
-rw-r--r-- | dom/streams/test/xpcshell/xpcshell.ini | 22 |
14 files changed, 539 insertions, 0 deletions
diff --git a/dom/streams/test/xpcshell/bug-1387503-1.js b/dom/streams/test/xpcshell/bug-1387503-1.js new file mode 100644 index 0000000000..777f614c44 --- /dev/null +++ b/dom/streams/test/xpcshell/bug-1387503-1.js @@ -0,0 +1,44 @@ +// Test uncatchable error when a stream source's pull() method is called. +let readerCreated = false; +let fnFinished = false; +let g; + +add_task(async function test() { + // Make `debugger;` raise an uncatchable error. + g = newGlobal({ newCompartment: true }); + g.parent = this; + g.hit = false; + g.eval( + ` new Debugger(parent).onDebuggerStatement = _frame => (hit = true, null);` + ); + + // Create a stream whose pull() method raises an uncatchable error, + // and try reading from it. + + async function fn() { + try { + let stream = new ReadableStream({ + start(controller) {}, + pull(controller) { + // eslint-disable-next-line no-debugger + debugger; + }, + }); + + let reader = stream.getReader(); + let p = reader.read(); + readerCreated = true; + await p; + } finally { + fnFinished = true; + } + } + + fn(); +}); + +add_task(() => { + equal(readerCreated, true); + equal(g.hit, true); + equal(fnFinished, false); +}); diff --git a/dom/streams/test/xpcshell/bug-1387503-2.js b/dom/streams/test/xpcshell/bug-1387503-2.js new file mode 100644 index 0000000000..1678b46649 --- /dev/null +++ b/dom/streams/test/xpcshell/bug-1387503-2.js @@ -0,0 +1,52 @@ +// Test uncatchable error when a stream's queuing strategy's size() method is called. +/* global newGlobal */ + +let fnFinished = false; +let g; +add_task(async function test() { + // Make `debugger;` raise an uncatchable exception. + g = newGlobal(); + g.parent = this; + g.hit = false; + g.info = info; + g.eval(` + var dbg = new Debugger(parent); + dbg.onDebuggerStatement = (_frame, exc) => {hit = true; info("hit"); return null}; +`); + + async function fn() { + // Await once to postpone the uncatchable error until we're running inside + // a reaction job. We don't want the rest of the test to be terminated. + // (`drainJobQueue` catches uncatchable errors!) + await 1; + + try { + // Create a stream with a strategy whose .size() method raises an + // uncatchable exception, and have it call that method. + new ReadableStream( + { + start(controller) { + controller.enqueue("FIRST POST"); // this calls .size() + }, + }, + { + size() { + // eslint-disable-next-line no-debugger + debugger; + }, + } + ); + } finally { + fnFinished = true; + } + } + + fn() + .then(() => info("Resolved")) + .catch(() => info("Rejected")); +}); + +add_task(() => { + equal(g.hit, true, "We hit G"); + equal(fnFinished, false, "We didn't hit the finally block"); +}); diff --git a/dom/streams/test/xpcshell/bug-1503406.js b/dom/streams/test/xpcshell/bug-1503406.js new file mode 100644 index 0000000000..8e45291faf --- /dev/null +++ b/dom/streams/test/xpcshell/bug-1503406.js @@ -0,0 +1,20 @@ +let read; +let reader; + +add_task(async function test() { + let g = newGlobal({ wantGlobalProperties: ["ReadableStream"] }); + reader = g.eval(` + let stream = new ReadableStream({ + start(controller) { + controller.enqueue([]); + }, + }); + let [b1, b2] = stream.tee(); + b1.getReader(); +`); + read = new ReadableStream({}).getReader().read; +}); + +add_task(async function test2() { + read.call(reader); +}); diff --git a/dom/streams/test/xpcshell/bug-1773237.js b/dom/streams/test/xpcshell/bug-1773237.js new file mode 100644 index 0000000000..0d0107fe85 --- /dev/null +++ b/dom/streams/test/xpcshell/bug-1773237.js @@ -0,0 +1,11 @@ +// This test fails if there is an unhandled promise rejection +var stream = new ReadableStream({ + pull() { + return Promise.reject("foobar"); + }, +}); +var response = new Response(stream); +var text = response.text().then( + () => {}, + e => {} +); diff --git a/dom/streams/test/xpcshell/dom_stream_prototype_test.js b/dom/streams/test/xpcshell/dom_stream_prototype_test.js new file mode 100644 index 0000000000..b127368318 --- /dev/null +++ b/dom/streams/test/xpcshell/dom_stream_prototype_test.js @@ -0,0 +1,29 @@ +"use strict"; + +var log = []; +const stream = new ReadableStream({ + start(controller) { + log.push("started"); + }, + pull(controller) { + log.push("pulled"); + controller.enqueue("hi from pull"); + }, + cancel() { + log.push("cancelled"); + }, +}); + +print(log); // Currently prints "started"! + +add_task(async function helper() { + var reader = stream.getReader(); + var readPromise = reader.read(); + readPromise.then(x => print(`Printing promise result ${x}, log ${log}`)); + print(log); + + var x = await readPromise; + print(`Promise result ${x} ${x.value}`); + Assert.equal(x.value, "hi from pull"); + Assert.ok(true); +}); diff --git a/dom/streams/test/xpcshell/fetch.js b/dom/streams/test/xpcshell/fetch.js new file mode 100644 index 0000000000..03c8471f80 --- /dev/null +++ b/dom/streams/test/xpcshell/fetch.js @@ -0,0 +1,36 @@ +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.init(this); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "42", + "42" +); + +Cu.importGlobalProperties(["fetch"]); +add_task(async function helper() { + do_get_profile(); + + // The SearchService is also needed in order to construct the initial state, + // which means that the AddonManager needs to be available. + await AddonTestUtils.promiseStartupManager(); + + // The example.com domain will be used to host the dynamic layout JSON and + // the top stories JSON. + let server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] }); + server.registerDirectory("/", do_get_cwd()); + + Assert.equal(true, fetch instanceof Function); + var k = await fetch("http://example.com/"); + console.log(k); + console.log(k.body); + var r = k.body.getReader(); + console.log(r); + var v = await r.read(); + console.log(v); +}); diff --git a/dom/streams/test/xpcshell/head.js b/dom/streams/test/xpcshell/head.js new file mode 100644 index 0000000000..510cabe757 --- /dev/null +++ b/dom/streams/test/xpcshell/head.js @@ -0,0 +1,45 @@ +"use strict"; + +const { addDebuggerToGlobal } = ChromeUtils.importESModule( + "resource://gre/modules/jsdebugger.sys.mjs" +); + +const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"].createInstance( + Ci.nsIPrincipal +); + +function addTestingFunctionsToGlobal(global) { + global.eval( + ` + const testingFunctions = Cu.getJSTestingFunctions(); + for (let k in testingFunctions) { + + this[k] = testingFunctions[k]; + } + ` + ); + if (!global.print) { + global.print = info; + } + if (!global.newGlobal) { + global.newGlobal = newGlobal; + } + if (!global.Debugger) { + addDebuggerToGlobal(global); + } +} + +addTestingFunctionsToGlobal(this); + +/* Create a new global, with all the JS shell testing functions. Similar to the + * newGlobal function exposed to JS shells, and useful for porting JS shell + * tests to xpcshell tests. + */ +function newGlobal(args) { + const global = new Cu.Sandbox(SYSTEM_PRINCIPAL, { + freshCompartment: true, + ...args, + }); + addTestingFunctionsToGlobal(global); + return global; +} diff --git a/dom/streams/test/xpcshell/large-pipeto.js b/dom/streams/test/xpcshell/large-pipeto.js new file mode 100644 index 0000000000..420adfea20 --- /dev/null +++ b/dom/streams/test/xpcshell/large-pipeto.js @@ -0,0 +1,101 @@ +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); + +// Stamp an array buffer with a pattern; verified by verify_chunk below. +function init(array) { + for (var i = 0; i < array.length; i++) { + array[i] = i % 256; + } +} + +// The construction of the file below ends up with 12 instances +// of the array buffer -- we want this to be larger than 2**32 bytes +// to exercise potential truncation of nsIInputStream::Read's count +// parameter. +const ABLENGTH = (2 ** 32 + 8) / 12; + +// Get a large file (bigger than 2GB!) +function get_file() { + const array = new ArrayBuffer(ABLENGTH); + const buff = new Uint8Array(array); + + // Stamp with pattern. + init(buff); + + const blob = new Blob([buff, buff], {}); + return new File([blob, blob, blob, blob, blob, blob], {}); +} + +// Verify that the chunks the stream recieve correspond to the initialization +function verify_chunk(chunk, verification_state) { + for (var j = 0; j < chunk.length; j++) { + // If we don't match the fill pattern. + if (chunk[j] != verification_state.expected) { + // we ran out of array buffer; so we should be looking at the first byte of the array again. + if ( + verification_state.total_index % ABLENGTH != 0 || + chunk[j] != 0 /* ASSUME THAT THE INITIALIZATION OF THE BUFFER IS ZERO */ + ) { + throw new Error( + `Mismatch: chunk[${j}] (${chunk[j]}) != ${verification_state.expected} (total_index ${verification_state.total_index})` + ); + } + // Reset the fill expectation to 1 for next round. + verification_state.expected = 1; + } else { + // We are inside regular fill section + verification_state.expected = (verification_state.expected + 1) % 256; + } + verification_state.total_index++; + } +} + +// Pipe To Testing: Win32 can't handle the file size created in this test and OOMs. +add_task( + { + skip_if: () => AppConstants.platform == "win" && !Services.appinfo.is64Bit, + }, + async () => { + var chunk_verification_state = { + expected: 0, + total_index: 0, + }; + + const file = get_file(); + + await file.stream().pipeTo( + new WritableStream({ + write(chunk) { + verify_chunk(chunk, chunk_verification_state); + }, + }) + ); + } +); + +// Do the same test as above, but this time don't use pipeTo. +add_task( + { + skip_if: () => AppConstants.platform == "win" && !Services.appinfo.is64Bit, + }, + async () => { + var file = get_file(); + + var chunk_verification_state = { + expected: 0, + total_index: 0, + }; + + var streamReader = file.stream().getReader(); + + while (true) { + var res = await streamReader.read(); + if (res.done) { + break; + } + var chunk = res.value; + verify_chunk(chunk, chunk_verification_state); + } + } +); diff --git a/dom/streams/test/xpcshell/proper-realm-cancel.js b/dom/streams/test/xpcshell/proper-realm-cancel.js new file mode 100644 index 0000000000..1de6db3172 --- /dev/null +++ b/dom/streams/test/xpcshell/proper-realm-cancel.js @@ -0,0 +1,5 @@ +// This test passes if we don't have a CCW assertion. +var g = newGlobal(); +var ccwCancelMethod = new g.Function("return 17;"); + +new ReadableStream({ cancel: ccwCancelMethod }).cancel("bye"); diff --git a/dom/streams/test/xpcshell/proper-realm-pull.js b/dom/streams/test/xpcshell/proper-realm-pull.js new file mode 100644 index 0000000000..9d42d9a65d --- /dev/null +++ b/dom/streams/test/xpcshell/proper-realm-pull.js @@ -0,0 +1,6 @@ +// This test passes if we don't have a CCW assertion. + +var g = newGlobal({ newCompartment: true }); +var ccwPullMethod = new g.Function("return 17;"); + +new ReadableStream({ pull: ccwPullMethod }); diff --git a/dom/streams/test/xpcshell/response.js b/dom/streams/test/xpcshell/response.js new file mode 100644 index 0000000000..ead1be527f --- /dev/null +++ b/dom/streams/test/xpcshell/response.js @@ -0,0 +1,30 @@ +"use strict"; + +add_task(async function (test) { + return new Response(new Blob([], { type: "text/plain" })).body.cancel(); +}); + +add_task(function (test) { + var response = new Response( + new Blob(["This is data"], { type: "text/plain" }) + ); + var reader = response.body.getReader(); + reader.read(); + return reader.cancel(); +}); + +add_task(function (test) { + var response = new Response(new Blob(["T"], { type: "text/plain" })); + var reader = response.body.getReader(); + + var closedPromise = reader.closed.then(function () { + return reader.cancel(); + }); + reader.read().then(function readMore({ done, value }) { + if (!done) { + return reader.read().then(readMore); + } + return undefined; + }); + return closedPromise; +}); diff --git a/dom/streams/test/xpcshell/subclassing.js b/dom/streams/test/xpcshell/subclassing.js new file mode 100644 index 0000000000..b2b86cf353 --- /dev/null +++ b/dom/streams/test/xpcshell/subclassing.js @@ -0,0 +1,123 @@ +// Adapted from js/src/tests/non262/ReadableStream/subclassing.js to suit requirements of xpcshell-testing. + +function assertEq(a, b) { + Assert.equal(a, b); +} +function assertThrowsInstanceOf(fun, err) { + var regexp = new RegExp(err.name); + print(regexp); + Assert.throws(fun, regexp); +} + +// Spot-check subclassing of stream constructors. + +// ReadableStream can be subclassed. +class PartyStreamer extends ReadableStream {} + +let started = false; +add_task(function subclass_helper() { + // The base class constructor is called. + let stream = new PartyStreamer({ + // (The ReadableStream constructor calls this start method.) + start(c) { + started = true; + }, + }); + + assertEq(started, true); + + // The instance's prototype chain is correct. + assertEq(stream.__proto__, PartyStreamer.prototype); + assertEq(stream.__proto__.__proto__, ReadableStream.prototype); + assertEq(stream.__proto__.__proto__.__proto__, Object.prototype); + assertEq(stream.__proto__.__proto__.__proto__.__proto__, null); + assertEq(stream instanceof ReadableStream, true); + + // Non-generic methods can be called on the resulting stream. + stream.getReader(); + assertEq(stream.locked, true); +}); + +add_task(function strategy_helper() { + // CountQueuingStrategy can be subclassed. + class PixelStrategy extends CountQueuingStrategy {} + assertEq( + new PixelStrategy({ highWaterMark: 4 }).__proto__, + PixelStrategy.prototype + ); + + // The base class constructor is called. + assertThrowsInstanceOf(() => new PixelStrategy(), TypeError); + assertEq(new PixelStrategy({ highWaterMark: -1 }).highWaterMark, -1); + + // // VerySmartStrategy can be subclassed. + // class VerySmartStrategy extends ByteLengthQueuingStrategy { + // size(chunk) { + // return super.size(chunk) * 8; + // } + // } + // let vss = new VerySmartStrategy({ highWaterMark: 12 }); + // assertEq(vss.size(new ArrayBuffer(8)), 64); + // assertEq(vss.__proto__, VerySmartStrategy.prototype); +}); + +// Even ReadableStreamDefaultReader can be subclassed. +add_task(async function readerTest() { + const ReadableStreamDefaultReader = new ReadableStream().getReader() + .constructor; + class MindReader extends ReadableStreamDefaultReader { + async read() { + let foretold = { value: "death", done: false }; + let actual = await super.read(); + actual = foretold; // ZOMG I WAS RIGHT, EXACTLY AS FORETOLD they should call me a righter + return actual; + } + } + + let stream = new ReadableStream({ + start(c) { + c.enqueue("one"); + c.enqueue("two"); + }, + pull(c) { + c.close(); + }, + }); + let reader = new MindReader(stream); + let result = await reader.read(); + assertEq(result.value, "death"); + reader.releaseLock(); + + reader = stream.getReader(); + result = await reader.read(); + assertEq(result.done, false); + assertEq(result.value, "two"); + result = await reader.read(); + assertEq(result.done, true); + assertEq(result.value, undefined); +}); + +add_task(function default_controller() { + // Even ReadableStreamDefaultController, which can't be constructed, + // can be subclassed. + let ReadableStreamDefaultController; + new ReadableStream({ + start(c) { + ReadableStreamDefaultController = c.constructor; + }, + }); + class MasterController extends ReadableStreamDefaultController { + constructor() { + // don't call super, it'll just throw + return Object.create(MasterController.prototype); + } + } + let c = new MasterController(); + + // The prototype chain is per spec. + assertEq(c instanceof ReadableStreamDefaultController, true); + + // But the instance does not have the internal slots of a + // ReadableStreamDefaultController, so the non-generic methods can't be used. + assertThrowsInstanceOf(() => c.enqueue("horse"), TypeError); +}); diff --git a/dom/streams/test/xpcshell/too-big-array-buffer.js b/dom/streams/test/xpcshell/too-big-array-buffer.js new file mode 100644 index 0000000000..b80c36e813 --- /dev/null +++ b/dom/streams/test/xpcshell/too-big-array-buffer.js @@ -0,0 +1,15 @@ +add_task(async function helper() { + // Note: this test assumes the largest possible ArrayBuffer is + // smaller than 10GB -- if that changes, this test will fail. + let rs = new ReadableStream({ + type: "bytes", + autoAllocateChunkSize: 10 * 1024 * 1024 * 1024, + }); + let reader = rs.getReader(); + try { + await reader.read(); + Assert.equal(true, false, "Shouldn't succeed at reading"); + } catch (e) { + Assert.equal(e instanceof RangeError, true, "Should throw RangeError"); + } +}); diff --git a/dom/streams/test/xpcshell/xpcshell.ini b/dom/streams/test/xpcshell/xpcshell.ini new file mode 100644 index 0000000000..7c509fe7f9 --- /dev/null +++ b/dom/streams/test/xpcshell/xpcshell.ini @@ -0,0 +1,22 @@ +[DEFAULT] +head = head.js +skip-if = toolkit == 'android' +support-files = + +[response.js] +[fetch.js] +[dom_stream_prototype_test.js] +[subclassing.js] +[bug-1387503-1.js] +prefs= + security.allow_parent_unrestricted_js_loads=true +[bug-1503406.js] +[proper-realm-cancel.js] +[proper-realm-pull.js] +[large-pipeto.js] +skip-if = + os == "win" + tsan # Causes claim expired errors; see Bug 1770170. +run-sequentially = very high failure rate in parallel +[too-big-array-buffer.js] +[bug-1773237.js] |