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