pptx/scale-slides--n500

Stress test that writes a presentation with N title-only slides, each titled 'Slide <i>' for 1-indexed i, and verifies the sldIdLst carries N entries. Parameterised across N = 10, 100, 500 (the 1000-slide case is intentionally omitted; 500 saves in ~1s on the author machine, 1000 crossed 2s without uncovering anything new).

Library verdicts: python-pptx: — pptxjs: —

Metadata

Feature idpptx/scale-slides--n500
Formatpptx
Categoryscale
Familypptx/scale-slides
Axis valuessize=n500
Spececma-376-5-part-1 § 19.3.1 p:sldIdLst

XPath assertions

ID / partPredicateXPathpython-pptxpptxjs
sldidlst-count-n500
ppt/presentation.xml
equal = 500
The sldIdLst must list exactly N <p:sldId> entries.
string(count(/p:presentation/p:sldIdLst/p:sldId))
slide1-title-n500
ppt/slides/slide1.xml
exist
The first slide must carry the title 'Slide 1', guarding against head-of-stream truncation.
//a:t[normalize-space()='Slide 1']
slide-last-title-n500
ppt/slides/slide500.xml
exist
The last slide must carry the title 'Slide N', guarding against tail truncation or a missing slideN.xml part.
//a:t[normalize-space()='Slide 500']

Render assertions

No render assertions declared.

Generator source

scripts/gen_scale_slides.py

#!/usr/bin/env python3
"""Generate ``fixtures/pptx/scale-slides--<n>.pptx``.

Parameterised stress fixture for the ``pptx/scale-slides`` family.
Writes a presentation with ``--slides`` title-only slides; each slide's
title contains its 1-indexed position so the presentation is internally
distinguishable.

Assertions target the first, middle, and last slide-part so the fixture
proves the slide id generator allocated unique ids all the way through
the run.

Usage::

    python scripts/gen_scale_slides.py \
        --slides 100 \
        --out fixtures/pptx/scale-slides--n100.pptx
"""

from __future__ import annotations

import argparse
import datetime as _dt
from pathlib import Path

from pptx import Presentation

_REPRODUCIBLE_DT = (1980, 1, 1, 0, 0, 0)

_REPO_ROOT = Path(__file__).resolve().parent.parent


def _write(slides: int, out: Path) -> None:
    prs = Presentation()
    layout = prs.slide_layouts[5]  # title-only layout
    for i in range(1, slides + 1):
        slide = prs.slides.add_slide(layout)
        slide.shapes.title.text = f"Slide {i}"
    out.parent.mkdir(parents=True, exist_ok=True)
    prs.save(out, zip_date_time=_REPRODUCIBLE_DT)


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--slides", required=True, type=int, help="Number of slides.")
    parser.add_argument("--out", required=True, help="Output path for the .pptx file.")
    args = parser.parse_args()

    out_path = Path(args.out)
    if not out_path.is_absolute():
        out_path = _REPO_ROOT / out_path
    _write(args.slides, out_path)
    print(out_path)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())

Manifest (expanded)

{
  "$schema": "../manifest.schema.json",
  "id": "pptx/scale-slides--n500",
  "kind": "literal",
  "title": "Scale: many slides",
  "format": "pptx",
  "category": "scale",
  "summary": "Stress test that writes a presentation with N title-only slides, each titled 'Slide <i>' for 1-indexed i, and verifies the sldIdLst carries N entries. Parameterised across N = 10, 100, 500 (the 1000-slide case is intentionally omitted; 500 saves in ~1s on the author machine, 1000 crossed 2s without uncovering anything new).",
  "spec": {
    "source": "ecma-376-5-part-1",
    "clause": "19.3.1",
    "element": "p:sldIdLst",
    "rnc_reference": "spec/ecma-376-5/part-1/rnc/PresentationML.rnc",
    "xsd_reference": "spec/ecma-376-5/part-1/xsd/pml.xsd",
    "notes": "Each slide added via slides.add_slide() is registered as a <p:sldId> under <p:sldIdLst> in ppt/presentation.xml and emitted as ppt/slides/slideN.xml. python-pptx starts allocation at id=256 and increments by 1 per slide. This family verifies (a) the sldIdLst cardinality matches the requested count, (b) the first and last slide parts exist and carry the expected 'Slide 1' / 'Slide N' title, guarding against id-generator collisions, missed slide parts, or off-by-one truncation."
  },
  "fixtures": {
    "machine": "pptx/scale-slides--n500"
  },
  "generator": {
    "python": "scripts/gen_scale_slides.py",
    "arg_template": "--slides {size.n} --out fixtures/pptx/scale-slides--{size.id}.pptx"
  },
  "meta": {
    "performance": {
      "recorded_on": "2026-05-05",
      "notes": "Save-time scales roughly linearly with slide count (0.21s, 0.32s, 1.03s for 10/100/500). File size grows linearly at roughly 850 bytes per slide after fixed overhead.",
      "cases": [
        {
          "id": "n10",
          "save_seconds": 0.21,
          "file_bytes": 35233,
          "first_sld_id": 256,
          "last_sld_id": 265
        },
        {
          "id": "n100",
          "save_seconds": 0.32,
          "file_bytes": 106881,
          "first_sld_id": 256,
          "last_sld_id": 355
        },
        {
          "id": "n500",
          "save_seconds": 1.03,
          "file_bytes": 427336,
          "first_sld_id": 256,
          "last_sld_id": 755
        }
      ]
    }
  },
  "_expansion": {
    "parent_id": "pptx/scale-slides",
    "bindings": {
      "size": "n500"
    }
  },
  "assertions": [
    {
      "id": "sldidlst-count-n500",
      "part": "ppt/presentation.xml",
      "namespaces": {
        "p": "http://schemas.openxmlformats.org/presentationml/2006/main"
      },
      "xpath": "string(count(/p:presentation/p:sldIdLst/p:sldId))",
      "must": "equal",
      "value": "500",
      "description": "The sldIdLst must list exactly N <p:sldId> entries."
    },
    {
      "id": "slide1-title-n500",
      "part": "ppt/slides/slide1.xml",
      "namespaces": {
        "p": "http://schemas.openxmlformats.org/presentationml/2006/main",
        "a": "http://schemas.openxmlformats.org/drawingml/2006/main"
      },
      "xpath": "//a:t[normalize-space()='Slide 1']",
      "must": "exist",
      "description": "The first slide must carry the title 'Slide 1', guarding against head-of-stream truncation."
    },
    {
      "id": "slide-last-title-n500",
      "part": "ppt/slides/slide500.xml",
      "namespaces": {
        "p": "http://schemas.openxmlformats.org/presentationml/2006/main",
        "a": "http://schemas.openxmlformats.org/drawingml/2006/main"
      },
      "xpath": "//a:t[normalize-space()='Slide 500']",
      "must": "exist",
      "description": "The last slide must carry the title 'Slide N', guarding against tail truncation or a missing slideN.xml part."
    }
  ]
}

Fixture

Download scale-slides--n500.pptx (417.3 KB)

Reference preview

No rendered reference is available for this case.

Spec notes

Each slide added via slides.add_slide() is registered as a <p:sldId> under <p:sldIdLst> in ppt/presentation.xml and emitted as ppt/slides/slideN.xml. python-pptx starts allocation at id=256 and increments by 1 per slide. This family verifies (a) the sldIdLst cardinality matches the requested count, (b) the first and last slide parts exist and carry the expected 'Slide 1' / 'Slide N' title, guarding against id-generator collisions, missed slide parts, or off-by-one truncation.