#!/usr/bin/env python3 """Every build log of every document, read for what a green run hides. A build that ends with a PDF has not said that it went well. LaTeX reports a box that runs over the edge, a character its font does not carry and a head that is too small for what stands in it, and then writes the page anyway. An error that stands in every log hides the next one that means something. """ import os import re import sys sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import support from support import Check, build EXAMPLES = os.path.join(support.ROOT, "examples") DOCUMENTS = ("sample.tex", "logo-example.tex", "letter.tex", "presentation.tex") # What no log of this suite may carry, and why each one matters. COMPLAINTS = ( # An error stops nothing in nonstopmode: the run goes on and the page comes # out with the defect on it. (re.compile(r"^! ", re.MULTILINE), "an error"), # A box that runs over the edge is text outside the paper. (re.compile(r"^Overfull \\hbox", re.MULTILINE), "an overfull box"), # A character the font does not carry prints NOTHING, with no gap a reader # would notice and no error. (re.compile(r"Missing character", re.MULTILINE), "a missing character"), # A font shape LaTeX does not find is replaced silently, and the page then # carries a face nobody chose. (re.compile(r"Font shape .* undefined", re.MULTILINE), "an undefined font shape"), (re.compile(r"Some font shapes were not available", re.MULTILINE), "a substituted font"), # A head that is too small for what stands in it overprints the text under # it, and fancyhdr says so once per page. (re.compile(r"headheight is too small", re.MULTILINE), "a head that is too small"), # And a foot that is too small is the same defect at the other edge: the # page number and whatever the document put beside it are drawn into the # text above them. fancyhdr says so once per page. (re.compile(r"footskip is too small", re.MULTILINE), "a foot that is too small"), # A title block without an author prints the block and leaves the line out, # and the document says nothing about who wrote it. (re.compile(r"No \\author given", re.MULTILINE), "a title block without an author"), (re.compile(r"Package hyperref Warning", re.MULTILINE), "a warning of hyperref"), ) def logs(): """Every log THIS RUN produced, from the examples and from the fixtures. Only the logs under the test build: a log beside the examples was written by whatever state the working tree had when somebody last built there, and a defect that was repaired an hour ago still stands in it. Measured on 2026-09-21, when a warning that the fresh build no longer carries was reported from a log of the build before it. The probe of the converter is not among them either: it is run once, on purpose, to measure the width of a line, and it produces no page for any reader. Its log therefore always asks for the second run that a document gets and a measurement does not need. """ found = [] for directory, _, names in os.walk(support.BUILD): for name in names: if name.endswith(".log") and name != "metrics.log": found.append(os.path.join(directory, name)) return sorted(found) def main(): check = Check("logs") for name in DOCUMENTS: source = os.path.join(EXAMPLES, name) if os.path.exists(source): build(source, EXAMPLES) read = logs() check.that(read, "no build log was found at all") for path in read: with open(path, encoding="utf-8", errors="replace") as handle: text = handle.read() for pattern, what in COMPLAINTS: hit = pattern.search(text) check.that(hit is None, f"{os.path.basename(path)} reports {what}: " f"{text[hit.start():hit.start() + 120].splitlines()[0] if hit else ''}") print(f" {len(read)} logs read") return check.done() if __name__ == "__main__": sys.exit(main())