Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions specs/001-pathway-pdfs/tasks.md
Original file line number Diff line number Diff line change
Expand Up @@ -649,11 +649,12 @@ passes.
- **L3, untested holds:** tests that every method is held and the calls said in one line, that the console is put back when the comparison fails, and that `compare` holds the console while it compares (a library writing as a file is not read).
- **L4/L5, wording:** what joins and what does not, and the tables still read as figures, are said as they are (above, and the known gaps).
- **Mutants, each caught:** a waypoint not a piece of a line (the reviewer's diagram, printed and as shapes); segments not grouped into one line; the last section's figures sought on its first page only; the first section's heading reaching onto the cover; check-book printing the message; check-book's reader, or the contents' reader, not turning pdf.js's errors into `UnreadPdf`; `compare` without the held console; the console put back only on success; only `warn` and `error` held; one line a call. 11, none survived; each run on the test files it touches alone, the host being loaded.
- **From #171's review:** two mediums and lows, each reproduced first; the waypoint fix held.
- **M1, check-book's stopped line said nothing useful:** it printed the wrapper's kind alone, and a file system's or the graph's error lost its path or message. `UnreadPdf` now names its file and pdf.js's error's kind (`<path> not read (<kind>)`), holding the kind as `kind` and pdf.js's error as its `cause`, never printed; check-book's stopped line prints the error's message, which for a PDF not read is that, and for a file system's or the graph's error is its own (a path; no text of a PDF). Tests: a planted filter's file and a truncated PDF are named with `UnknownErrorException` and `InvalidPDFException`, the word never printed; a file it may not read is named with `EACCES`.
- **M2, the contents' reader's test could not fail:** it now reads a PDF whose named destinations are a stream filtered by the planted word, which pdf.js quotes (`Bad filter name "…"`, checked on its `cause`); `UnreadPdf`'s message is `A PDF not read (<kind>)` without it.
- **Lows:** a figure three pages into the last section is found; the held console holds only the methods that write (`log`, `info`, `warn`, `error`, `debug`, `trace`, `dir`, `dirxml`, `table`, `group`, `groupCollapsed`, `groupEnd`, `count`, `countReset`, `time`, `timeEnd`, `timeLog`, `assert`), leaving `Console`, `context`, `createTask` and the rest alone, with a test; `landed`'s doc comment is back on it; the known gaps add a shaded header just below a figure.
- **Mutants, each caught:** the cause's message appended to `UnreadPdf`'s, with a path or without; the stopped line by kind alone; the file not named; the cause's kind lost; the last section's figures sought one page on only; every console member held. 7, none survived; each run on its own test file.
- **Follow-up (#175), from #173's review:** `UnreadPdf` keeps pdf.js's error in a private field (`pdfError()`), not as `cause`: Node prints an error's `cause` wherever it prints the error whole (`console.error`, an uncaught error), enumerable or not, so a later `console.error(error)` would have printed the file's words. Tested by printing the error whole with `util.inspect` (the `{ cause }` mutant fails it). Each of the console's writing methods is now tested held, from the test's own list (dropping `assert` and `group` from the code's list fails it). `console.clear` is not held: it writes only control codes.
- **From #171's review:** two mediums and lows, each reproduced first; the waypoint fix held.
- **M1, check-book's stopped line said nothing useful:** it printed the wrapper's kind alone, and a file system's or the graph's error lost its path or message. `UnreadPdf` now names its file and pdf.js's error's kind (`<path> not read (<kind>)`), holding the kind as `kind` and pdf.js's error as its `cause`, never printed; check-book's stopped line prints the error's message, which for a PDF not read is that, and for a file system's or the graph's error is its own (a path; no text of a PDF). Tests: a planted filter's file and a truncated PDF are named with `UnknownErrorException` and `InvalidPDFException`, the word never printed; a file it may not read is named with `EACCES`.
- **M2, the contents' reader's test could not fail:** it now reads a PDF whose named destinations are a stream filtered by the planted word, which pdf.js quotes (`Bad filter name "…"`, checked on its `cause`); `UnreadPdf`'s message is `A PDF not read (<kind>)` without it.
- **Lows:** a figure three pages into the last section is found; the held console holds only the methods that write (`log`, `info`, `warn`, `error`, `debug`, `trace`, `dir`, `dirxml`, `table`, `group`, `groupCollapsed`, `groupEnd`, `count`, `countReset`, `time`, `timeEnd`, `timeLog`, `assert`), leaving `Console`, `context`, `createTask` and the rest alone, with a test; `landed`'s doc comment is back on it; the known gaps add a shaded header just below a figure.
- **Mutants, each caught:** the cause's message appended to `UnreadPdf`'s, with a path or without; the stopped line by kind alone; the file not named; the cause's kind lost; the last section's figures sought one page on only; every console member held. 7, none survived; each run on its own test file.
- **Found:** pdf-export's analysis part does not print the resource (`TOTAL`), which FR-012 lists among the properties: `analysis resource missing` until it does, for T040 to record or fix.
- **Known gaps, for T040:** `event-pdf`'s own labels, its cover's names' separator (whether U+2060 survives pdf.js's reading of its files), its margins and its contents' form are not known here, so the reference's shape is the Background's and R2's, and its wording this task's. A label not known leaves its part unread, which shows as a difference, never a pass; T040 adds `event-pdf`'s labels to `RULES` and `PROPERTIES` in `src/compare/reduce.ts` and sets `--reference-margin`. A table with stroked or shaded cells can still be read as a figure, its text then left out: borders 4 pt or thicker are shapes, not lines; borders touching two shaded cells join them; shaded rows within 30 pt of each other cluster; and a shaded table header within 30 pt below a figure is taken into it. T040 checks `event-pdf`'s tables. A figure's label outside its shapes is read as text. A fact's value wrapped to the section's left edge would be read as the summation's start. A cover whose title a figure takes in compares its largest remaining text as its title (it says `differs`, nothing more). A summation and an analysis's found tables are compared as text and rows, not by their markup.

Expand Down
20 changes: 14 additions & 6 deletions src/print/unread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,26 @@ export const errorKind = (error: unknown): string => {

/**
* A PDF pdf.js could not read. Its message names the file, where it is given, and pdf.js's
* error by its kind (`kind`) alone; pdf.js's own error is its `cause`, never printed.
* error by its kind (`kind`) alone. pdf.js's own error is kept in a private field, read
* with `pdfError()`, and not as `cause`: Node prints an error's `cause` wherever it prints
* the error whole (`console.error`, an uncaught error), enumerable or not, and pdf.js's
* message may quote the file.
*/
export class UnreadPdf extends Error {
override name = 'UnreadPdf';
/** pdf.js's error's kind and code. */
readonly kind: string;
readonly #error: unknown;

constructor(cause: unknown, path?: string) {
const kind = errorKind(cause);
super(path === undefined ? `A PDF not read (${kind})` : `${path} not read (${kind})`, {
cause,
});
constructor(error: unknown, path?: string) {
const kind = errorKind(error);
super(path === undefined ? `A PDF not read (${kind})` : `${path} not read (${kind})`);
this.kind = kind;
this.#error = error;
}

/** pdf.js's own error, for a debugger: never to be printed. */
pdfError(): unknown {
return this.#error;
}
}
5 changes: 4 additions & 1 deletion test/check/unread.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// the contents' reader `book` uses likewise. A file system's error says its message. The
// planted word stands for a name the book's PDFs hold.
import assert from 'node:assert/strict';
import { inspect } from 'node:util';
import { chmodSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
Expand Down Expand Up @@ -30,11 +31,13 @@ describe('a PDF pdf.js cannot read, its error quoting it', () => {
assert.ok(error instanceof UnreadPdf, 'not an UnreadPdf');
// pdf.js's own error quoted the word: the planted PDF is one it reads as intended
assert.ok(
error.cause instanceof Error && error.cause.message.includes(PLANTED),
error.pdfError() instanceof Error && (error.pdfError() as Error).message.includes(PLANTED),
"pdf.js's error did not quote the planted word"
);
assert.ok(!error.message.includes(PLANTED), 'the planted word is in its message');
assert.equal(error.message, `A PDF not read (${error.kind})`);
// printed whole, as console.error or an uncaught error prints it: no cause, no word
assert.ok(!inspect(error).includes(PLANTED), 'the planted word is in the error printed whole');
});

/** check-book on a folder of the fixture's book's names, each file written by `write`. */
Expand Down
40 changes: 40 additions & 0 deletions test/compare/units.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,46 @@ describe('what a library writes to the console', () => {
assert.equal(err, '8 message(s) from a library withheld (TypeError)\n');
});

test("each of the console's writing methods is held", async () => {
// the list is the test's own, so a method dropped from the code's list fails here
const writing = [
'log',
'info',
'warn',
'error',
'debug',
'trace',
'dir',
'dirxml',
'table',
'group',
'groupCollapsed',
'groupEnd',
'count',
'countReset',
'time',
'timeEnd',
'timeLog',
'assert',
];
for (const name of writing) {
let err = '';
const said = await spied(() =>
withConsoleHeld(
() => {
const method = Reflect.get(console, name) as (...args: unknown[]) => void;
if (name === 'assert') method.call(console, false, PLANTED);
else method.call(console, PLANTED);
return Promise.resolve();
},
(t) => (err += t)
)
);
assert.deepEqual(said, [], name);
assert.ok(!err.includes(PLANTED), name);
}
});

test("the console's members that do not write are left alone", async () => {
const members = ['Console', 'context', 'createTask', 'profile', 'timeStamp'];
const before = members.map((name) => Reflect.get(console, name) as unknown);
Expand Down
Loading