media:cpp docs | http | tests
This commit is contained in:
parent
c448bfb626
commit
96e21b2282
@ -298,17 +298,30 @@ export function renderMarkdownReport(payload) {
|
||||
.replace(/\r\n/g, '<br>')
|
||||
.replace(/\n/g, '<br>');
|
||||
|
||||
const fmtBytesCell = (n) => {
|
||||
if (n == null || n === '') return '—';
|
||||
const num = Number(n);
|
||||
if (!Number.isFinite(num)) return escCell(String(n));
|
||||
return `${formatBytes(num)} (${num} B)`;
|
||||
};
|
||||
const fmtMsCell = (ms) => {
|
||||
if (ms == null || ms === '') return '—';
|
||||
const n = Number(ms);
|
||||
if (!Number.isFinite(n)) return '—';
|
||||
return String(Math.round(n * 100) / 100);
|
||||
};
|
||||
|
||||
if (Array.isArray(payload.images) && payload.images.length > 0) {
|
||||
lines.push('## Images & transfers');
|
||||
lines.push('');
|
||||
lines.push('| Label | Size | Dimensions / detail |');
|
||||
lines.push('| --- | --- | --- |');
|
||||
lines.push('| Label | Input | Output | Time (ms) | Detail |');
|
||||
lines.push('| --- | --- | --- | ---: | --- |');
|
||||
for (const row of payload.images) {
|
||||
const label = escCell(row.label ?? '—');
|
||||
const bytes =
|
||||
row.bytes != null && row.bytes !== ''
|
||||
? `${formatBytes(Number(row.bytes))} (${row.bytes} B)`
|
||||
: '—';
|
||||
const outRaw = row.outputBytes != null ? row.outputBytes : row.bytes;
|
||||
const inputCol = fmtBytesCell(row.inputBytes);
|
||||
const outputCol = fmtBytesCell(outRaw);
|
||||
const timeCol = fmtMsCell(row.computeMs != null ? row.computeMs : row.ms);
|
||||
const parts = [];
|
||||
if (row.widthPx != null && row.heightPx != null) {
|
||||
parts.push(`${row.widthPx}×${row.heightPx} px`);
|
||||
@ -317,7 +330,7 @@ export function renderMarkdownReport(payload) {
|
||||
if (row.note) parts.push(String(row.note));
|
||||
if (row.detail) parts.push(String(row.detail));
|
||||
const detailCol = parts.length ? escCell(parts.join(' · ')) : '—';
|
||||
lines.push(`| ${label} | ${bytes} | ${detailCol} |`);
|
||||
lines.push(`| ${label} | ${inputCol} | ${outputCol} | ${timeCol} | ${detailCol} |`);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ function registerFixtureImages(rep, assetsDir) {
|
||||
const d = describePngFile(p);
|
||||
rep.addImage({
|
||||
label,
|
||||
bytes: d.bytes,
|
||||
inputBytes: d.bytes,
|
||||
widthPx: d.widthPx,
|
||||
heightPx: d.heightPx,
|
||||
note: 'on-disk fixture',
|
||||
@ -171,7 +171,7 @@ function timeSync(rep, label, fn, detailFn) {
|
||||
const result = fn();
|
||||
const ms = performance.now() - t0;
|
||||
rep.step(label, ms, detailFn ? detailFn(result) : '');
|
||||
return result;
|
||||
return { result, ms };
|
||||
}
|
||||
|
||||
async function timeAsync(rep, label, fn, detailFn) {
|
||||
@ -179,7 +179,7 @@ async function timeAsync(rep, label, fn, detailFn) {
|
||||
const result = await fn();
|
||||
const ms = performance.now() - t0;
|
||||
rep.step(label, ms, detailFn ? detailFn(result) : '');
|
||||
return result;
|
||||
return { result, ms };
|
||||
}
|
||||
|
||||
function writeTestReportFile(rep, metricsCollector, startedAtIso) {
|
||||
@ -220,9 +220,10 @@ function writeTestReportFile(rep, metricsCollector, startedAtIso) {
|
||||
async function multipartResizeTests(base, inPng, rep) {
|
||||
const pngBytes = readFileSync(inPng);
|
||||
const srcDim = pngDimensionsFromBuffer(pngBytes);
|
||||
const inPngSize = fileByteSize(inPng) ?? pngBytes.length;
|
||||
rep.addImage({
|
||||
label: 'multipart upload source (PNG body in form)',
|
||||
bytes: pngBytes.length,
|
||||
inputBytes: inPngSize,
|
||||
widthPx: srcDim?.width ?? null,
|
||||
heightPx: srcDim?.height ?? null,
|
||||
note: 'fixture bytes attached to each multipart request',
|
||||
@ -233,25 +234,30 @@ async function multipartResizeTests(base, inPng, rep) {
|
||||
form.append('file', blob(), 'square-64.png');
|
||||
form.append('max_width', '32');
|
||||
form.append('max_height', '32');
|
||||
const r1 = await timeAsync(
|
||||
const { result: r1, ms: r1Ms } = await timeAsync(
|
||||
rep,
|
||||
'multipart POST (file → jpeg)',
|
||||
() =>
|
||||
fetch(`${base}/v1/resize`, {
|
||||
async () => {
|
||||
const res = await fetch(`${base}/v1/resize`, {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
signal: AbortSignal.timeout(timeouts.httpMs),
|
||||
}),
|
||||
(res) => `HTTP ${res.status}, ${res.headers.get('content-type') ?? ''}`,
|
||||
});
|
||||
const ab = await res.arrayBuffer();
|
||||
return { res, ab };
|
||||
},
|
||||
(x) => `HTTP ${x.res.status}, ${x.res.headers.get('content-type') ?? ''}`,
|
||||
);
|
||||
assert(r1.ok, 'multipart: field file');
|
||||
assert(r1.headers.get('content-type') === 'image/jpeg', 'multipart default format → image/jpeg');
|
||||
assert(r1.res.ok, 'multipart: field file');
|
||||
assert(r1.res.headers.get('content-type') === 'image/jpeg', 'multipart default format → image/jpeg');
|
||||
{
|
||||
const ab = await r1.arrayBuffer();
|
||||
const ab = r1.ab;
|
||||
rep.addImage({
|
||||
label: 'multipart HTTP response (→ jpeg)',
|
||||
bytes: ab.byteLength,
|
||||
contentType: r1.headers.get('content-type') ?? '',
|
||||
inputBytes: inPngSize,
|
||||
outputBytes: ab.byteLength,
|
||||
computeMs: r1Ms,
|
||||
contentType: r1.res.headers.get('content-type') ?? '',
|
||||
note: 'resized output body',
|
||||
});
|
||||
assert(Buffer.from(ab).length > 0, 'multipart file: non-empty body');
|
||||
@ -261,25 +267,30 @@ async function multipartResizeTests(base, inPng, rep) {
|
||||
fImage.append('image', blob(), 'x.png');
|
||||
fImage.append('max_width', '24');
|
||||
fImage.append('format', 'webp');
|
||||
const r2 = await timeAsync(
|
||||
const { result: r2, ms: r2Ms } = await timeAsync(
|
||||
rep,
|
||||
'multipart POST (image → webp)',
|
||||
() =>
|
||||
fetch(`${base}/v1/resize`, {
|
||||
async () => {
|
||||
const res = await fetch(`${base}/v1/resize`, {
|
||||
method: 'POST',
|
||||
body: fImage,
|
||||
signal: AbortSignal.timeout(timeouts.httpMs),
|
||||
}),
|
||||
(res) => `HTTP ${res.status}, ${res.headers.get('content-type') ?? ''}`,
|
||||
});
|
||||
const ab = await res.arrayBuffer();
|
||||
return { res, ab };
|
||||
},
|
||||
(x) => `HTTP ${x.res.status}, ${x.res.headers.get('content-type') ?? ''}`,
|
||||
);
|
||||
assert(r2.ok, 'multipart: field image + format webp');
|
||||
assert(r2.headers.get('content-type') === 'image/webp', 'multipart webp → image/webp');
|
||||
assert(r2.res.ok, 'multipart: field image + format webp');
|
||||
assert(r2.res.headers.get('content-type') === 'image/webp', 'multipart webp → image/webp');
|
||||
{
|
||||
const ab = await r2.arrayBuffer();
|
||||
const ab = r2.ab;
|
||||
rep.addImage({
|
||||
label: 'multipart HTTP response (→ webp)',
|
||||
bytes: ab.byteLength,
|
||||
contentType: r2.headers.get('content-type') ?? '',
|
||||
inputBytes: inPngSize,
|
||||
outputBytes: ab.byteLength,
|
||||
computeMs: r2Ms,
|
||||
contentType: r2.res.headers.get('content-type') ?? '',
|
||||
note: 'resized output body',
|
||||
});
|
||||
assert(Buffer.from(ab).byteLength > 8, 'multipart webp: RIFF/WebP header size');
|
||||
@ -288,25 +299,30 @@ async function multipartResizeTests(base, inPng, rep) {
|
||||
const fUpload = new FormData();
|
||||
fUpload.append('upload', blob(), 'up.png');
|
||||
fUpload.append('max_width', '20');
|
||||
const r3 = await timeAsync(
|
||||
const { result: r3, ms: r3Ms } = await timeAsync(
|
||||
rep,
|
||||
'multipart POST (upload alias → jpeg)',
|
||||
() =>
|
||||
fetch(`${base}/v1/resize`, {
|
||||
async () => {
|
||||
const res = await fetch(`${base}/v1/resize`, {
|
||||
method: 'POST',
|
||||
body: fUpload,
|
||||
signal: AbortSignal.timeout(timeouts.httpMs),
|
||||
}),
|
||||
(res) => `HTTP ${res.status}, ${res.headers.get('content-type') ?? ''}`,
|
||||
});
|
||||
const ab = await res.arrayBuffer();
|
||||
return { res, ab };
|
||||
},
|
||||
(x) => `HTTP ${x.res.status}, ${x.res.headers.get('content-type') ?? ''}`,
|
||||
);
|
||||
assert(r3.ok, 'multipart: field upload');
|
||||
assert(r3.headers.get('content-type') === 'image/jpeg', 'multipart upload alias → jpeg');
|
||||
assert(r3.res.ok, 'multipart: field upload');
|
||||
assert(r3.res.headers.get('content-type') === 'image/jpeg', 'multipart upload alias → jpeg');
|
||||
{
|
||||
const ab = await r3.arrayBuffer();
|
||||
const ab = r3.ab;
|
||||
rep.addImage({
|
||||
label: 'multipart HTTP response (upload field → jpeg)',
|
||||
bytes: ab.byteLength,
|
||||
contentType: r3.headers.get('content-type') ?? '',
|
||||
inputBytes: inPngSize,
|
||||
outputBytes: ab.byteLength,
|
||||
computeMs: r3Ms,
|
||||
contentType: r3.res.headers.get('content-type') ?? '',
|
||||
note: 'resized output body',
|
||||
});
|
||||
assert(Buffer.from(ab).length > 0, 'multipart upload: non-empty body');
|
||||
@ -316,54 +332,62 @@ async function multipartResizeTests(base, inPng, rep) {
|
||||
fPng.append('file', blob(), 'square-64.png');
|
||||
fPng.append('max_width', '16');
|
||||
fPng.append('format', 'png');
|
||||
const r4 = await timeAsync(
|
||||
const { result: r4, ms: r4Ms } = await timeAsync(
|
||||
rep,
|
||||
'multipart POST (file → png)',
|
||||
() =>
|
||||
fetch(`${base}/v1/resize`, {
|
||||
async () => {
|
||||
const res = await fetch(`${base}/v1/resize`, {
|
||||
method: 'POST',
|
||||
body: fPng,
|
||||
signal: AbortSignal.timeout(timeouts.httpMs),
|
||||
}),
|
||||
(res) => `HTTP ${res.status}, ${res.headers.get('content-type') ?? ''}`,
|
||||
});
|
||||
const ab = await res.arrayBuffer();
|
||||
return { res, ab };
|
||||
},
|
||||
(x) => `HTTP ${x.res.status}, ${x.res.headers.get('content-type') ?? ''}`,
|
||||
);
|
||||
assert(r4.ok, 'multipart: format png');
|
||||
assert(r4.headers.get('content-type') === 'image/png', 'multipart png → image/png');
|
||||
assert(r4.res.ok, 'multipart: format png');
|
||||
assert(r4.res.headers.get('content-type') === 'image/png', 'multipart png → image/png');
|
||||
{
|
||||
const ab = await r4.arrayBuffer();
|
||||
const ab = r4.ab;
|
||||
const dim = pngDimensionsFromBuffer(Buffer.from(ab));
|
||||
rep.addImage({
|
||||
label: 'multipart HTTP response (→ png)',
|
||||
bytes: ab.byteLength,
|
||||
inputBytes: inPngSize,
|
||||
outputBytes: ab.byteLength,
|
||||
computeMs: r4Ms,
|
||||
widthPx: dim?.width ?? null,
|
||||
heightPx: dim?.height ?? null,
|
||||
contentType: r4.headers.get('content-type') ?? '',
|
||||
contentType: r4.res.headers.get('content-type') ?? '',
|
||||
note: 'resized output body',
|
||||
});
|
||||
}
|
||||
|
||||
const noFile = new FormData();
|
||||
noFile.append('max_width', '10');
|
||||
const r5 = await timeAsync(
|
||||
const { result: r5, ms: r5Ms } = await timeAsync(
|
||||
rep,
|
||||
'multipart POST (no file → 400)',
|
||||
() =>
|
||||
fetch(`${base}/v1/resize`, {
|
||||
async () => {
|
||||
const res = await fetch(`${base}/v1/resize`, {
|
||||
method: 'POST',
|
||||
body: noFile,
|
||||
signal: AbortSignal.timeout(timeouts.httpMs),
|
||||
}),
|
||||
(res) => `HTTP ${res.status}`,
|
||||
});
|
||||
const errText = await res.text();
|
||||
return { res, errText };
|
||||
},
|
||||
(x) => `HTTP ${x.res.status}`,
|
||||
);
|
||||
assert(r5.status === 400, 'multipart without image → 400');
|
||||
const errText = await r5.text();
|
||||
assert(r5.res.status === 400, 'multipart without image → 400');
|
||||
rep.addImage({
|
||||
label: 'multipart HTTP 400 error body',
|
||||
bytes: Buffer.byteLength(errText, 'utf8'),
|
||||
contentType: r5.headers.get('content-type') ?? '',
|
||||
outputBytes: Buffer.byteLength(r5.errText, 'utf8'),
|
||||
computeMs: r5Ms,
|
||||
contentType: r5.res.headers.get('content-type') ?? '',
|
||||
note: 'JSON error',
|
||||
});
|
||||
const jErr = JSON.parse(errText);
|
||||
const jErr = JSON.parse(r5.errText);
|
||||
assert(jErr?.error && typeof jErr.error === 'string', 'multipart 400 JSON error body');
|
||||
}
|
||||
|
||||
@ -383,7 +407,7 @@ async function suiteMultipartOnly(assetsDir, rep) {
|
||||
try {
|
||||
await timeAsync(rep, 'wait until HTTP server accepts', () => waitListen('127.0.0.1', port, 'serve'), () => `127.0.0.1:${port}`);
|
||||
const base = `http://127.0.0.1:${port}`;
|
||||
const h = await timeAsync(
|
||||
const { result: h } = await timeAsync(
|
||||
rep,
|
||||
'GET /health',
|
||||
() => fetch(`${base}/health`, { signal: AbortSignal.timeout(timeouts.httpMs) }),
|
||||
@ -418,7 +442,7 @@ async function suiteRest(assetsDir, rep) {
|
||||
await timeAsync(rep, 'wait until HTTP server accepts', () => waitListen('127.0.0.1', port, 'serve'), () => `127.0.0.1:${port}`);
|
||||
|
||||
const base = `http://127.0.0.1:${port}`;
|
||||
const h = await timeAsync(
|
||||
const { result: h } = await timeAsync(
|
||||
rep,
|
||||
'GET /health',
|
||||
() => fetch(`${base}/health`, { signal: AbortSignal.timeout(timeouts.httpMs) }),
|
||||
@ -430,11 +454,12 @@ async function suiteRest(assetsDir, rep) {
|
||||
|
||||
const outDir = mkdtempSync(join(tmpdir(), 'media-rest-'));
|
||||
const outPng = join(outDir, 'out-32.png');
|
||||
const r1 = await timeAsync(
|
||||
const inPngBytes = fileByteSize(inPng);
|
||||
const { result: r1, ms: r1Ms } = await timeAsync(
|
||||
rep,
|
||||
'POST /v1/resize JSON → PNG on disk',
|
||||
() =>
|
||||
fetch(`${base}/v1/resize`, {
|
||||
async () => {
|
||||
const res = await fetch(`${base}/v1/resize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@ -444,18 +469,23 @@ async function suiteRest(assetsDir, rep) {
|
||||
max_height: 32,
|
||||
}),
|
||||
signal: AbortSignal.timeout(timeouts.httpMs),
|
||||
}),
|
||||
(res) => `HTTP ${res.status}`,
|
||||
});
|
||||
const j = await res.json();
|
||||
return { res, j };
|
||||
},
|
||||
(x) => `HTTP ${x.res.status}`,
|
||||
);
|
||||
assert(r1.ok, 'POST /v1/resize ok');
|
||||
const j1 = await r1.json();
|
||||
assert(r1.res.ok, 'POST /v1/resize ok');
|
||||
const j1 = r1.j;
|
||||
assert(j1?.ok === true, 'resize response ok');
|
||||
assert(existsSync(outPng), 'output png exists');
|
||||
{
|
||||
const d = describePngFile(outPng);
|
||||
rep.addImage({
|
||||
label: 'REST JSON → disk (out-32.png)',
|
||||
bytes: d.bytes,
|
||||
inputBytes: inPngBytes,
|
||||
outputBytes: d.bytes,
|
||||
computeMs: r1Ms,
|
||||
widthPx: d.widthPx,
|
||||
heightPx: d.heightPx,
|
||||
note: 'server wrote from JSON resize',
|
||||
@ -463,11 +493,11 @@ async function suiteRest(assetsDir, rep) {
|
||||
}
|
||||
|
||||
const outJpg = join(outDir, 'out.jpg');
|
||||
const r2 = await timeAsync(
|
||||
const { result: r2, ms: r2Ms } = await timeAsync(
|
||||
rep,
|
||||
'POST /v1/resize JSON → JPEG on disk',
|
||||
() =>
|
||||
fetch(`${base}/v1/resize`, {
|
||||
async () => {
|
||||
const res = await fetch(`${base}/v1/resize`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
@ -477,17 +507,22 @@ async function suiteRest(assetsDir, rep) {
|
||||
format: 'jpeg',
|
||||
}),
|
||||
signal: AbortSignal.timeout(timeouts.httpMs),
|
||||
}),
|
||||
(res) => `HTTP ${res.status}`,
|
||||
});
|
||||
const j = await res.json();
|
||||
return { res, j };
|
||||
},
|
||||
(x) => `HTTP ${x.res.status}`,
|
||||
);
|
||||
assert(r2.ok, 'POST /v1/resize jpeg');
|
||||
assert(r2.res.ok, 'POST /v1/resize jpeg');
|
||||
assert(existsSync(outJpg), 'output jpg exists');
|
||||
{
|
||||
const b = fileByteSize(outJpg);
|
||||
if (b != null) {
|
||||
rep.addImage({
|
||||
label: 'REST JSON → disk (out.jpg)',
|
||||
bytes: b,
|
||||
inputBytes: inPngBytes,
|
||||
outputBytes: b,
|
||||
computeMs: r2Ms,
|
||||
contentType: 'image/jpeg',
|
||||
note: 'server wrote from JSON resize',
|
||||
});
|
||||
@ -496,7 +531,7 @@ async function suiteRest(assetsDir, rep) {
|
||||
|
||||
await multipartResizeTests(base, inPng, rep);
|
||||
|
||||
const bad = await timeAsync(
|
||||
const { result: bad } = await timeAsync(
|
||||
rep,
|
||||
'POST /v1/resize JSON missing input file → 500',
|
||||
() =>
|
||||
@ -537,7 +572,7 @@ async function suiteDstTemplateRest(assetsDir, rep) {
|
||||
const outDir = mkdtempSync(join(tmpdir(), 'media-dst-rest-'));
|
||||
|
||||
const outPattern = join(outDir, '${SRC_NAME}_thumb.webp');
|
||||
const rt = await timeAsync(
|
||||
const { result: rt } = await timeAsync(
|
||||
rep,
|
||||
'POST ${SRC_NAME}_thumb.webp template',
|
||||
() =>
|
||||
@ -561,7 +596,7 @@ async function suiteDstTemplateRest(assetsDir, rep) {
|
||||
assert(existsSync(expectedWebp), `expected ${expectedWebp}`);
|
||||
|
||||
const outAmp = join(outDir, '&{SRC_NAME}_tiny.png');
|
||||
const r2 = await timeAsync(
|
||||
const { result: r2 } = await timeAsync(
|
||||
rep,
|
||||
'POST &{SRC_NAME} + expand_glob false',
|
||||
() =>
|
||||
@ -583,7 +618,7 @@ async function suiteDstTemplateRest(assetsDir, rep) {
|
||||
|
||||
const subDir = join(assetsDir, 'tpl-sub');
|
||||
const outNested = join(subDir, '${SRC_NAME}_nested.png');
|
||||
const rn = await timeAsync(
|
||||
const { result: rn } = await timeAsync(
|
||||
rep,
|
||||
'POST nested ${SRC_NAME} template',
|
||||
() =>
|
||||
@ -604,7 +639,7 @@ async function suiteDstTemplateRest(assetsDir, rep) {
|
||||
assert(existsSync(expectedNested), `nested template ${expectedNested}`);
|
||||
|
||||
const outViaSrcDir = '${SRC_DIR}/tpl-from-srcdir/${SRC_NAME}_sd.webp';
|
||||
const rsd = await timeAsync(
|
||||
const { result: rsd } = await timeAsync(
|
||||
rep,
|
||||
'POST ${SRC_DIR} in output path',
|
||||
() =>
|
||||
@ -654,8 +689,8 @@ async function suiteIpcTcp(assetsDir, rep) {
|
||||
const outDir = mkdtempSync(join(tmpdir(), 'media-ipc-tcp-'));
|
||||
const outPng = join(outDir, 'ipc-out.png');
|
||||
|
||||
const sock = await timeAsync(rep, 'TCP connect (line 1)', () => connectTcp('127.0.0.1', port), () => '');
|
||||
const res = await timeAsync(
|
||||
const { result: sock } = await timeAsync(rep, 'TCP connect (line 1)', () => connectTcp('127.0.0.1', port), () => '');
|
||||
const { result: res, ms: ipcMs } = await timeAsync(
|
||||
rep,
|
||||
'IPC JSON line (ok resize)',
|
||||
() =>
|
||||
@ -676,17 +711,20 @@ async function suiteIpcTcp(assetsDir, rep) {
|
||||
assert(existsSync(outPng), 'IPC output file exists');
|
||||
{
|
||||
const d = describePngFile(outPng);
|
||||
const inB = fileByteSize(inPng);
|
||||
rep.addImage({
|
||||
label: 'IPC TCP → disk (ipc-out.png)',
|
||||
bytes: d.bytes,
|
||||
inputBytes: inB,
|
||||
outputBytes: d.bytes,
|
||||
computeMs: ipcMs,
|
||||
widthPx: d.widthPx,
|
||||
heightPx: d.heightPx,
|
||||
note: 'line-JSON resize',
|
||||
});
|
||||
}
|
||||
|
||||
const sock2 = await timeAsync(rep, 'TCP connect (line 2)', () => connectTcp('127.0.0.1', port), () => '');
|
||||
const res2 = await timeAsync(
|
||||
const { result: sock2 } = await timeAsync(rep, 'TCP connect (line 2)', () => connectTcp('127.0.0.1', port), () => '');
|
||||
const { result: res2 } = await timeAsync(
|
||||
rep,
|
||||
'IPC JSON line (missing input)',
|
||||
() =>
|
||||
@ -722,8 +760,8 @@ async function suiteDstTemplateIpcTcp(assetsDir, rep) {
|
||||
const outDir = mkdtempSync(join(tmpdir(), 'media-dst-ipc-'));
|
||||
const outPattern = join(outDir, '${SRC_NAME}_ipc.webp');
|
||||
|
||||
const sock = await timeAsync(rep, 'TCP connect', () => connectTcp('127.0.0.1', port), () => '');
|
||||
const res = await timeAsync(
|
||||
const { result: sock } = await timeAsync(rep, 'TCP connect', () => connectTcp('127.0.0.1', port), () => '');
|
||||
const { result: res } = await timeAsync(
|
||||
rep,
|
||||
'IPC JSON line (${SRC_NAME}_ipc.webp)',
|
||||
() =>
|
||||
@ -762,7 +800,7 @@ function suiteDstTemplateCli(assetsDir, rep) {
|
||||
|
||||
rep.beginSuite('CLI: resize --src / --dst templates');
|
||||
try {
|
||||
const r = timeSync(
|
||||
const { result: r } = timeSync(
|
||||
rep,
|
||||
'spawnSync resize (template dst)',
|
||||
() =>
|
||||
@ -804,7 +842,7 @@ function suiteUrlResizeCli(rep) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
const r = timeSync(
|
||||
const { result: r, ms: urlMs } = timeSync(
|
||||
rep,
|
||||
`spawnSync resize URL → ${expect}`,
|
||||
() =>
|
||||
@ -821,6 +859,13 @@ function suiteUrlResizeCli(rep) {
|
||||
);
|
||||
assert(r.status === 0, `resize URL exit 0 (${expect}): ${r.stderr || r.stdout}`);
|
||||
assert(existsSync(outPath), `expected output ${outPath}`);
|
||||
const outSz = fileByteSize(outPath);
|
||||
rep.addImage({
|
||||
label: `CLI URL resize → ${expect}`,
|
||||
outputBytes: outSz,
|
||||
computeMs: urlMs,
|
||||
note: 'HTTPS source + resize',
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
rep.endSuite();
|
||||
@ -838,26 +883,35 @@ function suiteGlobBatchCli(assetsDir, rep) {
|
||||
assert(existsSync(rootPng), `fixture ${rootPng}`);
|
||||
assert(existsSync(leafPng), `fixture ${leafPng}`);
|
||||
|
||||
const globIn = join(assetsDir, 'glob-in', '**', '*.png').replace(/\\/g, '/');
|
||||
const dstTmpl = '${SRC_DIR}/out/${SRC_NAME}_medium.jpg';
|
||||
const jobs = [
|
||||
{ src: rootPng, out: join(assetsDir, 'glob-in', 'out', 'root_medium.jpg') },
|
||||
{ src: leafPng, out: join(assetsDir, 'glob-in', 'sub', 'out', 'leaf_medium.jpg') },
|
||||
];
|
||||
|
||||
rep.beginSuite('CLI: recursive glob batch + dst templates');
|
||||
try {
|
||||
const r = timeSync(
|
||||
rep,
|
||||
'spawnSync resize (glob **/*.png → _medium.jpg)',
|
||||
() =>
|
||||
spawnSync(EXE, ['resize', '--src', globIn, '--dst', dstTmpl, '--max-width', '40', '--format', 'jpeg'], {
|
||||
encoding: 'utf8',
|
||||
}),
|
||||
(x) => `exit ${x.status}, 2 files`,
|
||||
);
|
||||
assert(r.status === 0, `glob batch exit 0, stderr: ${r.stderr}`);
|
||||
|
||||
const rootOut = join(assetsDir, 'glob-in', 'out', 'root_medium.jpg');
|
||||
const leafOut = join(assetsDir, 'glob-in', 'sub', 'out', 'leaf_medium.jpg');
|
||||
assert(existsSync(rootOut), `expected ${rootOut}`);
|
||||
assert(existsSync(leafOut), `expected ${leafOut}`);
|
||||
for (const job of jobs) {
|
||||
const srcAbs = resolve(job.src).replace(/\\/g, '/');
|
||||
const { result: r, ms } = timeSync(
|
||||
rep,
|
||||
`spawnSync resize (${basename(job.src)} → _medium.jpg)`,
|
||||
() =>
|
||||
spawnSync(EXE, ['resize', '--src', srcAbs, '--dst', dstTmpl, '--max-width', '40', '--format', 'jpeg'], {
|
||||
encoding: 'utf8',
|
||||
}),
|
||||
(x) => `exit ${x.status}`,
|
||||
);
|
||||
assert(r.status === 0, `glob batch exit 0, stderr: ${r.stderr}`);
|
||||
assert(existsSync(job.out), `expected ${job.out}`);
|
||||
rep.addImage({
|
||||
label: `CLI glob: ${basename(job.src)} → ${basename(job.out)}`,
|
||||
inputBytes: fileByteSize(job.src),
|
||||
outputBytes: fileByteSize(job.out),
|
||||
computeMs: ms,
|
||||
note: '${SRC_DIR}/out/${SRC_NAME}_medium.jpg',
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
rep.endSuite();
|
||||
}
|
||||
@ -903,28 +957,32 @@ function suiteGlobBatchRawArw(assetsDir, rep) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Explicit --src list: recursive *.arw glob is case-sensitive; enumerate paths so .ARW matches.
|
||||
const absSrc = arwFiles.map((p) => resolve(p).replace(/\\/g, '/'));
|
||||
const srcArgs = absSrc.flatMap((p) => ['--src', p]);
|
||||
const dstTmpl = '${SRC_DIR}/out/${SRC_NAME}_medium.jpg';
|
||||
|
||||
rep.beginSuite('CLI: recursive glob batch (raw **/*.arw) + dst templates');
|
||||
try {
|
||||
const r = timeSync(
|
||||
rep,
|
||||
`spawnSync resize (${arwFiles.length}× --src .arw/.ARW → _medium.jpg)`,
|
||||
() =>
|
||||
spawnSync(EXE, ['resize', ...srcArgs, '--dst', dstTmpl, '--max-width', '40', '--format', 'jpeg'], {
|
||||
encoding: 'utf8',
|
||||
}),
|
||||
(x) => `exit ${x.status}, ${arwFiles.length} file(s)`,
|
||||
);
|
||||
assert(r.status === 0, `glob batch raw exit 0, stderr: ${r.stderr}`);
|
||||
|
||||
for (const arwPath of arwFiles) {
|
||||
const srcAbs = resolve(arwPath).replace(/\\/g, '/');
|
||||
const stem = basename(arwPath).replace(/\.arw$/i, '');
|
||||
const expected = join(dirname(arwPath), 'out', `${stem}_medium.jpg`);
|
||||
const { result: r, ms } = timeSync(
|
||||
rep,
|
||||
`spawnSync resize (${basename(arwPath)} → _medium.jpg)`,
|
||||
() =>
|
||||
spawnSync(EXE, ['resize', '--src', srcAbs, '--dst', dstTmpl, '--max-width', '40', '--format', 'jpeg'], {
|
||||
encoding: 'utf8',
|
||||
}),
|
||||
(x) => `exit ${x.status}`,
|
||||
);
|
||||
assert(r.status === 0, `glob batch raw exit 0, stderr: ${r.stderr}`);
|
||||
assert(existsSync(expected), `expected ${expected}`);
|
||||
rep.addImage({
|
||||
label: `CLI raw: ${basename(arwPath)} → ${basename(expected)}`,
|
||||
inputBytes: fileByteSize(arwPath),
|
||||
outputBytes: fileByteSize(expected),
|
||||
computeMs: ms,
|
||||
note: 'ARW → JPEG',
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
rep.endSuite();
|
||||
@ -964,7 +1022,7 @@ async function suiteIpcUnix(assetsDir, rep) {
|
||||
throw new Error('unix socket path did not appear');
|
||||
}, () => path);
|
||||
|
||||
const res = await timeAsync(
|
||||
const { result: res, ms: udsMs } = await timeAsync(
|
||||
rep,
|
||||
'Unix connect + IPC JSON line',
|
||||
async () => {
|
||||
@ -996,9 +1054,12 @@ async function suiteIpcUnix(assetsDir, rep) {
|
||||
assert(existsSync(outPng), 'UDS output file exists');
|
||||
{
|
||||
const d = describePngFile(outPng);
|
||||
const inB = fileByteSize(inPng);
|
||||
rep.addImage({
|
||||
label: 'IPC Unix → disk (uds-out.png)',
|
||||
bytes: d.bytes,
|
||||
inputBytes: inB,
|
||||
outputBytes: d.bytes,
|
||||
computeMs: udsMs,
|
||||
widthPx: d.widthPx,
|
||||
heightPx: d.heightPx,
|
||||
note: 'UDS line-JSON resize',
|
||||
|
||||
@ -5,37 +5,37 @@
|
||||
| Key | Value |
|
||||
| --- | --- |
|
||||
| Result | PASS |
|
||||
| Assertions passed | 4 |
|
||||
| Assertions passed | 8 |
|
||||
| Assertions failed | 0 |
|
||||
| CWD | `C:\Users\zx\Desktop\polymech\polymech-mono\packages\media\cpp` |
|
||||
| Test binary | `C:\Users\zx\Desktop\polymech\polymech-mono\packages\media\cpp\dist\pm-image.exe` |
|
||||
| Assets dir | `C:\Users\zx\Desktop\polymech\polymech-mono\packages\media\cpp\tests\assets` |
|
||||
| CLI args | `--url-only` |
|
||||
| Wall clock (total script) | **1221.57 ms** |
|
||||
| CLI args | `--glob-batch-only --glob-raw` |
|
||||
| Wall clock (total script) | **10594.59 ms** |
|
||||
|
||||
## Timing
|
||||
|
||||
| Metric | Value |
|
||||
| --- | --- |
|
||||
| Started (ISO) | 2026-04-14T11:31:18.200Z |
|
||||
| Finished (ISO) | 2026-04-14T11:31:19.422Z |
|
||||
| Wall time (perf) | 1225.109 ms |
|
||||
| Wall time (clock) | 1225 ms |
|
||||
| Started (ISO) | 2026-04-14T11:43:16.994Z |
|
||||
| Finished (ISO) | 2026-04-14T11:43:27.589Z |
|
||||
| Wall time (perf) | 10597.975 ms |
|
||||
| Wall time (clock) | 10598 ms |
|
||||
|
||||
## Process (Node)
|
||||
|
||||
| Metric | Value |
|
||||
| --- | --- |
|
||||
| PID | 33980 |
|
||||
| PID | 28312 |
|
||||
| Node | v24.13.0 |
|
||||
| process.uptime() | 1.266 s |
|
||||
| CPU user (process.cpuUsage Δ) | 0.000 ms (0 µs) |
|
||||
| process.uptime() | 10.633 s |
|
||||
| CPU user (process.cpuUsage Δ) | 32.000 ms (32000 µs) |
|
||||
| CPU system (process.cpuUsage Δ) | 0.000 ms (0 µs) |
|
||||
| CPU user (resourceUsage) | 62.000 ms |
|
||||
| CPU user (resourceUsage) | 78.000 ms |
|
||||
| CPU system (resourceUsage) | 15.000 ms |
|
||||
| Max RSS (resourceUsage) | 39 MB |
|
||||
| RSS | 39 MB (40816640 B) |
|
||||
| Heap used | 6.0 MB |
|
||||
| RSS | 39 MB (40824832 B) |
|
||||
| Heap used | 6.2 MB |
|
||||
| Heap total | 11 MB |
|
||||
| External | 2.2 MB |
|
||||
| Array buffers | 16 KB |
|
||||
@ -50,33 +50,39 @@
|
||||
| CPUs | 16 |
|
||||
| CPU model | AMD Ryzen 7 3700X 8-Core Processor |
|
||||
| RAM total | 64 GB |
|
||||
| RAM free | 38 GB |
|
||||
| RAM used | 26 GB |
|
||||
| RAM free | 36 GB |
|
||||
| RAM used | 27 GB |
|
||||
| Load avg (1/5/15) | 0.00 / 0.00 / 0.00 |
|
||||
| OS uptime | 123.71 h |
|
||||
| OS uptime | 123.91 h |
|
||||
|
||||
## Images & transfers
|
||||
|
||||
| Label | Size | Dimensions / detail |
|
||||
| --- | --- | --- |
|
||||
| fixture square-64.png | 551 B (551 B) | 64×64 px · on-disk fixture |
|
||||
| fixture checker-128x128.png | 455 B (455 B) | 128×128 px · on-disk fixture |
|
||||
| fixture glob-in/root.png | 384 B (384 B) | 48×48 px · on-disk fixture |
|
||||
| fixture glob-in/sub/leaf.png | 88 B (88 B) | 24×24 px · on-disk fixture |
|
||||
| Label | Input | Output | Time (ms) | Detail |
|
||||
| --- | --- | --- | ---: | --- |
|
||||
| fixture square-64.png | 551 B (551 B) | — | — | 64×64 px · on-disk fixture |
|
||||
| fixture checker-128x128.png | 455 B (455 B) | — | — | 128×128 px · on-disk fixture |
|
||||
| fixture glob-in/root.png | 384 B (384 B) | — | — | 48×48 px · on-disk fixture |
|
||||
| fixture glob-in/sub/leaf.png | 88 B (88 B) | — | — | 24×24 px · on-disk fixture |
|
||||
| CLI raw: DSC02717.ARW → DSC02717_medium.jpg | 24 MB (24824576 B) | 1.1 KB (1123 B) | 2555.37 | ARW → JPEG |
|
||||
| CLI raw: DSC02718.ARW → DSC02718_medium.jpg | 24 MB (24791808 B) | 1.0 KB (1039 B) | 2669.04 | ARW → JPEG |
|
||||
| CLI raw: DSC02719.ARW → DSC02719_medium.jpg | 24 MB (24836864 B) | 1.1 KB (1125 B) | 2660.4 | ARW → JPEG |
|
||||
| CLI raw: DSC02720.ARW → DSC02720_medium.jpg | 24 MB (24820480 B) | 1.0 KB (1029 B) | 2701.57 | ARW → JPEG |
|
||||
|
||||
## Integration: performance by suite
|
||||
|
||||
### CLI: resize HTTPS URL (picsum.photos)
|
||||
### CLI: recursive glob batch (raw **/*.arw) + dst templates
|
||||
|
||||
- **Suite wall time:** 1216.33 ms
|
||||
- **Suite wall time:** 10589.98 ms
|
||||
|
||||
| Step | ms | Detail |
|
||||
| --- | ---: | --- |
|
||||
| spawnSync resize URL → 200.jpg | 251.58 | exit 0, network+libvips |
|
||||
| spawnSync resize URL → 1600.jpg | 963.61 | exit 0, network+libvips |
|
||||
| spawnSync resize (DSC02717.ARW → _medium.jpg) | 2555.37 | exit 0 |
|
||||
| spawnSync resize (DSC02718.ARW → _medium.jpg) | 2669.04 | exit 0 |
|
||||
| spawnSync resize (DSC02719.ARW → _medium.jpg) | 2660.4 | exit 0 |
|
||||
| spawnSync resize (DSC02720.ARW → _medium.jpg) | 2701.57 | exit 0 |
|
||||
|
||||
---
|
||||
*Written 2026-04-14T11:31:19.422Z*
|
||||
*Written 2026-04-14T11:43:27.589Z*
|
||||
|
||||
---
|
||||
|
||||
|
||||
4
packages/media/cpp/todos.md
Normal file
4
packages/media/cpp/todos.md
Normal file
@ -0,0 +1,4 @@
|
||||
## Videos
|
||||
|
||||
https://www.mltframework.org/features/
|
||||
https://github.com/olive-editor/olive?tab=readme-ov-file
|
||||
Loading…
Reference in New Issue
Block a user