util.ts 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import { existsSync, readFile, writeFile } from 'fs';
  2. import { resolve } from 'path';
  3. import { get } from 'https';
  4. import { suite } from 'uvu';
  5. import { performance } from 'perf_hooks';
  6. import { Worker } from 'worker_threads';
  7. const testFiles = {
  8. basic: Buffer.from('Hello world!'),
  9. text: 'https://www.gutenberg.org/files/2701/old/moby10b.txt',
  10. smallImage: 'https://hlevkin.com/hlevkin/TestImages/new/Rainier.bmp',
  11. image: 'https://www.hlevkin.com/hlevkin/TestImages/new/Maltese.bmp',
  12. largeImage: 'https://www.hlevkin.com/hlevkin/TestImages/new/Sunrise.bmp'
  13. };
  14. const testZipFiles = {
  15. model3D: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/kmz/Box.kmz',
  16. largeModel3D: 'https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/models/3mf/truck.3mf'
  17. };
  18. const dlCached = async <T extends Record<string, string | Buffer>>(files: T) => {
  19. let res = {} as Record<keyof T, Buffer>;
  20. for (const name in files) {
  21. let data: string | Buffer = files[name];
  22. if (typeof data == 'string') {
  23. const fn = resolve(__dirname, 'data', name);
  24. if (!existsSync(fn)) {
  25. console.log('\nDownloading ' + data + '...');
  26. data = await new Promise((r, re) => get(data as string, res => {
  27. const len = +res.headers['content-length'];
  28. const buf = Buffer.allocUnsafe(len);
  29. let i = 0;
  30. res.on('data', chunk => {
  31. buf.set(chunk, i);
  32. console.log((100 * (i += chunk.length) / len).toFixed(1) + '%\x1B[1A');
  33. });
  34. res.on('error', re);
  35. res.on('end', () => {
  36. console.log('Complete');
  37. writeFile(fn, buf, () => r(buf));
  38. });
  39. }));
  40. } else {
  41. data = await new Promise((res, rej) =>
  42. readFile(fn, (err, buf) => err ? rej(err) : res(buf))
  43. );
  44. }
  45. }
  46. res[name as keyof T] = data as Buffer;
  47. }
  48. return res;
  49. }
  50. const testFilesPromise = dlCached(testFiles);
  51. const testZipFilesPromise = dlCached(testZipFiles);
  52. export type TestHandler = (file: Buffer, name: string, resetTimer: () => void) => unknown | Promise<unknown>;
  53. export const testSuites = async <T extends Record<string, TestHandler>, D extends 'zip' | 'default' = 'default'>(suites: T, type?: D) => {
  54. type DK = keyof (D extends 'zip' ? typeof testZipFiles : typeof testFiles);
  55. const tf = type == 'zip' ? testZipFiles : testFiles;
  56. const tfp = type == 'zip' ? testZipFilesPromise : testFilesPromise;
  57. const perf = {} as Record<keyof T, Promise<Record<DK, number>>>;
  58. for (const k in suites) {
  59. perf[k] = new Promise(async setPerf => {
  60. const ste = suite(k);
  61. let localTestFiles: Record<DK, Buffer>;
  62. ste.before(async () => {
  63. localTestFiles = (await tfp) as unknown as Record<DK, Buffer>;
  64. });
  65. const localPerf = {} as Record<DK, number>;
  66. for (const name in tf) {
  67. ste(name, async () => {
  68. let ts = performance.now();
  69. await suites[k](localTestFiles[name], name, () => {
  70. ts = performance.now();
  71. });
  72. localPerf[name] = performance.now() - ts;
  73. });
  74. }
  75. ste.after(() => {
  76. setPerf(localPerf);
  77. });
  78. ste.run();
  79. })
  80. }
  81. const resolvedPerf = {} as Record<keyof T, Record<DK, number>>;
  82. for (const k in suites) resolvedPerf[k] = await perf[k];
  83. return resolvedPerf;
  84. };
  85. export const stream = (src: Uint8Array, dst: {
  86. push(dat: Uint8Array, final: boolean): void;
  87. }) => {
  88. for (let i = 0; i < src.length;) {
  89. const off = Math.floor(Math.random() * Math.min(131072, src.length >>> 3));
  90. dst.push(src.slice(i, i + off), (i += off) >= src.length);
  91. }
  92. }
  93. // create worker string
  94. const cws = (pkg: string, method: string = '_cjsDefault') => `
  95. const ${method == '_cjsDefault' ? method : `{ ${method} }`} = require('${pkg}');
  96. const { Worker, workerData, parentPort } = require('worker_threads');
  97. try {
  98. const buf = ${method}(...(Array.isArray(workerData) ? workerData : [workerData]));
  99. parentPort.postMessage(buf, [buf.buffer]);
  100. } catch (err) {
  101. parentPort.postMessage({ err });
  102. }
  103. `;
  104. export type Workerized = (workerData: Uint8Array | [Uint8Array, {}], transferable?: ArrayBuffer[]) => WorkerizedResult;
  105. export interface WorkerizedResult extends PromiseLike<Uint8Array> {
  106. timeout(ms: number): void;
  107. };
  108. // Worker creator
  109. const wc = (pkg: string, method?: string): Workerized => {
  110. const str = cws(pkg, method);
  111. return (workerData, transferable) => {
  112. const worker = new Worker(str, {
  113. eval: true,
  114. workerData,
  115. transferList: transferable
  116. });
  117. let terminated = false;
  118. return {
  119. timeout(ms: number) {
  120. const tm = setTimeout(() => {
  121. worker.terminate();
  122. terminated = true;
  123. }, ms);
  124. worker.once('message', () => clearTimeout(tm));
  125. },
  126. then(res, rej) {
  127. return new Promise((res, rej) => {
  128. worker
  129. .once('message', msg => {
  130. if (msg.err) rej(msg.err);
  131. res(msg);
  132. })
  133. .once('error', rej)
  134. .once('exit', code => {
  135. if (terminated) rej(new Error('Timed out'));
  136. else if (code !== 0) rej(new Error('Exited with status code ' + code));
  137. });
  138. }).then(res, rej);
  139. }
  140. };
  141. }
  142. }
  143. const fflate = resolve(__dirname, '..');
  144. export const workers = {
  145. fflate: {
  146. deflate: wc(fflate, 'deflateSync'),
  147. inflate: wc(fflate, 'inflateSync'),
  148. gzip: wc(fflate, 'gzipSync'),
  149. gunzip: wc(fflate, 'gunzipSync'),
  150. zlib: wc(fflate, 'zlibSync'),
  151. unzlib: wc(fflate, 'unzlibSync'),
  152. zip: wc(fflate, 'zipSync'),
  153. unzip: wc(fflate, 'unzipSync')
  154. },
  155. pako: {
  156. deflate: wc('pako', 'deflateRaw'),
  157. inflate: wc('pako', 'inflateRaw'),
  158. gzip: wc('pako', 'gzip'),
  159. gunzip: wc('pako', 'ungzip'),
  160. zlib: wc('pako', 'deflate'),
  161. unzlib: wc('pako', 'inflate')
  162. },
  163. uzip: {
  164. deflate: wc('uzip', 'deflateRaw'),
  165. inflate: wc('uzip', 'inflateRaw')
  166. },
  167. tinyInflate: {
  168. inflate: wc('tiny-inflate')
  169. },
  170. zlib: {
  171. deflate: wc('zlib', 'deflateRawSync'),
  172. inflate: wc('zlib', 'inflateRawSync'),
  173. gzip: wc('zlib', 'gzipSync'),
  174. gunzip: wc('zlib', 'gunzipSync'),
  175. zlib: wc('zlib', 'deflateSync'),
  176. unzlib: wc('zlib', 'inflateSync')
  177. }
  178. };
  179. export const bClone = (buf: Buffer) => {
  180. const clone = Buffer.allocUnsafe(buf.length);
  181. clone.set(buf);
  182. return clone;
  183. }