98 lines
4.1 KiB
Python
98 lines
4.1 KiB
Python
#!/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())
|