Stress test that writes a document with N body paragraphs and verifies the count survives save+load. Parameterised across N = 100, 1000, 10000 so non-linear blow-ups in the authoring pipeline show up as case-to-case size or time divergence.
Library verdicts: python-docx: — docxjs: —
| Feature id | docx/scale-paragraphs--n10000 |
|---|---|
| Format | docx |
| Category | scale |
| Family | docx/scale-paragraphs |
| Axis values | size=n10000 |
| Spec | ecma-376-5-part-1 § 17.2.2 w:p |
| ID / part | Predicate | XPath | python-docx | docxjs |
|---|---|---|---|---|
paragraph-count-n10000word/document.xml | equal = 10000The body must contain exactly N direct <w:p> children, matching the requested paragraph count. | string(count(/w:document/w:body/w:p)) | — | — |
first-paragraph-text-present-n10000word/document.xml | existThe first paragraph ('Paragraph 0') must be present, guarding against a truncation that loses the head of the stream. | //w:t[normalize-space()='Paragraph 0'] | — | — |
No render assertions declared.
scripts/gen_scale_paragraphs.py
#!/usr/bin/env python3
"""Generate ``fixtures/docx/scale-paragraphs--<n>.docx``.
Parameterised stress fixture for the ``docx/scale-paragraphs`` family.
Writes a document with ``--count`` body paragraphs, each containing a
single run with a short deterministic text. Designed to expose
non-linear memory or time behaviour in the authoring pipeline.
The assertion against the resulting fixture counts ``<w:p>`` children
under ``<w:body>`` and compares to ``--count``.
Usage::
python scripts/gen_scale_paragraphs.py \
--count 100 \
--out fixtures/docx/scale-paragraphs--n100.docx
"""
from __future__ import annotations
import argparse
from pathlib import Path
from docx import Document
_REPO_ROOT = Path(__file__).resolve().parent.parent
def _write(count: int, out: Path) -> None:
doc = Document()
for i in range(count):
doc.add_paragraph(f"Paragraph {i}")
out.parent.mkdir(parents=True, exist_ok=True)
doc.save(out, reproducible=True)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--count", required=True, type=int, help="Number of body paragraphs.")
parser.add_argument("--out", required=True, help="Output path for the .docx file.")
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = _REPO_ROOT / out_path
_write(args.count, out_path)
print(out_path)
return 0
if __name__ == "__main__":
raise SystemExit(main())
{
"$schema": "../manifest.schema.json",
"id": "docx/scale-paragraphs--n10000",
"kind": "literal",
"title": "Scale: many paragraphs",
"format": "docx",
"category": "scale",
"summary": "Stress test that writes a document with N body paragraphs and verifies the count survives save+load. Parameterised across N = 100, 1000, 10000 so non-linear blow-ups in the authoring pipeline show up as case-to-case size or time divergence.",
"spec": {
"source": "ecma-376-5-part-1",
"clause": "17.2.2",
"element": "w:p",
"rnc_reference": "spec/ecma-376-5/part-1/rnc/WordprocessingML.rnc",
"xsd_reference": "spec/ecma-376-5/part-1/xsd/wml.xsd",
"notes": "Real-world documents routinely carry thousands of paragraphs. Minimal fixtures hide three classes of bugs: (a) per-paragraph O(N) scans inside the save path that compound to O(N^2) wall time, (b) id-generator wrap or collision past 65536, and (c) file-size explosions from per-paragraph rsid bookkeeping. This family exists to surface all three as visible case-to-case deltas in the recorded performance baseline and as hard failures when save+reopen round-trip loses any paragraph."
},
"fixtures": {
"machine": "docx/scale-paragraphs--n10000"
},
"generator": {
"python": "scripts/gen_scale_paragraphs.py",
"arg_template": "--count {size.n} --out fixtures/docx/scale-paragraphs--{size.id}.docx"
},
"meta": {
"performance": {
"recorded_on": "2026-05-05",
"notes": "Save-time wall clock and on-disk size recorded on the authoring machine; not asserted, baseline only.",
"cases": [
{
"id": "n100",
"save_seconds": 0.23,
"file_bytes": 20716
},
{
"id": "n1000",
"save_seconds": 0.3,
"file_bytes": 35096
},
{
"id": "n10000",
"save_seconds": 1.48,
"file_bytes": 177410
}
]
}
},
"_expansion": {
"parent_id": "docx/scale-paragraphs",
"bindings": {
"size": "n10000"
}
},
"assertions": [
{
"id": "paragraph-count-n10000",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "string(count(/w:document/w:body/w:p))",
"must": "equal",
"value": "10000",
"description": "The body must contain exactly N direct <w:p> children, matching the requested paragraph count."
},
{
"id": "first-paragraph-text-present-n10000",
"part": "word/document.xml",
"namespaces": {
"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
},
"xpath": "//w:t[normalize-space()='Paragraph 0']",
"must": "exist",
"description": "The first paragraph ('Paragraph 0') must be present, guarding against a truncation that loses the head of the stream."
}
]
}
No rendered reference is available for this case.