Skip to content

Commit ad968b3

Browse files
committed
bumping coverage on pipeline
1 parent e840dde commit ad968b3

File tree

1 file changed

+84
-0
lines changed

1 file changed

+84
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package datamodel_test
2+
3+
import (
4+
"errors"
5+
"runtime"
6+
"sync/atomic"
7+
"testing"
8+
"time"
9+
10+
"github.com/pb33f/libopenapi/datamodel"
11+
"github.com/pb33f/libopenapi/orderedmap"
12+
"github.com/stretchr/testify/assert"
13+
"github.com/stretchr/testify/require"
14+
)
15+
16+
// TestTranslateMapParallel_ContextCancellation specifically targets lines 158-159
17+
// in translate.go which handle context cancellation during job dispatch.
18+
// This test ensures 100% coverage even on single-CPU systems like GitHub runners.
19+
//
20+
// The flaky coverage issue occurs because the select statement at lines 156-160:
21+
// select {
22+
// case jobChan <- j:
23+
// case <-ctx.Done():
24+
// return
25+
// }
26+
// The ctx.Done() branch (lines 158-159) is only hit when the context is cancelled
27+
// while the goroutine is blocked trying to send to jobChan. This is a race condition
28+
// that doesn't always occur, especially on single-CPU systems.
29+
//
30+
// This test forces the condition by:
31+
// 1. Setting GOMAXPROCS to 1 to limit concurrency
32+
// 2. Creating enough work items to fill the job channel
33+
// 3. Having the first job return an error to trigger context cancellation
34+
// 4. Running multiple iterations to ensure we hit the race condition
35+
func TestTranslateMapParallel_ContextCancellation(t *testing.T) {
36+
// Force single CPU to make the race condition more predictable
37+
oldMaxProcs := runtime.GOMAXPROCS(1)
38+
defer runtime.GOMAXPROCS(oldMaxProcs)
39+
40+
// Run the test multiple times to ensure we consistently hit the code path
41+
// This is necessary because even with our setup, the race condition might
42+
// not occur on the first try.
43+
for iteration := 0; iteration < 10; iteration++ {
44+
m := orderedmap.New[string, int]()
45+
const itemCount = 100
46+
for i := 0; i < itemCount; i++ {
47+
m.Set(string(rune('a'+i)), i)
48+
}
49+
50+
var translateStarted atomic.Bool
51+
var jobsBlocked atomic.Int32
52+
53+
translateFunc := func(pair orderedmap.Pair[string, int]) (string, error) {
54+
if translateStarted.CompareAndSwap(false, true) {
55+
// First job: wait briefly then return error to trigger cancel()
56+
// This causes context cancellation while other jobs are queuing
57+
time.Sleep(10 * time.Millisecond)
58+
return "", errors.New("trigger cancellation")
59+
}
60+
61+
// Other jobs: count how many get started
62+
jobsBlocked.Add(1)
63+
time.Sleep(100 * time.Millisecond)
64+
return "should not get here", nil
65+
}
66+
67+
resultFunc := func(value string) error {
68+
// Should not be called because translate returns error immediately
69+
return nil
70+
}
71+
72+
err := datamodel.TranslateMapParallel[string, int, string](m, translateFunc, resultFunc)
73+
require.Error(t, err)
74+
assert.Contains(t, err.Error(), "trigger cancellation")
75+
76+
// Wait for goroutines to clean up
77+
time.Sleep(20 * time.Millisecond)
78+
79+
// Verify context cancellation prevented all jobs from running
80+
// If lines 158-159 are hit, some jobs will be skipped
81+
assert.Less(t, int(jobsBlocked.Load()), itemCount-1,
82+
"Iteration %d: Context cancellation should prevent some jobs", iteration)
83+
}
84+
}

0 commit comments

Comments
 (0)