From 1ac78a2f778ce44a9f7248fa69b47605cca11bca Mon Sep 17 00:00:00 2001 From: Nils Lehnen <30603423+iderex@users.noreply.github.com> Date: Thu, 27 Aug 2026 08:34:54 +0200 Subject: [PATCH] Assert the order the page reader hands its problems back in [#44] Read sorts the problems it found by the line they are on, and nothing asserted the sort. Both mutants the tool reports at that comparison were applied by hand at 0112a59 and left the suite green: internal/markup/markup.go:227 Line < Line -> Line >= Line internal/markup/markup.go:227 Line < Line -> Line <= Line The order is not incidental to what the checker is for. The list is what somebody repairs a page from, and reversing the comparison hands them the bottom of the file first. It cannot be got right by accident either: an element still open when the input ends is only known to be open once the input has ended, so its problem is appended after every problem found further down the page, and the sort is the only thing that puts line 2 back in front of line 10. Two cases, one per direction of the same comparison. The first reads a document that ends with three elements still open and a skipped heading level below them, and asserts the four lines come back ascending. The document is not the wrapper the rest of the file uses, because that wrapper closes everything it opens and the out-of-order append cannot arise in it. The second puts three problems on one line and asserts they stay in the order the reader found them. That is the half the first case cannot see: every line in it is different, and a comparison that is no longer strict sorts distinct keys correctly and swaps equal ones. Three kinds on one line separate the two. Each of the two mutations above was applied again with these cases in, and a third, and all three produce a red run naming the property they removed. Signed-off-by: Nils Lehnen <30603423+iderex@users.noreply.github.com> --- internal/markup/markup_test.go | 68 ++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/internal/markup/markup_test.go b/internal/markup/markup_test.go index 74848e2..cfe9270 100644 --- a/internal/markup/markup_test.go +++ b/internal/markup/markup_test.go @@ -251,3 +251,71 @@ func TestOnlyASkippedHeadingLevelIsRefused(t *testing.T) { t.Errorf("a jump from the top level to the deepest was read as %d problem(s): %v", len(got), got) } } + +// The order the problems come back in, which is the order somebody repairs the +// page in. +// +// It is a sort rather than the order they were found, and nothing asserted it. +// An element still open when the input ends is only known to be open once the +// input has ended, so its problem is appended after every problem found further +// down the page. Reversing the comparison leaves a reader sent to line 10 first +// and then back up to line 2, over a list whose whole use is repairing a file +// from the top. +// +// The document here is deliberately not the wrapper the rest of this file uses. +// That wrapper closes everything it opens, and an element still open at the end +// of the input is the only shape that produces the out-of-order append. +func TestTheProblemsComeBackInTheOrderThePageReadsIn(t *testing.T) { + got := Read([]byte(` + + + + A title + + +
+

A

+

B

+`)) + + want := []int{2, 7, 8, 10} + if len(got) != len(want) { + t.Fatalf("%d problem(s) came back, and the page breaks %d rules: %v", len(got), len(want), got) + } + for i := range want { + if got[i].Line != want[i] { + t.Fatalf("problem %d is on line %d, and the page reads them in the order %v: %v", i+1, got[i].Line, want, got) + } + } + // The heading is the problem found first and reported last, which is the whole + // of what the sort is for. Naming it stops a fixture that had stopped producing + // it from leaving this case passing on four lines that happen to ascend. + if got[len(got)-1].Kind != Heading { + t.Errorf("the last problem is %s, and the one found before the input ended is the heading", got[len(got)-1].Kind) + } +} + +// Two problems on one line stay in the order the reader found them, which is the +// other half of the same comparison. A sort that is not strict swaps equal keys +// instead of leaving them alone, so the three below come back in an order the +// page does not read in, and the run above cannot see it because every line in +// it is different. +func TestProblemsOnOneLineStayInTheOrderTheyWereFound(t *testing.T) { + got := Read(page(`
+

A

+

B

One
Two
+
`)) + + want := []string{Heading, Alt, Identity} + if len(got) != len(want) { + t.Fatalf("%d problem(s) came back, and the one line breaks %d rules: %v", len(got), len(want), got) + } + for i := range want { + if got[i].Line != 10 { + t.Fatalf("problem %d is on line %d, and every one of them is on line 10: %v", i+1, got[i].Line, got) + } + if got[i].Kind != want[i] { + t.Fatalf("problem %d is %s, and the reader finds them in the order %v: %v", i+1, got[i].Kind, want, got) + } + } +}