From 7a0698d87b88d6aeb13e6bfd89792223a0c487de Mon Sep 17 00:00:00 2001 From: Rob Zolkos Date: Mon, 12 Oct 2026 13:26:50 -0400 Subject: [PATCH] Shrink two linear-time tests under the race detector, and shuffle the race run TestFindBareLinksPastRejectedPrefixesIsLinear and TestFindBareLinksInALongUnspacedParagraphIsLinear scan one large string on one goroutine. Under -race they took 44s and 13s, against 2.7s and 0.8s without it, and made internal/markdown the floor of Race Detection while giving the detector nothing to find. A race-tagged linearInputScale now divides their inputs by ten under -race; every build without -race runs them at full size, where the linear-time bound is held. Race Detection also runs with -shuffle=on, so a test that only passes in source order fails there with the seed to reproduce it. --- .github/workflows/test.yml | 2 +- internal/markdown/bare_links_test.go | 9 +++++---- internal/markdown/bound_race_test.go | 6 ++++++ internal/markdown/bound_test.go | 4 ++++ 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ecd4015b2..d81cd685d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -165,7 +165,7 @@ jobs: - name: Run tests with race detector env: GOCACHE: ${{ runner.temp }}/go-build-race - run: go test -race -count=1 ./... + run: go test -race -shuffle=on -count=1 ./... e2e: name: Bats e2e diff --git a/internal/markdown/bare_links_test.go b/internal/markdown/bare_links_test.go index ccd22d5e2..fb04d0108 100644 --- a/internal/markdown/bare_links_test.go +++ b/internal/markdown/bare_links_test.go @@ -537,7 +537,7 @@ func TestRenderWrapsANonASCIILinkWhole(t *testing.T) { // text with a link in every sentence; finding them must not scan to the paragraph's end // once for each. func TestFindBareLinksInALongUnspacedParagraphIsLinear(t *testing.T) { - const links = 20_000 + const links = 20_000 / linearInputScale for _, sentence := range []string{ "「mailto:jane@example.org」と", "「mailto:jane@example.org?subject=資料」と", @@ -619,8 +619,9 @@ func TestLinkifyURLsPastManyHyperlinksIsLinear(t *testing.T) { // A run of text full of prefixes that never become a link is read once, not once per // prefix, and a real link after them is still found. func TestFindBareLinksPastRejectedPrefixesIsLinear(t *testing.T) { + const repeats = 50_000 / linearInputScale for _, prefix := range []string{"https://", "www.", "mailto:", "https://a.b_", "mailto:a@b.co_"} { - run := strings.Repeat(prefix, 50_000) + " https://docs.example.com/room_list" + run := strings.Repeat(prefix, repeats) + " https://docs.example.com/room_list" found := make(chan []bareLink, 1) go func() { links, _ := findBareLinks(run) @@ -629,10 +630,10 @@ func TestFindBareLinksPastRejectedPrefixesIsLinear(t *testing.T) { select { case links := <-found: if len(links) == 0 || links[len(links)-1].target != "https://docs.example.com/room_list" { - t.Errorf("%q×50000: links end %v, want the room list last", prefix, links[max(0, len(links)-1):]) + t.Errorf("%q×%d: links end %v, want the room list last", prefix, repeats, links[max(0, len(links)-1):]) } case <-time.After(linearBound): - t.Fatalf("%q×50000: findBareLinks did not finish within %v", prefix, linearBound) + t.Fatalf("%q×%d: findBareLinks did not finish within %v", prefix, repeats, linearBound) } } } diff --git a/internal/markdown/bound_race_test.go b/internal/markdown/bound_race_test.go index 3321a9d65..ef5814911 100644 --- a/internal/markdown/bound_race_test.go +++ b/internal/markdown/bound_race_test.go @@ -7,3 +7,9 @@ import "time" // linearBound is longer under the race detector, which runs this code several times // slower; a quadratic run is slower by the same factor and still far past it. const linearBound = 60 * time.Second + +// linearInputScale shrinks the largest linear-time inputs under the race detector. Those +// tests scan one string on one goroutine, so the detector has nothing to find in them and +// only multiplies their cost (44s and 13s, against 2.7s and 0.8s without it). The +// full-size inputs run in every build without -race, where the bound is held. +const linearInputScale = 10 diff --git a/internal/markdown/bound_test.go b/internal/markdown/bound_test.go index 45a769ccc..b34ae5e97 100644 --- a/internal/markdown/bound_test.go +++ b/internal/markdown/bound_test.go @@ -8,3 +8,7 @@ import "time" // quadratic run it guards against: in one pass each takes well under a second, and the // quadratic runs took tens of seconds. const linearBound = 5 * time.Second + +// linearInputScale divides the largest linear-time inputs. Without the race detector +// they run at full size: this build is where the linear-time guarantee is held. +const linearInputScale = 1