Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FreeQueue: automated test coverage #388

Merged
merged 4 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eleventy.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ module.exports = function(eleventyConfig) {
'src/README.md',
'src/sitemap.xml',
'src/lib/**/*.js',
'src/lib/**/*.html',
].map(path => eleventyConfig.addPassthroughCopy(path));

// eleventyConfig.addPassthroughCopy('src/favicon');
Expand Down
115 changes: 115 additions & 0 deletions src/lib/free-queue/coi-serviceworker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*! coi-serviceworker v0.1.6 - Guido Zuidhof, licensed under MIT */
let coepCredentialless = false;
if (typeof window === 'undefined') {
self.addEventListener("install", () => self.skipWaiting());
self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim()));

self.addEventListener("message", (ev) => {
if (!ev.data) {
return;
} else if (ev.data.type === "deregister") {
self.registration
.unregister()
.then(() => {
return self.clients.matchAll();
})
.then(clients => {
clients.forEach((client) => client.navigate(client.url));
});
} else if (ev.data.type === "coepCredentialless") {
coepCredentialless = ev.data.value;
}
});

self.addEventListener("fetch", function (event) {
const r = event.request;
if (r.cache === "only-if-cached" && r.mode !== "same-origin") {
return;
}

const request = (coepCredentialless && r.mode === "no-cors")
? new Request(r, {
credentials: "omit",
})
: r;
event.respondWith(
fetch(request)
.then((response) => {
if (response.status === 0) {
return response;
}

const newHeaders = new Headers(response.headers);
newHeaders.set("Cross-Origin-Embedder-Policy",
coepCredentialless ? "credentialless" : "require-corp"
);
newHeaders.set("Cross-Origin-Opener-Policy", "same-origin");

return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: newHeaders,
});
})
.catch((e) => console.error(e))
);
});

} else {
(() => {
// You can customize the behavior of this script through a global `coi` variable.
const coi = {
shouldRegister: () => true,
shouldDeregister: () => false,
coepCredentialless: () => false,
doReload: () => window.location.reload(),
quiet: false,
...window.coi
};

const n = navigator;

if (n.serviceWorker && n.serviceWorker.controller) {
n.serviceWorker.controller.postMessage({
type: "coepCredentialless",
value: coi.coepCredentialless(),
});

if (coi.shouldDeregister()) {
n.serviceWorker.controller.postMessage({ type: "deregister" });
}
}

// If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are
// already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here.
if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return;

if (!window.isSecureContext) {
!coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required.");
return;
}

// In some environments (e.g. Chrome incognito mode) this won't be available
if (n.serviceWorker) {
n.serviceWorker.register(window.document.currentScript.src).then(
(registration) => {
!coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope);

registration.addEventListener("updatefound", () => {
!coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker.");
coi.doReload();
});

// If the registration is active, but it's not controlling the page
if (registration.active && !n.serviceWorker.controller) {
!coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker.");
coi.doReload();
}
},
(err) => {
!coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err);
}
);
}
})();
}
8 changes: 8 additions & 0 deletions src/lib/free-queue/free-queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@ class FreeQueue {
// The channel count of arraySequence and the length of each channel must
// match with this buffer obejct.

if (arraySequence.length !== this._channelCount) {
throw new Error('Channel count mismatch');
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
}

// Transfer data from the |arraySequence| storage to the internal buffer.
const sourceLength = arraySequence[0].length;
for (let i = 0; i < sourceLength; ++i) {
Expand All @@ -215,6 +219,10 @@ class FreeQueue {
// The channel count of arraySequence and the length of each channel must
// match with this buffer obejct.

if (arraySequence.length !== this._channelCount) {
throw new Error('Channel count mismatch');
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
}

// If the FIFO is completely empty, do nothing.
if (this._framesAvailable === 0) {
return;
Expand Down
38 changes: 38 additions & 0 deletions src/lib/free-queue/test/free-queue.test.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>FreeQueue Tests</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/mocha/10.6.0/mocha.css" integrity="sha512-xPbP9qFwQklQFQX6R6+36vNq6mZatPrpfEKUB/zWASwZvfDBy8Y2gEpkRuDlZ0WCiZZGMPyluZif5v2KKxylqg==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<style>
.head {
margin-top: 5vh;
text-align: center;
}
button {
margin-left: 45vw;
margin-top: 30vh;
padding: 10px 20px;
font-size: 16px;
}
</style>
</head>
<body>
<h1 class="head">FreeQueue Test</h1>
<div id="mocha"></div>
<button id="run-tests">Run Tests</button>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mocha/10.6.0/mocha.min.js" integrity="sha512-Wt8lK9Rbdq3uAOR9lm7Fy+XTPP0xie3DzFQ0JTzQLGPvdZ7tDFMqHOJq6hbB4v8uvj7xaBEsINRs7K7HTfi9wA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chai/5.1.1/chai.js" integrity="sha512-HLs4bXIGAstDHJcCm2HfGhBkTUW9oErgCOCiDEnoOhwCEJLcLZUXX7TKcarluZx1D385vuUziYjD1uNGXy9zzg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
<script>
mocha.setup('bdd');
</script>
<script src="../coi-serviceworker.js"></script>
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
<script type="module" src="./free-queue.test.js"></script>
<script>
document.getElementById('run-tests').addEventListener('click', function() {
mocha.run();
});
</script>
</body>
</html
147 changes: 147 additions & 0 deletions src/lib/free-queue/test/free-queue.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
import { expect } from 'https://cdnjs.cloudflare.com/ajax/libs/chai/5.1.1/chai.js';
import { FreeQueue, MAX_CHANNEL_COUNT, RENDER_QUANTUM_FRAMES } from '../free-queue.js';

// Mock WASM module
const mockWasmModule = {
_malloc: (size) => new ArrayBuffer(size), // Simulate memory allocation
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
_free: () => {}, // Simulate memory deallocation
HEAPF32: new Float32Array(1024), // Simulate HEAPF32
};

describe('FreeQueue Class', () => {
const bufferLength = 512;
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
const channelCount = 2;
const maxChannelCount = 4;
let freeQueue;
professorabhay marked this conversation as resolved.
Show resolved Hide resolved

beforeEach(() => {
freeQueue = new FreeQueue(mockWasmModule, bufferLength, channelCount, maxChannelCount);
});

afterEach(() => {
freeQueue.free();
});

describe('Initialization', () => {
it('should initialize with correct properties', () => {
expect(freeQueue.length).to.equal(bufferLength);
expect(freeQueue.numberOfChannels).to.equal(channelCount);
expect(freeQueue.maxChannelCount).to.equal(maxChannelCount);
});

it('should allocate the correct amount of memory', () => {
const dataByteSize = channelCount * bufferLength * Float32Array.BYTES_PER_ELEMENT;
expect(freeQueue.getPointer()).to.be.instanceof(ArrayBuffer);
expect(freeQueue.getPointer().byteLength).to.equal(dataByteSize);
});
});

describe('Channel Adaptation', () => {
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
it('should adapt to a new channel count within limits', () => {
freeQueue.adaptChannel(3);
expect(freeQueue.numberOfChannels).to.equal(3);
});
});

describe('Pushing and Pulling Data', () => {
it('should correctly push and pull data', () => {
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
const testData = [new Float32Array(bufferLength).fill(1), new Float32Array(bufferLength).fill(2)];
freeQueue.push(testData);

const outputData = [new Float32Array(bufferLength), new Float32Array(bufferLength)];
freeQueue.pull(outputData);

expect(outputData[0]).to.deep.equal(testData[0]);
expect(outputData[1]).to.deep.equal(testData[1]);
});

it('should handle buffer overflow correctly', () => {
const testData = [new Float32Array(bufferLength * 2).fill(1), new Float32Array(bufferLength * 2).fill(2)];
freeQueue.push(testData);

expect(freeQueue.framesAvailable).to.equal(bufferLength);
});

it('should not pull data when buffer is empty', () => {
const outputData = [new Float32Array(bufferLength), new Float32Array(bufferLength)];
freeQueue.pull(outputData);

expect(outputData[0]).to.deep.equal(new Float32Array(bufferLength));
expect(outputData[1]).to.deep.equal(new Float32Array(bufferLength));
});

it('should manage partial data pulls', () => {
const testData = [new Float32Array(bufferLength).fill(1), new Float32Array(bufferLength).fill(2)];
freeQueue.push(testData);

const partialOutput = [new Float32Array(bufferLength / 2), new Float32Array(bufferLength / 2)];
freeQueue.pull(partialOutput);

expect(partialOutput[0]).to.deep.equal(new Float32Array(bufferLength / 2).fill(1));
expect(partialOutput[1]).to.deep.equal(new Float32Array(bufferLength / 2).fill(2));
expect(freeQueue.framesAvailable).to.equal(bufferLength / 2);
});

it('should handle multiple push and pull cycles', () => {
const testData = [new Float32Array(bufferLength).fill(1), new Float32Array(bufferLength).fill(2)];

for (let i = 0; i < 5; i++) {
freeQueue.push(testData);

const outputData = [new Float32Array(bufferLength), new Float32Array(bufferLength)];
freeQueue.pull(outputData);

expect(outputData[0]).to.deep.equal(testData[0]);
expect(outputData[1]).to.deep.equal(testData[1]);
expect(freeQueue.framesAvailable).to.equal(0);
}
});
});

describe('Error Handling', () => {
it('should return null for invalid channel index in getChannelData', () => {
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
const invalidIndex = channelCount + 1;
expect(freeQueue.getChannelData(invalidIndex)).to.be.null;
});

it('should throw an error if pushing with mismatched channel count', () => {
const invalidTestData = [new Float32Array(bufferLength).fill(1)];
expect(() => freeQueue.push(invalidTestData)).to.throw(Error, 'Channel count mismatch');
});

it('should throw an error if pulling with mismatched channel count', () => {
const invalidOutputData = [new Float32Array(bufferLength)];
expect(() => freeQueue.pull(invalidOutputData)).to.throw(Error, 'Channel count mismatch');
});
});

professorabhay marked this conversation as resolved.
Show resolved Hide resolved
describe('Performance', () => {
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
it('should efficiently handle large data transfers', function() {
this.timeout(5000);
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
const largeBuffer = 1024 * 1024; // 1 MB buffer
const testData = [new Float32Array(largeBuffer).fill(1), new Float32Array(largeBuffer).fill(2)];

const start = performance.now();
freeQueue.push(testData);
freeQueue.pull(testData);
const end = performance.now();

expect(end - start).to.be.below(1000); // Ensure operations complete within 1 second
});

it('should perform consistently over many operations', function() {
this.timeout(10000);
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
const iterations = 10000;
const testData = [new Float32Array(RENDER_QUANTUM_FRAMES).fill(1), new Float32Array(RENDER_QUANTUM_FRAMES).fill(2)];

const start = performance.now();
for (let i = 0; i < iterations; i++) {
freeQueue.push(testData);
freeQueue.pull(testData);
}
const end = performance.now();

expect(end - start).to.be.below(5000); // Ensure it completes within 5 seconds
professorabhay marked this conversation as resolved.
Show resolved Hide resolved
});
});
});
Loading