Cutdown conformance corpus
This directory contains the golden test fixtures for the Cutdown language spec. A compliant parser must pass all of them.
Structure
Fixtures are organized by spec section. Each subdirectory maps to one section:
tests/
003-document-model/
004-block-segments/
005-inline-segments/
006-universal-attributes/
008-escaping/
010-block-structure/
012-whitespace-rules/
diagnostics/ ← one fixture per CDN-xxxx diagnostic code
A test runner must walk all *.yaml files recursively — loading only the top level will silently pass zero tests.
Fixture format
Each .yaml file is a single fixture:
id: emphasis-basic
section: "5.2"
description: "__ delimiters produce Emphasis; single _ is literal text"
input: "__bold__"
ast:
- type: Paragraph
children:
- type: Emphasis
children:
- type: Text
value: bold
Fields
| Field | Required | Description |
|---|---|---|
id |
yes | Unique fixture ID (kebab-case, matches filename without .yaml) |
section |
yes | Spec section number as a string (e.g. "9.4") |
description |
yes | One-line human-readable description |
input |
yes | Raw Cutdown source text |
ast |
— | Expected children of the first page (see Assertions) |
pages |
— | Expected full Document.children array; use when the fixture spans multiple pages. Overrides ast when present |
diagnostics |
— | Expected diagnostics (see Assertions); if omitted, the parser must emit zero diagnostics |
Exactly ONE OF ast or pages MUST be present.
Assertions
AST matching uses a deep subset match:
- All keys present in the expected object MUST exist in the actual output with identical values.
- Extra keys in the actual output MAY exist and are ignored (implementations may add position info, etc.).
- Arrays MUST be the same length; each element is subset-matched recursively.
- Primitives MUST be compared by value (
===).
This means a minimal fixture only needs to assert the fields it cares about — it will not break if the parser adds new optional fields later.
Streaming-profile assertions
Fixtures under 016-streaming-conformance-profile/ use the ordinary final-input assertions above and MAY add a streaming object:
streaming:
unit: unicode-scalar
checkpoints:
- at: 2
ast: []
diagnostics: []
at is the count of decoded Unicode scalar values from the beginning of input. A streaming-profile runner MUST parse every scalar prefix of input, including the empty prefix and the full input. It MUST verify total, deterministic parsing for every prefix. It additionally verifies the listed checkpoint AST/pages and diagnostics. Each checkpoint is evaluated as an ordinary input snapshot; it is not a separate parser mode.
Diagnostics matching is order-independent:
- Every
{ code, level }entry listed underdiagnosticsMUST appear in the actual output. - No diagnostic MAY appear in the actual output that is not listed in the fixture.
- If
diagnosticsis omitted entirely, the parser MUST produce zero diagnostics.
ast vs pages
ast matches the children array of the first page — the common case for single-page documents:
ast:
- type: Paragraph
children:
- type: Text
value: Hello
pages matches the full Document.children array and is used when the fixture tests page-splitting (PageBreak, Meta block pagination). A Ghost Page is written {}:
pages:
- children:
- type: Paragraph
children:
- type: Text
value: First
- {}
Attribute fixtures
Attributes are asserted as an ordered array of { key, value } objects. The class shorthand (.class) produces value: string[] (array of strings); all other values are plain strings:
ast:
- type: List
attributes:
- key: class
value: [list-class]
children:
- type: ListItem
attributes:
- key: class
value: [item-class]
Diagnostic fixtures
Fixtures in diagnostics/ test warning behaviour. The fixture asserts both the AST produced (recovery output) and the diagnostic code emitted:
id: cdn-0001-unclosed-codeblock
section: "4.4"
description: "Unclosed ``` fence; content runs to end of document; CDN-0001 warning emitted"
input: "```python\nx = 1\n"
ast:
- type: CodeBlock
language: python
content: x = 1
diagnostics:
- code: CDN-0001
level: warning
ID and filename conventions
- ID is kebab-case:
code-block-basic,cdn-0001-unclosed-codeblock - Filename matches ID exactly:
code-block-basic.yaml - Diagnostic fixtures are prefixed with the code:
cdn-0001-*.yaml - Variant fixtures suffix the base name:
emphasis-basic,emphasis-triple,emphasis-unclosed
Contributing a test
- Identify the spec section the fixture covers.
- Create
tests/<section-dir>/<id>.yaml. Use an existing fixture in that directory as a template. - Fill all required fields. Keep
inputminimal — test one behaviour per fixture. - Verify the
idis unique across the entire corpus (no two fixtures share anid). - Open a PR. See
CONTRIBUTION.mdfor process.
All fixtures
002-comments
- comment-block-basic
- comment-block-in-list-item
- comment-block-opaque-content
- comment-block-pass-through-page-assembly
- comment-inline-after-attrs-transparent
- comment-inline-basic
- comment-inline-between-paragraph-lines
- comment-inline-degrades-emphasis
- comment-inline-escape
- comment-inline-heading-attrs-transparent
- comment-inline-in-codeinline-literal
- comment-inline-in-link-text-degrades
- comment-inline-in-meta-literal
- comment-inline-midline
- comment-inline-namedblock-opener
- comment-inline-triple-hash-inline
- comment-table-delim-row-trailing
- comment-table-mid-cell-degrades-to-paragraph
- comment-table-row-trailing
- single-hash-literal
003-document-model
- consecutive-breaks-ghost-page
- document-empty
- meta-block-opens-new-page
- pagebreak-leading-ghost-page
- pagebreak-only-document
- pagebreak-page-split
- pagination-meta-after-break-ghost
- pagination-meta-first-fills-initial-page
- section-bounded-by-container
- section-closed-by-pagebreak
- section-nesting
- section-skipped-levels
004-block-segments
- code-block-attrs
- code-block-basic
- code-block-inside-listitem
- code-block-inside-namedblock
- code-block-inside-quoteblock
- code-block-literal
- fileref-basic
- fileref-fragment
- fileref-groups-dont-merge
- fileref-query-and-fragment
- fileref-query-string
- heading-attrs
- heading-first-line-of-container
- heading-inline-formatting
- heading-inline-variable
- heading-inside-listitem
- heading-inside-quote-inside-list
- heading-inside-quoteblock
- heading-last-attr-rule
- heading-level1
- heading-level2
- imageblock-basic
- imageblock-inline-alt
- imageblock-not-only-segment-falls-back
- list-blank-col0-resets
- list-blank-indented-absorbed
- list-inline-hyphen-continuation
- list-inline-hyphen
- list-intraitem-blank
- list-item-inside-quote-inside-list
- list-loose
- list-mixed-numeric-task
- list-mixed-task-numeric
- list-multiline-item
- list-nested
- list-ordered-inline-dot
- list-ordered-nonone-start
- list-ordered
- list-stack-arbitrary-indent
- list-unordered
- math-block-attrs
- math-block-basic
- math-block-inside-listitem
- meta-block-inside-container
- meta-block-toml
- meta-block-yaml
- named-block-attrs
- named-block-basic
- named-block-indent-collapse
- named-block-nested
- pagebreak-basic
- pagebreak-four-hyphens
- pagebreak-glued-to-paragraph
- pagebreak-inside-listitem
- pagebreak-inside-namedblock
- pagebreak-inside-quoteblock
- paragraph-basic
- paragraph-continuation-leading-spaces
- paragraph-multiline-trailing-space
- paragraph-multiline
- paragraph-textbreak
- quote-block-attrs-own-line
- quote-block-attrs-trailing
- quote-block-basic
- quote-block-consecutive
- quote-block-nested
- quote-block-no-lazy-continuation
- quote-block-strong-empty-run-of-four
- quote-block-with-ordered-list
- quote-block-with-tasklist
- ref-definition-basic
- spoiler-block-attrs
- spoiler-block-basic
- spoiler-block-indent-collapse
- spoiler-block-inside-listitem
- spoiler-block-inside-quoteblock
- spoiler-block-nested-in-named
- spoiler-block-unclosed
- table-attrs-last-row-noop
- table-attrs-last-row-single
- table-attrs-last-row
- table-cell-inline-attrs-before-closer
- table-cell-inline-attrs-non-final-cell
- table-cell-inline-attrs-orphan
- table-empty
- table-header-separator-attrs
- table-header-separator-narrow
- table-header-separator-wide-surplus
- table-multiline-attrs
- table-multiline-basic
- table-multiline-header
- table-multiline-multirow-cell
- table-multiline-plus-colons-inert
- table-multiline-plus-row-wider-inert
- table-multiline-scope-chain-stops-at-row
- table-opens-with-header-separator
- table-pipe-dash-placeholder-not-separator
- table-pipe-explicit-empty-last-cell
- table-pipe-no-closer
- table-pipe-plus-row-ignored
- table-pipe
- table-row-short-padded
- table-row-surplus-dropped
- table-scope-chain-attrs
- table-scope-chain-inline-last-cell-only
- table-scope-chain-inline-open-cell
- table-scope-chain-inline-sealed-cell
- table-simple
- task-item-checked-plus
- task-item-checked
- task-item-mixed-list
- task-item-unchecked
005-inline-segments
- attr-brace-literal-span-idiom
- code-inline-basic
- code-inline-escape-backslash
- code-inline-escape-backtick
- code-inline-escape-unknown
- code-inline-escape-with-run-of-three
- code-inline-softbreak
- emphasis-basic
- emphasis-cross-nesting
- emphasis-empty
- emphasis-single-asterisk
- emphasis-spanning-lines
- emphasis-triple
- emphasis-unclosed
- highlight-basic
- highlight-with-attrs
- image-inline
- link-both-brackets-empty
- link-cite-shorthand-no-text
- link-cite
- link-closed-twin-inner-survives
- link-comment-cut-slice
- link-empty-href
- link-external
- link-nested-dead-openers
- link-page
- link-ref
- link-tag
- link-unclosed-verbatim-slice
- math-inline-basic
- math-inline-single-dollar
- math-inline-spaced
- math-inline-text-after
- math-inline-unclosed
- quote-inline-cross-nesting
- quote-inline-double-spaced
- quote-inline-double
- quote-inline-single-spaced
- quote-inline-single
- quote-inline-unclosed
- span-attrs
- span-basic
- span-no-name
- spoiler-attrs
- spoiler-basic
- spoiler-boundary-cross
- spoiler-escape
- spoiler-nested-emphasis
- spoiler-run-of-three
- spoiler-same-type-nesting
- spoiler-single-caret-literal
- spoiler-unclosed
- spoiler-with-link
- strong-basic
- strong-run-of-four-splits
- strong-unclosed-inner-survives
- text-break
- variable-basic
- variable-empty-key
- variable-id-literal
- variable-unclosed
006-universal-attributes
- attrs-bare-key
- attrs-class-merge
- attrs-escape-brace
- attrs-heading-last-attr-empty
- attrs-heading-last-attr-inline
- attrs-heading-last-attr-simple
- attrs-link-text-orphan
- attrs-link-text
- attrs-orphan-quoteblock
- attrs-scope-chain-fileref-1
- attrs-scope-chain-fileref-empty
- attrs-scope-chain-fileref-noop
- attrs-scope-chain-fileref
- attrs-scope-chain-list
- attrs-scope-chain-span-1
- attrs-scope-chain-span-2
- attrs-scope-chain-span-empty
- attrs-scope-chain-span-noop
- attrs-scope-chain-span
- attrs-scope-chain-text-1
- attrs-scope-chain-text-3
- attrs-scope-chain-text
- caption-attrs-own-line
- caption-blank-line-breaks-binding
- caption-codeblock-basic
- caption-comment-own-line-transparent
- caption-fileref-basic
- caption-filerefgroup-basic
- caption-filerefgroup-mid-run-close
- caption-imageblock-basic
- caption-inline-emphasis
- caption-inline-variable
- caption-mathblock-basic
- caption-namedblock-basic
- caption-quoteblock-attribution
- caption-scope-local-inside-namedblock
- caption-second-line-is-paragraph
- caption-spoilerblock-basic
- caption-table-basic
007-input-interpretation
008-escaping
- escape-backslash
- escape-codeblock-closer
- escape-codeblock-opener
- escape-commentblock-closer
- escape-commentblock-opener
- escape-fileref-opener
- escape-heading-opener
- escape-list-opener
- escape-mathblock-no-closer-escape
- escape-mathblock-opener
- escape-meta-closer
- escape-meta-opener
- escape-namedblock-opener
- escape-non-special
- escape-pagebreak-opener
- escape-quote-opener
- escape-special-char
- escape-spoilerblock-opener
010-block-structure
- block-cannot-interrupt-paragraph-inside-quote
- block-cannot-interrupt-paragraph
- multiple-blank-lines
- paragraph-only-comment-not-elided
- trim-codeblock-edge-blanks-preserved
- trim-namedblock-edge-blanks
- trim-namedblock-empty-body
- trim-quoteblock-edge-blanks
- trim-spoilerblock-edge-blanks
012-whitespace-rules
- attr-orphan-no-strip
- attr-strip-heading
- attr-strip-namedblock-opener
- attr-strip-paragraph-trailing
- trailing-space-before-comment
- trailing-space-collapse-and-drop
- whitespace-adjacent-inlines
- whitespace-boundary-strip
- whitespace-empty-delimiter
- whitespace-inline-adjacent
- whitespace-interior-collapse
- whitespace-nested-boundary
016-streaming-conformance-profile
- code-fence-eof
- inline-symmetric-eof
- link-bracket-suffix
- page-break-boundary
- section-boundary
- table-column-count-monotonic
diagnostics
- cdn-0001-unclosed-codeblock
- cdn-0002-unclosed-metablock
- cdn-0003-unclosed-mathblock
- cdn-0004-unclosed-namedblock
- cdn-0005-unclosed-spoilerblock
- cdn-0006-unclosed-commentblock
- cdn-0008-orphaned-caption-after-imageblock-fallback
- cdn-0008-orphaned-caption-blank-line
- cdn-0008-orphaned-caption-no-predecessor
- cdn-0008-orphaned-caption-non-captionable
- cdn-0008-orphaned-caption-slot-filled
- cdn-0009-attrs-on-caption
- cdn-0011-orphaned-scope-chain
- cdn-0012-heading-level-out-of-range
- cdn-0013-nameless-namedblock-opener
- cdn-0014-emphasis-strong-cross
- cdn-0014-no-fire-valid-nesting
- cdn-0014-quote-link-cross
- cdn-0015-invalid-variable-key
- cdn-0016-no-fire-bare-pagebreak
- cdn-0016-pagebreak-tail-dropped
- cdn-0017-no-fire-glued-hyphens
- cdn-0017-pagebreak-inside-container
- cdn-0018-no-fire-plus-row-mismatch
- cdn-0018-no-fire-short-row
- cdn-0018-table-surplus-cells
- cdn-0020-duplicate-id
- cdn-0021-class-syntax-conflict
- cdn-0022-duplicate-custom-key
- cdn-0030-meta-block-inside-container