summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/0-valid.ts22
-rw-r--r--test/1-size.ts24
-rw-r--r--test/2-perf.ts47
-rw-r--r--test/3-zip.ts1
-rw-r--r--test/4-streams.ts1
-rw-r--r--test/5-async.ts1
-rw-r--r--test/data/.gitignore2
-rw-r--r--test/results/.gitignore2
-rw-r--r--test/tsconfig.json8
-rw-r--r--test/util.ts196
10 files changed, 304 insertions, 0 deletions
diff --git a/test/0-valid.ts b/test/0-valid.ts
new file mode 100644
index 0000000..99516b4
--- /dev/null
+++ b/test/0-valid.ts
@@ -0,0 +1,22 @@
+import { testSuites, workers, bClone } from './util';
+import * as assert from 'uvu/assert';
+
+// Name is to ensure that this runs first
+// Note that workers are not used here to optimize performance but rather
+// to prevent infinite loops from hanging the process.
+testSuites({
+ async compression(file) {
+ const fileClone = bClone(file);
+ const cProm = workers.fflate.deflate(fileClone, [fileClone.buffer]);
+ cProm.timeout(10000);
+ const buf = await cProm;
+ assert.ok(file.equals(await workers.zlib.inflate(buf, [buf.buffer])));
+ },
+ async decompression(file) {
+ const fileClone = bClone(file);
+ const data = await workers.zlib.deflate(fileClone, [fileClone.buffer]);
+ const dProm = workers.fflate.inflate(data, [data.buffer]);
+ dProm.timeout(5000);
+ assert.ok(file.equals(await dProm));
+ }
+}); \ No newline at end of file
diff --git a/test/1-size.ts b/test/1-size.ts
new file mode 100644
index 0000000..c2cc247
--- /dev/null
+++ b/test/1-size.ts
@@ -0,0 +1,24 @@
+import { testSuites, workers, bClone } from './util';
+import { writeFileSync } from 'fs';
+import { join } from 'path';
+import { performance } from 'perf_hooks';
+import * as assert from 'uvu/assert';
+
+const sizePerf: Record<string, Record<string, [number, number]>> = {};
+
+testSuites({
+ async main(file, name) {
+ sizePerf[name] = {};
+ for (const lib of (['fflate', 'pako', 'uzip', 'zlib'] as const)) {
+ const clone = bClone(file);
+ const ts = performance.now();
+ sizePerf[name][lib] = [(await workers[lib].deflate([clone, { level: 9 }], [clone.buffer])).length, performance.now() - ts];
+ }
+ for (const lib of ['pako', 'uzip', 'zlib']) {
+ // Less than 5% larger
+ assert.ok(((sizePerf[name].fflate[0] - sizePerf[name][lib][0]) / sizePerf[name][lib][0]) < 0.05);
+ }
+ }
+}).then(() => {
+ writeFileSync(join(__dirname, 'results', 'longTimings.json'), JSON.stringify(sizePerf, null, 2));
+}) \ No newline at end of file
diff --git a/test/2-perf.ts b/test/2-perf.ts
new file mode 100644
index 0000000..98e0668
--- /dev/null
+++ b/test/2-perf.ts
@@ -0,0 +1,47 @@
+import { testSuites, workers, bClone, TestHandler } from './util';
+import { writeFileSync } from 'fs';
+import { join } from 'path';
+
+const preprocessors = {
+ inflate: workers.zlib.deflate,
+ gunzip: workers.zlib.gzip,
+ unzlib: workers.zlib.zlib
+};
+
+const cache: Record<string, Record<string, Buffer>> = {
+ deflate: {},
+ inflate: {},
+ gzip: {},
+ gunzip: {},
+ zlib: {},
+ unzlib: {}
+};
+
+const flattenedWorkers: Record<string, TestHandler> = {};
+for (const k in workers) {
+ for (const l in workers[k]) {
+ if (l == 'zip' || l == 'unzip') continue;
+ flattenedWorkers[k + '.' + l] = async (file, name, resetTimer) => {
+ const fileClone = bClone(file);
+ let buf = fileClone;
+ if (preprocessors[l]) {
+ buf = bClone(cache[l][name] ||= Buffer.from(
+ await preprocessors[l as keyof typeof preprocessors](buf, [buf.buffer])
+ ));
+ resetTimer();
+ }
+ const opt2 = preprocessors[l]
+ ? k === 'tinyInflate'
+ ? new Uint8Array(file.length)
+ : null
+ : { level: 1 };
+ await workers[k][l]([buf, opt2], opt2 instanceof Uint8Array
+ ? [buf.buffer, opt2.buffer]
+ : [buf.buffer]);
+ }
+ }
+}
+
+testSuites(flattenedWorkers).then(perf => {
+ writeFileSync(join(__dirname, 'results', 'timings.json'), JSON.stringify(perf, null, 2));
+}); \ No newline at end of file
diff --git a/test/3-zip.ts b/test/3-zip.ts
new file mode 100644
index 0000000..840a999
--- /dev/null
+++ b/test/3-zip.ts
@@ -0,0 +1 @@
+// TODO: test ZIP \ No newline at end of file
diff --git a/test/4-streams.ts b/test/4-streams.ts
new file mode 100644
index 0000000..c85887d
--- /dev/null
+++ b/test/4-streams.ts
@@ -0,0 +1 @@
+// TODO: test all streams (including ZIP) \ No newline at end of file
diff --git a/test/5-async.ts b/test/5-async.ts
new file mode 100644
index 0000000..c3c6c42
--- /dev/null
+++ b/test/5-async.ts
@@ -0,0 +1 @@
+// TODO: test all async operations (including streams and ZIP) \ No newline at end of file
diff --git a/test/data/.gitignore b/test/data/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/test/data/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore \ No newline at end of file
diff --git a/test/results/.gitignore b/test/results/.gitignore
new file mode 100644
index 0000000..c96a04f
--- /dev/null
+++ b/test/results/.gitignore
@@ -0,0 +1,2 @@
+*
+!.gitignore \ No newline at end of file
diff --git a/test/tsconfig.json b/test/tsconfig.json
new file mode 100644
index 0000000..dd952bf
--- /dev/null
+++ b/test/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "../tsconfig.json",
+ "compilerOptions": {
+ "module": "CommonJS",
+ "moduleResolution": "node"
+ },
+ "include": ["./*.ts"]
+} \ No newline at end of file
diff --git a/test/util.ts b/test/util.ts
new file mode 100644
index 0000000..e91201f
--- /dev/null
+++ b/test/util.ts
@@ -0,0 +1,196 @@
+import { existsSync, readFile, writeFile } from 'fs';
+import { resolve } from 'path';
+import { get } from 'https';
+import { suite } from 'uvu';
+import { performance } from 'perf_hooks';
+import { Worker } from 'worker_threads';
+
+const testFiles = {
+ basic: Buffer.from('Hello world!'),
+ text: 'https://www.gutenberg.org/files/2701/old/moby10b.txt',
+ smallImage: 'https://hlevkin.com/hlevkin/TestImages/new/Rainier.bmp',
+ image: 'https://www.hlevkin.com/hlevkin/TestImages/new/Maltese.bmp',
+ largeImage: 'https://www.hlevkin.com/hlevkin/TestImages/new/Sunrise.bmp'
+};
+
+const testZipFiles = {
+ model3D: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/kmz/Box.kmz',
+ largeModel3D: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/3mf/truck.3mf'
+};
+
+const dlCached = async <T extends Record<string, string | Buffer>>(files: T) => {
+ let res = {} as Record<keyof T, Buffer>;
+ for (const name in files) {
+ let data: string | Buffer = files[name];
+ if (typeof data == 'string') {
+ const fn = resolve(__dirname, 'data', name);
+ if (!existsSync(fn)) {
+ console.log('\nDownloading ' + data + '...');
+ data = await new Promise((r, re) => get(data as string, res => {
+ const len = +res.headers['content-length'];
+ const buf = Buffer.allocUnsafe(len);
+ let i = 0;
+ res.on('data', chunk => {
+ buf.set(chunk, i);
+ console.log((100 * (i += chunk.length) / len).toFixed(1) + '%\x1B[1A');
+ });
+ res.on('error', re);
+ res.on('end', () => {
+ console.log('Complete');
+ writeFile(fn, buf, () => r(buf));
+ });
+ }));
+ } else {
+ data = await new Promise((res, rej) =>
+ readFile(fn, (err, buf) => err ? rej(err) : res(buf))
+ );
+ }
+ }
+ res[name as keyof T] = data as Buffer;
+ }
+ return res;
+}
+
+const testFilesPromise = dlCached(testFiles);
+const testZipFilesPromise = dlCached(testZipFiles);
+
+export type TestHandler = (file: Buffer, name: string, resetTimer: () => void) => unknown | Promise<unknown>;
+
+export const testSuites = async <T extends Record<string, TestHandler>, D extends 'zip' | 'default' = 'default'>(suites: T, type?: D) => {
+ type DK = keyof (D extends 'zip' ? typeof testZipFiles : typeof testFiles);
+ const tf = type == 'zip' ? testZipFiles : testFiles;
+ const tfp = type == 'zip' ? testZipFilesPromise : testFilesPromise;
+ const perf = {} as Record<keyof T, Promise<Record<DK, number>>>;
+ for (const k in suites) {
+ perf[k] = new Promise(async setPerf => {
+ const ste = suite(k);
+ let localTestFiles: Record<DK, Buffer>;
+ ste.before(async () => {
+ localTestFiles = (await tfp) as unknown as Record<DK, Buffer>;
+ });
+ const localPerf = {} as Record<DK, number>;
+ for (const name in tf) {
+ ste(name, async () => {
+ let ts = performance.now();
+ await suites[k](localTestFiles[name], name, () => {
+ ts = performance.now();
+ });
+ localPerf[name] = performance.now() - ts;
+ });
+ }
+ ste.after(() => {
+ setPerf(localPerf);
+ });
+ ste.run();
+ })
+ }
+ const resolvedPerf = {} as Record<keyof T, Record<DK, number>>;
+ for (const k in suites) resolvedPerf[k] = await perf[k];
+ return resolvedPerf;
+};
+
+export const stream = (src: Uint8Array, dst: {
+ push(dat: Uint8Array, final: boolean): void;
+}) => {
+ for (let i = 0; i < src.length;) {
+ const off = Math.floor(Math.random() * Math.min(131072, src.length >>> 3));
+ dst.push(src.slice(i, i + off), (i += off) >= src.length);
+ }
+}
+
+// create worker string
+const cws = (pkg: string, method: string = '_cjsDefault') => `
+ const ${method == '_cjsDefault' ? method : `{ ${method} }`} = require('${pkg}');
+ const { Worker, workerData, parentPort } = require('worker_threads');
+ try {
+ const buf = ${method}(...(Array.isArray(workerData) ? workerData : [workerData]));
+ parentPort.postMessage(buf, [buf.buffer]);
+ } catch (err) {
+ parentPort.postMessage({ err });
+ }
+`;
+
+export type Workerized = (workerData: Uint8Array | [Uint8Array, {}], transferable?: ArrayBuffer[]) => WorkerizedResult;
+export interface WorkerizedResult extends PromiseLike<Uint8Array> {
+ timeout(ms: number): void;
+};
+
+// Worker creator
+const wc = (pkg: string, method?: string): Workerized => {
+ const str = cws(pkg, method);
+ return (workerData, transferable) => {
+ const worker = new Worker(str, {
+ eval: true,
+ workerData,
+ transferList: transferable
+ });
+ let terminated = false;
+ return {
+ timeout(ms: number) {
+ const tm = setTimeout(() => {
+ worker.terminate();
+ terminated = true;
+ }, ms);
+ worker.once('message', () => clearTimeout(tm));
+ },
+ then(res, rej) {
+ return new Promise((res, rej) => {
+ worker
+ .once('message', msg => {
+ if (msg.err) rej(msg.err);
+ res(msg);
+ })
+ .once('error', rej)
+ .once('exit', code => {
+ if (terminated) rej(new Error('Timed out'));
+ else if (code !== 0) rej(new Error('Exited with status code ' + code));
+ });
+ }).then(res, rej);
+ }
+ };
+ }
+}
+
+const fflate = resolve(__dirname, '..');
+
+export const workers = {
+ fflate: {
+ deflate: wc(fflate, 'deflateSync'),
+ inflate: wc(fflate, 'inflateSync'),
+ gzip: wc(fflate, 'gzipSync'),
+ gunzip: wc(fflate, 'gunzipSync'),
+ zlib: wc(fflate, 'zlibSync'),
+ unzlib: wc(fflate, 'unzlibSync'),
+ zip: wc(fflate, 'zipSync'),
+ unzip: wc(fflate, 'unzipSync')
+ },
+ pako: {
+ deflate: wc('pako', 'deflateRaw'),
+ inflate: wc('pako', 'inflateRaw'),
+ gzip: wc('pako', 'gzip'),
+ gunzip: wc('pako', 'ungzip'),
+ zlib: wc('pako', 'deflate'),
+ unzlib: wc('pako', 'inflate')
+ },
+ uzip: {
+ deflate: wc('uzip', 'deflateRaw'),
+ inflate: wc('uzip', 'inflateRaw')
+ },
+ tinyInflate: {
+ inflate: wc('tiny-inflate')
+ },
+ zlib: {
+ deflate: wc('zlib', 'deflateRawSync'),
+ inflate: wc('zlib', 'inflateRawSync'),
+ gzip: wc('zlib', 'gzipSync'),
+ gunzip: wc('zlib', 'gunzipSync'),
+ zlib: wc('zlib', 'deflateSync'),
+ unzlib: wc('zlib', 'inflateSync')
+ }
+};
+
+export const bClone = (buf: Buffer) => {
+ const clone = Buffer.allocUnsafe(buf.length);
+ clone.set(buf);
+ return clone;
+} \ No newline at end of file