Skip to content

Commit 39bfc14

Browse files
authored
Merge pull request #118 from flashcatcloud/test/incident-comment-verify-stub-cleanup
test(incident): dedupe concurrency probe tests, tighten stub lock
2 parents 35a4620 + 39fc0f6 commit 39bfc14

2 files changed

Lines changed: 39 additions & 38 deletions

File tree

internal/cli/command_test.go

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1080,20 +1080,21 @@ func incidentFeedConcurrencyProbe(want string, delay time.Duration, inFlight, ma
10801080
}
10811081
}
10821082

1083-
// TestCommandIncidentCommentVerifyRunsConcurrently guards against a future
1084-
// refactor silently reverting verifyIncidentCommentsWritten to a sequential
1085-
// walk: with 4 incidents each sleeping on their /incident/feed request, a
1086-
// sequential implementation would never have more than one request in flight
1087-
// at once, while a correctly concurrent one will. Nothing about the command's
1088-
// exit code or output would catch that regression — only observing overlap
1089-
// directly, via incidentFeedConcurrencyProbe, does.
1090-
func TestCommandIncidentCommentVerifyRunsConcurrently(t *testing.T) {
1083+
// runIncidentCommentVerifyProbe runs `incident comment` against n incidents
1084+
// whose /incident/feed requests are all served by incidentFeedConcurrencyProbe,
1085+
// asserts the command itself still succeeded, and returns the peak number of
1086+
// feed requests the probe observed in flight at once. tag prefixes every
1087+
// failure message so a failing assertion names the calling test. Shared by
1088+
// TestCommandIncidentCommentVerifyRunsConcurrently and
1089+
// TestCommandIncidentCommentVerifyRespectsConcurrencyBound, which differ only
1090+
// in n and in what they consider a passing peak.
1091+
func runIncidentCommentVerifyProbe(t *testing.T, n int, tag string) int32 {
1092+
t.Helper()
10911093
saveAndResetGlobals(t)
10921094
stub := newGFStub(t)
10931095
var inFlight, maxInFlight atomic.Int32
10941096
stub.dataForPath = incidentFeedConcurrencyProbe("the real comment", 20*time.Millisecond, &inFlight, &maxInFlight)
10951097

1096-
const n = 4
10971098
ids := make([]string, n)
10981099
for i := range ids {
10991100
ids[i] = fmt.Sprintf("inc-%d", i+1)
@@ -1104,12 +1105,24 @@ func TestCommandIncidentCommentVerifyRunsConcurrently(t *testing.T) {
11041105
args = append(args, "--comment-file", commentFile)
11051106
out, err := execCommand(args...)
11061107
if err != nil {
1107-
t.Fatalf("[verify-concurrent] unexpected error: %v", err)
1108+
t.Fatalf("[%s] unexpected error: %v", tag, err)
11081109
}
11091110
if !strings.Contains(out, fmt.Sprintf("Commented on %d incident(s).", n)) {
1110-
t.Fatalf("[verify-concurrent] unexpected output:\n%s", out)
1111+
t.Fatalf("[%s] unexpected output:\n%s", tag, out)
11111112
}
1112-
if got := maxInFlight.Load(); got < 2 {
1113+
return maxInFlight.Load()
1114+
}
1115+
1116+
// TestCommandIncidentCommentVerifyRunsConcurrently guards against a future
1117+
// refactor silently reverting verifyIncidentCommentsWritten to a sequential
1118+
// walk: with 4 incidents each sleeping on their /incident/feed request, a
1119+
// sequential implementation would never have more than one request in flight
1120+
// at once, while a correctly concurrent one will. Nothing about the command's
1121+
// exit code or output would catch that regression — only observing overlap
1122+
// directly, via incidentFeedConcurrencyProbe, does.
1123+
func TestCommandIncidentCommentVerifyRunsConcurrently(t *testing.T) {
1124+
const n = 4
1125+
if got := runIncidentCommentVerifyProbe(t, n, "verify-concurrent"); got < 2 {
11131126
t.Fatalf("[verify-concurrent] observed peak in-flight verify requests = %d, want > 1 (verification never actually overlapped)", got)
11141127
}
11151128
}
@@ -1121,28 +1134,8 @@ func TestCommandIncidentCommentVerifyRunsConcurrently(t *testing.T) {
11211134
// the semaphore (e.g. an unbounded "go func" per incident) would fire every
11221135
// request at once and blow past it.
11231136
func TestCommandIncidentCommentVerifyRespectsConcurrencyBound(t *testing.T) {
1124-
saveAndResetGlobals(t)
1125-
stub := newGFStub(t)
1126-
var inFlight, maxInFlight atomic.Int32
1127-
stub.dataForPath = incidentFeedConcurrencyProbe("the real comment", 20*time.Millisecond, &inFlight, &maxInFlight)
1128-
11291137
const n = maxIncidentVerifyConcurrency * 3
1130-
ids := make([]string, n)
1131-
for i := range ids {
1132-
ids[i] = fmt.Sprintf("inc-%d", i+1)
1133-
}
1134-
commentFile := writeCommentFile(t, "the real comment")
1135-
1136-
args := append([]string{"incident", "comment"}, ids...)
1137-
args = append(args, "--comment-file", commentFile)
1138-
out, err := execCommand(args...)
1139-
if err != nil {
1140-
t.Fatalf("[verify-bound] unexpected error: %v", err)
1141-
}
1142-
if !strings.Contains(out, fmt.Sprintf("Commented on %d incident(s).", n)) {
1143-
t.Fatalf("[verify-bound] unexpected output:\n%s", out)
1144-
}
1145-
if got := maxInFlight.Load(); got > maxIncidentVerifyConcurrency {
1138+
if got := runIncidentCommentVerifyProbe(t, n, "verify-bound"); got > maxIncidentVerifyConcurrency {
11461139
t.Fatalf("[verify-bound] observed peak in-flight verify requests = %d, want <= %d (the worker limit)", got, maxIncidentVerifyConcurrency)
11471140
}
11481141
}

internal/cli/gfstub_test.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,20 @@ type gfStub struct {
3030
// is responsible for synchronizing that state itself.
3131
mu sync.Mutex
3232

33-
// lastPath is the path of the most recent request (no query string).
33+
// lastPath is the path of the most recent request (no query string). When
34+
// a test fans out concurrent requests, "most recent" is whichever
35+
// goroutine happened to reach this handler last, not any particular
36+
// logical request — such a test should assert against bodies, or a
37+
// dataForPath closure that inspects each request as it arrives, instead.
3438
lastPath string
35-
// lastBody is the decoded JSON body of the most recent request.
39+
// lastBody is the decoded JSON body of the most recent request. Same
40+
// concurrency caveat as lastPath.
3641
lastBody map[string]any
37-
// lastAuthorization is the Authorization header of the most recent request.
42+
// lastAuthorization is the Authorization header of the most recent
43+
// request. Same concurrency caveat as lastPath.
3844
lastAuthorization string
39-
// bodies records the decoded body of every request, in order.
45+
// bodies records the decoded body of every request, in the order each
46+
// reached this handler.
4047
bodies []map[string]any
4148
// requests counts how many requests reached the stub.
4249
requests int
@@ -70,11 +77,12 @@ func newGFStub(t *testing.T) *gfStub {
7077
_ = json.Unmarshal(raw, &body)
7178
}
7279
path := r.URL.Path
80+
authorization := r.Header.Get("Authorization")
7381

7482
s.mu.Lock()
7583
s.requests++
7684
s.lastPath = path
77-
s.lastAuthorization = r.Header.Get("Authorization")
85+
s.lastAuthorization = authorization
7886
s.lastBody = body
7987
s.bodies = append(s.bodies, body)
8088
dataForPath, dataFor, data := s.dataForPath, s.dataFor, s.data

0 commit comments

Comments
 (0)