diff options
Diffstat (limited to 'testing/web-platform/tests/IndexedDB/blob-composite-blob-reads.any.js')
-rw-r--r-- | testing/web-platform/tests/IndexedDB/blob-composite-blob-reads.any.js | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/testing/web-platform/tests/IndexedDB/blob-composite-blob-reads.any.js b/testing/web-platform/tests/IndexedDB/blob-composite-blob-reads.any.js new file mode 100644 index 0000000000..7d6ab9be35 --- /dev/null +++ b/testing/web-platform/tests/IndexedDB/blob-composite-blob-reads.any.js @@ -0,0 +1,155 @@ +// META: title=IDB-backed composite blobs maintain coherency +// META: script=resources/support-promises.js +// META: timeout=long + +// This test file is intended to help validate browser handling of complex blob +// scenarios where one or more levels of multipart blobs are used and varying +// IPC serialization strategies may be used depending on various complexity +// heuristics. +// +// A variety of approaches of reading the blob's contents are attempted for +// completeness: +// - `fetch-blob-url`: fetch of a URL created via URL.createObjectURL +// - Note that this is likely to involve multi-process behavior in a way that +// the next 2 currently will not unless their Blobs are round-tripped +// through a MessagePort. +// - `file-reader`: FileReader +// - `direct`: Blob.prototype.arrayBuffer() + +function composite_blob_test({ blobCount, blobSize, name }) { + // NOTE: In order to reduce the runtime of this test and due to the similarity + // of the "file-reader" mechanism to the "direct", "file-reader" is commented + // out, but if you are investigating failures detected by this test, you may + // want to uncomment it. + for (const mode of ["fetch-blob-url", /*"file-reader",*/ "direct"]) { + promise_test(async testCase => { + const key = "the-blobs"; + let memBlobs = []; + for (let iBlob = 0; iBlob < blobCount; iBlob++) { + memBlobs.push(new Blob([make_arraybuffer_contents(iBlob, blobSize)])); + } + + const db = await createDatabase(testCase, db => { + db.createObjectStore("blobs"); + }); + + const write_tx = db.transaction("blobs", "readwrite", {durability: "relaxed"}); + let store = write_tx.objectStore("blobs"); + store.put(memBlobs, key); + // Make the blobs eligible for GC which is most realistic and most likely + // to cause problems. + memBlobs = null; + + await promiseForTransaction(testCase, write_tx); + + const read_tx = db.transaction("blobs", "readonly", {durability: "relaxed"}); + store = read_tx.objectStore("blobs"); + const read_req = store.get(key); + + await promiseForTransaction(testCase, read_tx); + + const diskBlobs = read_req.result; + const compositeBlob = new Blob(diskBlobs); + + if (mode === "fetch-blob-url") { + const blobUrl = URL.createObjectURL(compositeBlob); + let urlResp = await fetch(blobUrl); + let urlFetchArrayBuffer = await urlResp.arrayBuffer(); + urlResp = null; + + URL.revokeObjectURL(blobUrl); + validate_arraybuffer_contents("fetched URL", urlFetchArrayBuffer, blobCount, blobSize); + urlFetchArrayBuffer = null; + + } else if (mode === "file-reader") { + let reader = new FileReader(); + let readerPromise = new Promise(resolve => { + reader.onload = () => { + resolve(reader.result); + } + }) + reader.readAsArrayBuffer(compositeBlob); + + let readArrayBuffer = await readerPromise; + readerPromise = null; + reader = null; + + validate_arraybuffer_contents("FileReader", readArrayBuffer, blobCount, blobSize); + readArrayBuffer = null; + } else if (mode === "direct") { + let directArrayBuffer = await compositeBlob.arrayBuffer(); + validate_arraybuffer_contents("arrayBuffer", directArrayBuffer, blobCount, blobSize); + } + }, `Composite Blob Handling: ${name}: ${mode}`); + } +} + +// Create an ArrayBuffer whose even bytes are the index identifier and whose +// odd bytes are a sequence incremented by 3 (wrapping at 256) so that +// discontinuities at power-of-2 boundaries are more detectable. +function make_arraybuffer_contents(index, size) { + const arr = new Uint8Array(size); + for (let i = 0, counter = 0; i < size; i += 2, counter = (counter + 3) % 256) { + arr[i] = index; + arr[i + 1] = counter; + } + return arr.buffer; +} + +function validate_arraybuffer_contents(source, buffer, blobCount, blobSize) { + // Accumulate a list of problems we perceive so we can report what seems to + // have happened all at once. + const problems = []; + + const arr = new Uint8Array(buffer); + + const expectedLength = blobCount * blobSize; + const actualCount = arr.length / blobSize; + if (arr.length !== expectedLength) { + problems.push(`ArrayBuffer only holds ${actualCount} blobs' worth instead of ${blobCount}.`); + problems.push(`Actual ArrayBuffer is ${arr.length} bytes but expected ${expectedLength}`); + } + + const counterBlobStep = (blobSize / 2 * 3) % 256; + let expectedBlob = 0; + let blobSeenSoFar = 0; + let expectedCounter = 0; + let counterDrift = 0; + for (let i = 0; i < arr.length; i += 2) { + if (arr[i] !== expectedBlob || blobSeenSoFar >= blobSize) { + if (blobSeenSoFar !== blobSize) { + problems.push(`Truncated blob ${expectedBlob} after ${blobSeenSoFar} bytes.`); + } else { + expectedBlob++; + } + if (expectedBlob !== arr[i]) { + problems.push(`Expected blob ${expectedBlob} but found ${arr[i]}, compensating.`); + expectedBlob = arr[i]; + } + blobSeenSoFar = 0; + expectedCounter = (expectedBlob * counterBlobStep) % 256; + counterDrift = 0; + } + + if (arr[i + 1] !== (expectedCounter + counterDrift) % 256) { + const newDrift = expectedCounter - arr[i + 1]; + problems.push(`In blob ${expectedBlob} at ${blobSeenSoFar + 1} bytes in, counter drift now ${newDrift} was ${counterDrift}`); + counterDrift = newDrift; + } + + blobSeenSoFar += 2; + expectedCounter = (expectedCounter + 3) % 256; + } + + if (problems.length) { + assert_true(false, `${source} blob payload problem: ${problems.join("\n")}`); + } else { + assert_true(true, `${source} blob payloads validated.`); + } +} + +composite_blob_test({ + blobCount: 16, + blobSize: 256 * 1024, + name: "Many blobs", +}); |