Development
git clone https://github.com/luisleo526/doc2mark && cd doc2mark
pip install -e ".[all,dev,docs]"
Unit tests
python -m pytest -m "not integration and not requires_api_key and not e2e" -q # as CI runs them
A plain python -m pytest also collects the end-to-end suite (below); -m "not e2e" leaves it
out. Markers (pyproject.toml): e2e (added automatically to everything under
tests/e2e), requires_api_key (calls OpenAI; CI runs them only when the repository has the
secret), requires_typesafe (calls TypeSafe; skipped without the typesafe extra or
TYPESAFE_API_KEY, failed instead when D2M_REQUIRE_TYPESAFE=1), integration, slow,
ocr, unit.
End-to-end tests
tests/e2e runs the installed doc2mark command as a subprocess against real Tesseract and
LibreOffice and checks only what it writes: exit code, stdout/stderr and the output files. It
needs Tesseract with the eng, chi_tra and chi_sim data and LibreOffice (soffice);
the LLM OCR tests point the OpenAI provider at a local stand-in for the API
(tests/e2e/fake_openai.py), so no key is needed.
Docker (the reference environment)
scripts/run_e2e_docker.sh -q -n 8 # the E2E suite, 8 pytest-xdist workers
scripts/run_e2e_docker.sh -k tesseract -v # any pytest arguments
scripts/run_e2e_docker.sh -m "not e2e" -q -n 8 # the unit suite in the same image
The script builds tests/e2e/Dockerfile (Python 3.12, Tesseract, LibreOffice, Noto CJK fonts)
as d2m-e2e:<hash of the Dockerfile> when that tag is missing, copies the checkout into a
throwaway container (the checkout is mounted read-only), installs it with pip install -e
".[$D2M_E2E_EXTRAS]" and runs pytest -m e2e with your arguments; a later -m replaces
-m e2e. The exit code is pytest’s, or 90 when the run could not be set up (image build,
copying the checkout, pip install, a variable named in D2M_E2E_PASS_ENV that is not
set). CI runs the suite on every pull request with -n 4 and OMP_THREAD_LIMIT=1
(Tesseract otherwise starts several OpenMP threads per call, which slows parallel runs down).
Environment variables:
D2M_E2E_STRICT=1Set by the script: a test whose tool (
tesseract,soffice) is missing fails instead of being skipped, so a broken environment cannot pass.D2M_E2E_PASS_ENVSpace-separated names of host variables to forward into the container. Only the names go on the
docker runcommand line, so a secret can come from a file.D2M_E2E_EXTRASThe extras installed in the container (default
ocr,dev).D2M_REQUIRE_TYPESAFE=1The
requires_typesafetests fail instead of skipping when the extra or the key is missing.
set -a; . ~/.config/typesafe/env; set +a # defines TYPESAFE_API_KEY
D2M_E2E_EXTRAS="ocr,dev,typesafe" D2M_REQUIRE_TYPESAFE=1 \
D2M_E2E_PASS_ENV="TYPESAFE_API_KEY D2M_REQUIRE_TYPESAFE" scripts/run_e2e_docker.sh -q -n 8
Without Docker
Install Tesseract (with the languages above) and LibreOffice, then:
pip install -e ".[ocr,dev]"
python -m pytest -m e2e -q
A test whose tool is not on PATH is skipped with that reason, unless D2M_E2E_STRICT=1.
Writing an E2E test
tests/e2e/conftest.py provides the fixtures:
run_cli(input_path, *args, fmt="markdown", env=None, timeout=300, raw=False)runs thedoc2markconsole script on one file and returnsexit_code,stdout,stderr,markdown,json(parsed, forfmt="json"or"both"),out_diranddescribe()for assertion messages. It adds-oand--formatitself;raw=Trueleaves the whole command line to you.e2e_dir: a per-test scratch folder namede2e-<test>-<timestamp>for input files.require_tool("tesseract")/require_tool("soffice"): call it first in a test that needs the program.
tests/e2e/pdfgen.py builds input PDFs at test time (text_pdf, image_pdf,
text_png); lane-specific builders live in tests/e2e/builders_*.py.
from tests.e2e import pdfgen
def test_ocr_reads_scanned_page(run_cli, require_tool, e2e_dir):
require_tool("tesseract")
pdf = pdfgen.image_pdf(e2e_dir / "scan.pdf", "HELLO 123")
result = run_cli(pdf, "--ocr", "tesseract", "--ocr-images")
assert result.exit_code == 0, result.describe()
assert "HELLO 123" in result.markdown
Documentation
python -m sphinx -b html -W --keep-going docs docs/_build/html
The API reference is generated from the docstrings (sphinx.ext.autodoc), so signatures cannot
drift from the code. eval/docs_audit/ runs every code example of the README and of these pages
in the E2E image and checks its output (eval/docs_audit/run_on_spark.sh OUT_DIR; see
eval/docs_audit/README.md). The Docs workflow builds the site on pull requests and publishes
it to GitHub Pages from main.