test(web): make batch limits independently testable

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Avijit SinghandCopilot committed 2026-10-15 06:39:30 +05:30
1 parent cd3781c1cf
commit 297b0dfacf
6 files changed
+65 -36

No files matched your search

+3 -2
View File
@@ -25,7 +25,8 @@ jobs:
- name: Check browser module syntax
run: |
node --check demo/web/app.mjs
node --check demo/web/batch.mjs
node --check demo/web/output-directory.mjs
node --check demo/web/worker.mjs
- name: Test folder output recovery
run: node --test demo/web/tests/output-directory.test.mjs
- name: Test browser converter helpers
run: node --test demo/web/tests/batch.test.mjs demo/web/tests/output-directory.test.mjs
+2 -22
View File
@@ -1,8 +1,9 @@
import { BlobReader, Uint8ArrayReader, ZipReader, ZipWriter, configure } from "https://cdn.jsdelivr.net/npm/@zip.js/[email protected]/lib/zip-no-worker.js/+esm";
import { batchMemoryEstimate, batchOutputDirectory, MEMORY_LIMIT, memoryEstimate } from "./batch.mjs";
configure({ useWebWorkers: false, useCompressionStream: false, chunkSize: 65536 });
export const MEMORY_LIMIT = 256 * 1024 * 1024;
export { batchMemoryEstimate, batchOutputDirectory, memoryEstimate, MEMORY_LIMIT };
export const CONVERSION_LIMIT = 256 * 1024 * 1024;
export const ICON_LIMIT = 16 * 1024 * 1024;
const MAX_ENTRIES = 100000;
@@ -204,27 +205,6 @@ export class Archive {
async close() { await this.reader.close(); }
}
export function memoryEstimate(archiveBytes, selectedBytes, libraryBytes, entries) {
return safeSize(Math.ceil(archiveBytes * 1.02) + selectedBytes * 4 + libraryBytes + entries * 1024 + 8 * 1024 * 1024);
}
export function batchMemoryEstimate(summaries, target, libraryBytes) {
if (target !== "linux" && target !== "windows") throw new Error(`Unsupported conversion target: ${target}`);
return summaries.reduce((total, summary) => safeSize(total + memoryEstimate(
summary.totalBytes,
target === "windows" ? summary.windowsSelectedBytes : summary.selectedBytes,
libraryBytes,
summary.files
)), 0);
}
export function batchOutputDirectory(name, index) {
if (!Number.isSafeInteger(index) || index < 0) throw new Error("Invalid app index");
const stem = String(name).replace(/\.zip$/iu, "").normalize("NFKC")
.replace(/[^A-Za-z0-9._-]+/gu, "-").replace(/^[.-]+/u, "").slice(0, 80).replace(/[. -]+$/u, "");
return `${String(index + 1).padStart(2, "0")}-${stem || "app"}`;
}
export function createPackageWriter(destination) {
return new ZipWriter(destination, { level: 9, zip64: true, bufferedWrite: false, useWebWorkers: false, useCompressionStream: false, preventClose: true });
}
+27
View File
@@ -0,0 +1,27 @@
export const MEMORY_LIMIT = 256 * 1024 * 1024;
function safeSize(value) {
if (!Number.isSafeInteger(value) || value < 0) throw new Error("ZIP contains an unsupported file size");
return value;
}
export function memoryEstimate(archiveBytes, selectedBytes, libraryBytes, entries) {
return safeSize(Math.ceil(archiveBytes * 1.02) + selectedBytes * 4 + libraryBytes + entries * 1024 + 8 * 1024 * 1024);
}
export function batchMemoryEstimate(summaries, target, libraryBytes) {
if (target !== "linux" && target !== "windows") throw new Error(`Unsupported conversion target: ${target}`);
return summaries.reduce((total, summary) => safeSize(total + memoryEstimate(
summary.totalBytes,
target === "windows" ? summary.windowsSelectedBytes : summary.selectedBytes,
libraryBytes,
summary.files
)), 0);
}
export function batchOutputDirectory(name, index) {
if (!Number.isSafeInteger(index) || index < 0) throw new Error("Invalid app index");
const stem = String(name).replace(/\.zip$/iu, "").normalize("NFKC")
.replace(/[^A-Za-z0-9._-]+/gu, "-").replace(/^[.-]+/u, "").slice(0, 80).replace(/[. -]+$/u, "");
return `${String(index + 1).padStart(2, "0")}-${stem || "app"}`;
}
+1 -11
View File
@@ -7,7 +7,7 @@ import { register } from "node:module";
register("./cdn-loader.mjs", import.meta.url, { data: { url: process.env.ANYPS5_TEST_ZIP_URL, path: process.env.ANYPS5_TEST_ZIP_PATH } });
const site = name => pathToFileURL(path.join(process.argv[2], name)).href;
const { Archive, batchMemoryEstimate, batchOutputDirectory, memoryEstimate, MEMORY_LIMIT, ICON_LIMIT, packageArchive } = await import(site("archive.mjs"));
const { Archive, memoryEstimate, MEMORY_LIMIT, ICON_LIMIT, packageArchive } = await import(site("archive.mjs"));
const { BlobReader, BlobWriter, Uint8ArrayReader, Uint8ArrayWriter, ZipReader, ZipWriter } = await import(process.env.ANYPS5_TEST_ZIP_URL);
await import("./archive-reserved.test.mjs");
const { default: createRelinker } = await import(site("native/relinker_wasm.mjs"));
@@ -157,16 +157,6 @@ assert.equal(selfModuleSummary.warnings.length, 2);
assert.match(selfModuleSummary.warnings.join(" "), /SELF container/u);
await selfModule.close();
assert(memoryEstimate(200 * 1024 ** 3, 1024, 1024, 100) > MEMORY_LIMIT);
assert(batchMemoryEstimate([
{ totalBytes: 1024, selectedBytes: 128, windowsSelectedBytes: 256, files: 2 },
{ totalBytes: 2048, selectedBytes: 256, windowsSelectedBytes: 512, files: 3 }
], "linux", 1024) > memoryEstimate(2048, 256, 1024, 3));
assert.equal(batchOutputDirectory("First App.zip", 0), "01-First-App");
assert.equal(batchOutputDirectory("First App.zip", 1), "02-First-App");
assert.equal(batchOutputDirectory("CON.zip", 0), "01-CON");
assert.equal(batchOutputDirectory("...zip", 0), "01-app");
assert.throws(() => batchOutputDirectory("app.zip", -1), /Invalid app index/u);
assert.throws(() => batchMemoryEstimate([], "macos", 0), /Unsupported conversion target/u);
const invalid = native.Convert({ target: "unsupported" }, () => {});
assert.equal(invalid.ok, false);
console.log("Archive validation, missing-folder warnings and large-download budget verified");
+30
View File
@@ -0,0 +1,30 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { batchMemoryEstimate, batchOutputDirectory, memoryEstimate, MEMORY_LIMIT } from "../batch.mjs";
test("estimates all ZIP outputs against one shared memory limit", () => {
const summaries = [
{ totalBytes: 1024, selectedBytes: 128, windowsSelectedBytes: 256, files: 2 },
{ totalBytes: 2048, selectedBytes: 256, windowsSelectedBytes: 512, files: 3 }
];
assert.equal(batchMemoryEstimate(summaries, "linux", 1024), summaries.reduce(
(total, summary) => total + memoryEstimate(summary.totalBytes, summary.selectedBytes, 1024, summary.files),
0
));
assert.equal(batchMemoryEstimate(summaries, "windows", 1024), summaries.reduce(
(total, summary) => total + memoryEstimate(summary.totalBytes, summary.windowsSelectedBytes, 1024, summary.files),
0
));
assert.throws(() => batchMemoryEstimate([], "macos", 0), /Unsupported conversion target/u);
assert.throws(() => batchMemoryEstimate([{ totalBytes: Number.MAX_SAFE_INTEGER, selectedBytes: 0, windowsSelectedBytes: 0, files: 0 }], "linux", 0), /unsupported file size/u);
assert.equal(MEMORY_LIMIT, 256 * 1024 * 1024);
});
test("creates safe and distinct ZIP folder names for duplicate app filenames", () => {
assert.equal(batchOutputDirectory("First App.zip", 0), "01-First-App");
assert.equal(batchOutputDirectory("First App.zip", 1), "02-First-App");
assert.equal(batchOutputDirectory("CON.zip", 0), "01-CON");
assert.equal(batchOutputDirectory("...zip", 0), "01-app");
assert.throws(() => batchOutputDirectory("app.zip", -1), /Invalid app index/u);
});
+2 -1
View File
@@ -1,4 +1,5 @@
import { Archive, batchMemoryEstimate, batchOutputDirectory, CONVERSION_LIMIT, MEMORY_LIMIT, createPackageWriter, packageArchive } from "./archive.mjs";
import { batchMemoryEstimate, batchOutputDirectory, MEMORY_LIMIT } from "./batch.mjs";
import { Archive, CONVERSION_LIMIT, createPackageWriter, packageArchive } from "./archive.mjs";
let archive;
let inputs = [];