mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-16 01:45:29 +02:00
fix(make-pdf): pdftotext version and flavor probe returns unknown on poppler
describeBinary reports version="unknown" flavor="unknown" for every poppler
install, so logDiagnostics prints nothing useful on the most common
implementation. Two independent causes:
1. poppler writes the -v banner to stderr and exits 0. execFileSync returns
stdout (empty) and does not throw on a zero exit, so the stderr fallback in
the catch block is unreachable. The in-code comment already notes poppler
exits 0, but only the throwing path reads stderr.
2. flavor is matched against the version line alone. poppler prints
"pdftotext version 26.06.0" on line 1 and names itself on line 2,
"Copyright ... The Poppler Developers", so even a working stderr read
yields "unknown".
Switch the probe to spawnSync, which returns both streams regardless of exit
status, match the version banner rather than assuming line 0, and derive the
flavor from the full output.
Measured on poppler 26.06.0 (Homebrew, macOS), same machine and binary:
before: { version: "unknown", flavor: "unknown" }
after: { version: "pdftotext version 26.06.0", flavor: "poppler" }
xpdf is unaffected: it exits non-zero and names itself on line 1, so it
resolved correctly before and still does.
Tests use shell shims reproducing each vendor's banner, stream and exit status,
since a real pdftotext cannot be assumed present in CI. Two of the four fail on
this commit's parent; the xpdf and no-banner cases pass there and are included
as regression guards rather than red-proofs.
This commit is contained in:
@@ -8,6 +8,8 @@
|
||||
|
||||
import { describe, expect, test } from "bun:test";
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import { normalize, copyPasteGate, findExecutable, resolvePdftotext, PdftotextUnavailableError } from "../src/pdftotext";
|
||||
|
||||
@@ -205,3 +207,96 @@ describe("resolvePdftotext (override resolution, v1.24-aligned)", () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ─── Version + flavor probe (describeBinary via resolvePdftotext) ────
|
||||
//
|
||||
// Regression cover for the probe returning version="unknown" flavor="unknown"
|
||||
// on every poppler install. Two independent causes, both exercised here:
|
||||
//
|
||||
// 1. poppler writes its -v banner to STDERR and exits 0. The old code used
|
||||
// execFileSync, which returns stdout (empty) and does not throw on a zero
|
||||
// exit, so the stderr fallback in the catch block was unreachable.
|
||||
// 2. flavor was matched against the first line only. poppler prints
|
||||
// "pdftotext version X" on line 1 and identifies itself on line 2
|
||||
// ("Copyright ... The Poppler Developers"), so even a working stderr read
|
||||
// yielded flavor="unknown".
|
||||
//
|
||||
// Real pdftotext binaries cannot be assumed present in CI, so these use shell
|
||||
// shims that reproduce each vendor's exact banner, stream and exit status.
|
||||
|
||||
describe("describeBinary (version + flavor probe)", () => {
|
||||
const shimDir = fs.mkdtempSync(path.join(os.tmpdir(), "pdftotext-shim-"));
|
||||
|
||||
function shim(name: string, body: string): string {
|
||||
if (process.platform === "win32") return "";
|
||||
const p = path.join(shimDir, name);
|
||||
fs.writeFileSync(p, `#!/bin/sh\n${body}\n`, { mode: 0o755 });
|
||||
return p;
|
||||
}
|
||||
|
||||
// poppler: banner on stderr, exit 0, vendor named on line 2.
|
||||
const popplerShim = shim(
|
||||
"poppler-pdftotext",
|
||||
[
|
||||
'if [ "$1" = "-v" ]; then',
|
||||
' echo "pdftotext version 26.06.0" >&2',
|
||||
' echo "Copyright 2005-2026 The Poppler Developers - http://poppler.freedesktop.org" >&2',
|
||||
' echo "Copyright 1996-2011, 2022 Glyph & Cog, LLC" >&2',
|
||||
" exit 0",
|
||||
"fi",
|
||||
"exit 0",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
// xpdf: banner on stderr, non-zero exit, vendor named on line 1.
|
||||
const xpdfShim = shim(
|
||||
"xpdf-pdftotext",
|
||||
[
|
||||
'if [ "$1" = "-v" ]; then',
|
||||
' echo "pdftotext version 4.05 [xpdf]" >&2',
|
||||
' echo "Copyright 1996-2024 Glyph & Cog, LLC" >&2',
|
||||
" exit 99",
|
||||
"fi",
|
||||
"exit 0",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
test.skipIf(process.platform === "win32")(
|
||||
"reads a poppler banner from stderr on a zero exit",
|
||||
() => {
|
||||
const info = withEnv({ GSTACK_PDFTOTEXT_BIN: popplerShim }, () => resolvePdftotext());
|
||||
expect(info.version).toBe("pdftotext version 26.06.0");
|
||||
expect(info.flavor).toBe("poppler");
|
||||
},
|
||||
);
|
||||
|
||||
test.skipIf(process.platform === "win32")(
|
||||
"identifies poppler from the copyright line, not the version line",
|
||||
() => {
|
||||
const info = withEnv({ GSTACK_PDFTOTEXT_BIN: popplerShim }, () => resolvePdftotext());
|
||||
// The line carrying the version does NOT contain the vendor name, which is
|
||||
// exactly why a line-0-only match reported "unknown".
|
||||
expect(info.version.toLowerCase()).not.toContain("poppler");
|
||||
expect(info.flavor).toBe("poppler");
|
||||
},
|
||||
);
|
||||
|
||||
test.skipIf(process.platform === "win32")(
|
||||
"reads an xpdf banner from stderr on a non-zero exit",
|
||||
() => {
|
||||
const info = withEnv({ GSTACK_PDFTOTEXT_BIN: xpdfShim }, () => resolvePdftotext());
|
||||
expect(info.version).toBe("pdftotext version 4.05 [xpdf]");
|
||||
expect(info.flavor).toBe("xpdf");
|
||||
},
|
||||
);
|
||||
|
||||
test.skipIf(process.platform === "win32")(
|
||||
"falls back to unknown when the binary emits no banner",
|
||||
() => {
|
||||
const silent = shim("silent-pdftotext", "exit 0");
|
||||
const info = withEnv({ GSTACK_PDFTOTEXT_BIN: silent }, () => resolvePdftotext());
|
||||
expect(info.version).toBe("unknown");
|
||||
expect(info.flavor).toBe("unknown");
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user