mirror of
https://github.com/PerpetualSoftware/pad.git
synced 2026-10-25 22:10:25 +00:00
test(links): mention linearity tests compare n with 4n, not an absolute bound (BUG-3995) (#2262)
* test(links): mention linearity tests compare n with 4n, not an absolute bound (BUG-3995) TestExtractMentionsLinearAfterCodeSpans failed CI under -race at 3.08 s against its 3 s bound (#2259). Both mention linearity tests, it and its sibling TestExtractMentionsLinearOnSlashRuns, now share assertMentionsLinear: best of five runs at n and 4n, each after a runtime.GC(), and the ratio must stay under 8x (about 4x when linear, about 16x when quadratic). A 30 s per-run backstop stops a regressed scan from running the suite for minutes. No absolute bound remains. Claude-Session: https://claude.ai/code/session_01EZ6hZzQiytCeBxXrtCWcVd * test(links): the mention linearity helper's comment bounds its claim (BUG-3995 r1) It said a slow runner "cannot fail" the check and that the backstop stops a regressed scan "running the suite for minutes". The 30 s per-run backstop is absolute, and a regressed run measured 1.5 to 3 minutes before failing. The comment now says what is true: the ratio is independent of runner speed, and the backstop is about 16x the slowest linear run measured. Claude-Session: https://claude.ai/code/session_01EZ6hZzQiytCeBxXrtCWcVd
This commit is contained in:
@@ -2,6 +2,7 @@ package links
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -75,26 +76,53 @@ func TestParseMentions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractMentions stays linear in the body: a backward scan per "@" made
|
||||
// "/@a" repeated quadratic (TASK-3917 r3 F1; 240 KB took ~10 s). The bound is
|
||||
// ~60x the linear cost, so only a superlinear walk can trip it.
|
||||
func TestExtractMentionsLinearOnSlashRuns(t *testing.T) {
|
||||
body := strings.Repeat("/@a", 100000)
|
||||
start := time.Now()
|
||||
ExtractMentions(body)
|
||||
if d := time.Since(start); d > 3*time.Second {
|
||||
t.Fatalf("ExtractMentions on %d bytes took %v; a per-match backward scan is back", len(body), d)
|
||||
// assertMentionsLinear times ExtractMentions on body(n) and body(4n), best of
|
||||
// five each, and fails if 4x the input costs 8x the time or more: about 4x
|
||||
// when linear, about 16x when quadratic. A slow runner (-race, a loaded box)
|
||||
// slows both sizes alike, so the ratio does not depend on its speed, which a
|
||||
// 3 s absolute bound did (BUG-3995). The one absolute check is a 30 s per-run
|
||||
// backstop, about 16x the slowest linear run measured (-race, 4n): it caps how
|
||||
// long a regressed scan runs before failing, at a few minutes for the test.
|
||||
func assertMentionsLinear(t *testing.T, n int, body func(int) string, regression string) {
|
||||
t.Helper()
|
||||
timeIt := func(n int) time.Duration {
|
||||
b := body(n)
|
||||
best := time.Duration(0)
|
||||
for i := 0; i < 5; i++ {
|
||||
runtime.GC() // a collection left over from the last run is not this run's cost
|
||||
start := time.Now()
|
||||
ExtractMentions(b)
|
||||
d := time.Since(start)
|
||||
if d > 30*time.Second {
|
||||
t.Fatalf("n=%d: one run took %v; %s", n, d, regression)
|
||||
}
|
||||
if best == 0 || d < best {
|
||||
best = d
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
small, large := timeIt(n), timeIt(4*n)
|
||||
ratio := float64(large) / float64(small)
|
||||
t.Logf("n=%d: %v; n=%d: %v; ratio %.1f", n, small, 4*n, large, ratio)
|
||||
if ratio >= 8 {
|
||||
t.Fatalf("4x the input took %.1fx the time (%v -> %v); %s", ratio, small, large, regression)
|
||||
}
|
||||
}
|
||||
|
||||
// ExtractMentions stays linear in the body: a backward scan per "@" made
|
||||
// "/@a" repeated quadratic (TASK-3917 r3 F1; 240 KB took ~10 s).
|
||||
func TestExtractMentionsLinearOnSlashRuns(t *testing.T) {
|
||||
assertMentionsLinear(t, 40000, func(n int) string {
|
||||
return strings.Repeat("/@a", n)
|
||||
}, "a per-match backward scan is back")
|
||||
}
|
||||
|
||||
// The code-range check walks the spans once per call, not once per match: a
|
||||
// body of many inline code spans followed by many mentions was quadratic
|
||||
// (TASK-3917 r4; 360 KB took ~1.1 s, so a 2 MiB comment body ~40 s).
|
||||
func TestExtractMentionsLinearAfterCodeSpans(t *testing.T) {
|
||||
body := strings.Repeat("`a` ", 160000) + strings.Repeat("@aaa ", 160000)
|
||||
start := time.Now()
|
||||
ExtractMentions(body)
|
||||
if d := time.Since(start); d > 3*time.Second {
|
||||
t.Fatalf("ExtractMentions on %d bytes took %v; a per-match range scan is back", len(body), d)
|
||||
}
|
||||
assertMentionsLinear(t, 40000, func(n int) string {
|
||||
return strings.Repeat("`a` ", n) + strings.Repeat("@aaa ", n)
|
||||
}, "a per-match range scan is back")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user