util.ts 6.4 KB

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