Skip to content

test(conformance): add blind cases for whitespace in an HTML tag - #67

Merged
CameronBrooks11 merged 9 commits into
mainfrom
test/tag-whitespace-blind-cases
Oct 21, 2026
Merged

CameronBrooks11 merged 9 commits into
mainfrom
test/tag-whitespace-blind-cases

Conversation

@CameronBrooks11

@CameronBrooks11 CameronBrooks11 commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Adds 22 conformance cases for the spacing CommonMark 0.31.2 allows inside an HTML tag (§6.6) and after one (§4.6, start condition 7). They were written from SPEC.md at f7533e4 by an author who had not seen the implementation, and land byte for byte as written (sha256 of every file checked after copying; no name collides with an existing case). Eight of them failed against the reference. That is #66, fixed here in its own commit.

Commits

  1. test(conformance): add blind cases for whitespace in an HTML tag: the cases alone.
  2. fix(reference): limit HTML tag spacing to spaces, tabs and a line ending: closes reference: HTML tag whitespace accepts vertical tab and form feed #66.
  3. test(conformance): raise the case floor to 542: MIN_CASES 525 → 542.
  4. docs(conformance): record the provenance of the tag spacing cases: the README provenance paragraph.
  5. test(conformance): pin HTML tag spacing in the mutation gate: two mutants for the reader, below.
  6. test(conformance): add blind cases for VT and FF after an attribute's =: five more cases, alone (below).
  7. test(conformance): pin the spacing after an attribute's = in the gate: a third mutant.
  8. test(conformance): raise the case floor to 547: MIN_CASES 542 → 547.
  9. docs(conformance): record the provenance of the five tag spacing cases: the README paragraph, extended.

Every case, and its outcome against the reference

"Before" is the reference at f7533e4, unchanged. "After" is with commit 2. VT is U+000B, FF is U+000C.

case input (one line) expected before after
accepts-a-form-feed-between-tag-name-and-attribute <a FF b=c> paragraph FAIL: got raw-html pass
accepts-a-vertical-tab-between-tag-name-and-attribute <a VT b=c> paragraph FAIL: got raw-html pass
refuses-a-tab-between-tag-name-and-attribute <a tab b=c> raw-html pass pass
accepts-a-form-feed-after-an-open-tag <a b=c> FF paragraph FAIL: got raw-html pass
accepts-a-vertical-tab-after-an-open-tag <a b=c> VT paragraph FAIL: got raw-html pass
refuses-a-tab-after-an-open-tag <a b=c> tab raw-html pass pass
accepts-a-form-feed-before-an-attribute-equals-sign <a b FF =c> paragraph FAIL: got raw-html pass
refuses-a-tab-before-an-attribute-equals-sign <a b tab =c> raw-html pass pass
accepts-a-form-feed-before-an-open-tag-s-bracket <a FF > paragraph FAIL: got raw-html pass
refuses-a-tab-before-an-open-tag-s-bracket <a tab > raw-html pass pass
accepts-a-form-feed-before-a-closing-tag-s-bracket </a FF > paragraph FAIL: got raw-html pass
refuses-a-tab-before-a-closing-tag-s-bracket </a tab > raw-html pass pass
refuses-a-form-feed-opening-an-unquoted-attribute-value <a b= FF c> raw-html pass pass
refuses-a-tab-before-an-unquoted-attribute-value <a b= tab c> raw-html pass pass
accepts-a-backtick-opening-an-unquoted-attribute-value <a b=`c> paragraph pass pass
refuses-a-space-and-tab-after-an-open-tag <a b=c> space tab raw-html pass pass
accepts-a-space-and-form-feed-after-an-open-tag <a b=c> space FF paragraph FAIL: got raw-html pass
accepts-a-form-feed-before-a-single-quoted-attribute-value <a b= FF 'q'> paragraph FAIL: got raw-html pass
accepts-a-vertical-tab-before-a-single-quoted-attribute-value <a b= VT 'q'> paragraph FAIL: got raw-html pass
refuses-a-tab-before-a-single-quoted-attribute-value <a b= tab 'q'> raw-html pass pass
accepts-a-vertical-tab-before-an-attribute-equals-sign <a b VT =c> paragraph FAIL: got raw-html pass
accepts-a-vertical-tab-before-an-open-tag-s-bracket <a VT > paragraph FAIL: got raw-html pass

The last five rows are the second set (commit 6). Their "before" column comes from running them against the reader as at f7533e4, because the fix had already landed on this branch when they were added. All are under refusal/raw-html/. The eight failures are exactly the accepting cases with VT or FF in a tag's spacing or after the tag, which is what #66 describes. No other case failed. The author recorded no question the text leaves undecided (AMBIGUITIES: none).

The second set: the spacing after =

Review found that no case pinned the spacing after an attribute's =. With VT and FF put back into that one place, every case passed (0 failures across 542; reproduced). <a b= FF 'q'> tells the two readings apart. Under §6.6 the FF is not spacing, so it starts an unquoted value. That value has to stop before ', so the line holds no tag. If FF were spacing, 'q' would be a quoted value, and the line would be a type 7 HTML block.

The same author added five cases, from the same files and with the same constraints, and landed them byte for byte (hash-checked; no name collides). The author recorded no undecided question. Two of the five are the VT twins of refuses-a-tab-before-an-attribute-equals-sign and refuses-a-tab-before-an-open-tag-s-bracket. Those two refusals are left unchanged, so they do not list the twins as controls. The new tab refusal lists the FF and VT single-quoted accepts as its controls.

Commit d3dd75c's message says that a form feed or vertical tab "between the tag name and an attribute, before an attribute's =, before an open or closing tag's >, or after the tag" makes no HTML block. Its cases covered VT in only two of those positions: between the tag name and an attribute, and after an open tag. The new cases add VT before = and before an open tag's >. One position the message names still has no VT case: VT before a closing tag's >. The FF case there (accepts-a-form-feed-before-a-closing-tag-s-bracket) goes through the same spacing class, and the in-tag mutant puts VT and FF back together, so no mutant in the table survives on it.

The reference fix (#66)

The reader used one class, space, tab, LF, VT, FF and CR, both for spacing inside a tag and for the run after it. Now:

  • Spacing inside a tag (§6.6: "spaces, tabs, and up to one line ending") is spaces and tabs with at most one line ending among them. Before an attribute it must be non-empty.
  • The run after the tag (§4.6, condition 7: "zero or more spaces and tabs") is spaces and tabs only.
  • The unquoted attribute value class from reference: unquoted attribute value refuses control characters CommonMark allows #64 is unchanged, so <a b= FF c> is still refused: the FF is part of the value.

Each half is pinned by its own cases, and just mutants now checks both (commits 5 and 7). The reader's table has three new mutants:

  • tag-spacing-takes-vertical-tab-and-form-feed puts VT and FF back into the spacing inside a tag (_WS and _WS1). It is killed by 9 cases: the 5 from the first set and the 4 accepting cases of the second.
  • spacing-after-an-attribute-equals-takes-vertical-tab-and-form-feed puts VT and FF back into the spacing after = only. It survived the first set and is killed by 2 cases: the FF and VT single-quoted accepts.
  • a-tag-may-be-followed-by-vertical-tab-and-form-feed puts VT and FF back into condition 7's trailing run. It is killed by 3 cases: FF, VT, and space plus FF after an open tag.

The reader splits lines before it matches a start condition, so no line ending reaches these patterns and no case can observe the "up to one line ending" limit. Matched directly, the patterns take <a, LF, b=c> and </a, LF, > as tags, and reject the same with two LFs. They also reject <a href='bar'title=title>, §6.6's Missing whitespace example.

Gates

Each run on its own, exit code read directly:

just check    exit 0   All checks passed!
just test     exit 0   181 passed
just conform  exit 0   0 failure(s) across 547 case(s) in the fixture tree
just tree     exit 0   547 cases: 178 accept, 91 refuse, 278 anchor / 0 problem(s)
just mutants  exit 0   reader:   96 killed, 0 survived, 0 equivalent, 0 stale, 0 broken
                       resolver: 123 killed, 0 survived, 1 equivalent, 0 stale, 0 broken

On main the reader's table has 93 mutants, none on tag spacing. The three new ones bring it to 96, and all are killed. The resolver's totals are unchanged.

Refs #66

Adds 17 cases under refusal/raw-html/ for the spacing CommonMark 0.31.2
allows inside an HTML tag (section 6.6: "spaces, tabs, and up to one
line ending") and after one (section 4.6, start condition 7: "zero or
more spaces and tabs"). They were written from SPEC.md at f7533e4 by an
author who had not seen the implementation, and are added byte for
byte as written.

- A form feed or vertical tab between the tag name and an attribute,
  before an attribute's `=`, before an open or closing tag's `>`, or
  after the tag, makes no HTML block, so the line is a paragraph.
  Each has a twin with a tab there, refused as raw-html.
- A form feed opening an unquoted attribute value is part of the value
  (section 6.6 does not exclude it), so that tag is refused; a backtick
  there is excluded, so that line is a paragraph.
- After a complete tag, a space and a tab are refused, and a space and
  a form feed are a paragraph.

The author worked from SPEC.md, conformance/cases/README.md and
case-body.schema.json as at f7533e4, kindkit's case-tree/CASE-TREE.md,
and CommonMark 0.31.2's spec.txt, and recorded no question the text
leaves undecided.

Refs #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
CommonMark 0.31.2 section 6.6 allows "spaces, tabs, and up to one line
ending" before an attribute, around an attribute's `=`, and before an
open or closing tag's `>`. Section 4.6's start condition 7 allows "zero
or more spaces and tabs" after the tag. The reader used one class for
both, [ \t\n\v\f\r], so a vertical tab or form feed in either place
still made a type 7 HTML block, and `<a` U+000C `b=c>` or `<a b=c>`
U+000C was refused as raw-html where CommonMark reads a paragraph.

The spacing inside a tag is now spaces and tabs with at most one line
ending among them, and must be non-empty before an attribute. The run
after the tag is spaces and tabs only. The unquoted attribute value is
unchanged: a form feed may still be part of one.

The eight accepting cases added with this, under refusal/raw-html/,
fail without the change and pass with it. Restoring vertical tab and
form feed to only the trailing run fails three of them, and to only
the spacing inside the tag the other five.

Closes #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
The tree now holds 542 cases (check_tree.py: "542 cases: 174 accept,
90 refuse, 278 anchor").

Refs #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
Refs #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
Adds two mutants to the reader's table, each restoring vertical tab
and form feed to one half of the tag spacing #66 narrowed: the spacing
inside a tag (_WS and _WS1), and the run after a type 7 tag. just
mutants kills the first with five refusal/raw-html cases and the
second with three, and the reader's table now reports 95 killed, 0
survived.

Refs #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
Adds 5 cases under refusal/raw-html/, written by the same author, from
the same files and under the same constraints as the 17 before them
(SPEC.md, conformance/cases/README.md and case-body.schema.json as at
f7533e4, kindkit's case-tree/CASE-TREE.md and CommonMark 0.31.2's
spec.txt), without seeing the implementation. They are added byte for
byte as written.

No case pinned the spacing after an attribute's `=`: with vertical tab
and form feed put back into that one spot, every case still passed.
`<a b=` FF `'q'>` tells the readings apart. Under section 6.6 the FF is
not spacing, so it opens an unquoted value that must stop before `'`,
and the line holds no tag; with FF as spacing, `'q'` is a quoted value
and the line is a type 7 HTML block.

- FF, and VT, before a single-quoted value: a paragraph.
- A tab there: refused as raw-html, with the two above as controls.
- VT before an attribute's `=`, and VT before an open tag's `>`: a
  paragraph. They are the vertical-tab twins of two refusals already
  in the tree, which are left as they are and so do not list them as
  controls.

The four accepting cases fail against the reader as at f7533e4 and
pass with #66's fix. The author recorded no question the text leaves
undecided.

Refs #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
Adds a mutant to the reader's table that puts vertical tab and form
feed back into the spacing after an attribute's `=` alone. With the
tree as it stood it survived, 0 failures across 542 cases. The two
single-quoted cases added before this kill it, and the reader's table
now reports 96 killed, 0 survived.

Refs #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
The tree now holds 547 cases (check_tree.py: "547 cases: 178 accept,
91 refuse, 278 anchor").

Refs #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
Refs #66

Signed-off-by: Cameron Brooks <95648474+CameronBrooks11@users.noreply.github.com>
@CameronBrooks11
CameronBrooks11 merged commit 3629226 into main Oct 21, 2026
6 checks passed
@CameronBrooks11
CameronBrooks11 deleted the test/tag-whitespace-blind-cases branch October 21, 2026 04:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

reference: HTML tag whitespace accepts vertical tab and form feed

1 participant