11package frankenphp
22
33import (
4+ "io"
5+ "math/rand/v2"
6+ "net/http/httptest"
47 "path/filepath"
8+ "sync"
9+ "sync/atomic"
510 "testing"
11+ "time"
612
713 "github.com/stretchr/testify/assert"
814 "go.uber.org/zap"
@@ -20,57 +26,132 @@ func TestStartAndStopTheMainThreadWithOneInactiveThread(t *testing.T) {
2026 assert .Nil (t , phpThreads )
2127}
2228
23- func TestTransition2RegularThreadsToWorkerThreadsAndBack (t * testing.T ) {
24- numThreads := 2
25- logger , _ = zap .NewDevelopment ()
26- assert .NoError (t , initPHPThreads (numThreads ))
29+ func TestTransitionRegularThreadToWorkerThread (t * testing.T ) {
30+ logger = zap .NewNop ()
31+ assert .NoError (t , initPHPThreads (1 ))
2732
28- // transition to worker thread
29- for i := 0 ; i < numThreads ; i ++ {
30- convertToRegularThread (phpThreads [i ])
31- assert .IsType (t , & regularThread {}, phpThreads [i ].handler )
32- }
33+ // transition to regular thread
34+ convertToRegularThread (phpThreads [0 ])
35+ assert .IsType (t , & regularThread {}, phpThreads [0 ].handler )
3336
3437 // transition to worker thread
35- worker := getDummyWorker ()
36- for i := 0 ; i < numThreads ; i ++ {
37- convertToWorkerThread (phpThreads [i ], worker )
38- assert .IsType (t , & workerThread {}, phpThreads [i ].handler )
39- }
40- assert .Len (t , worker .threads , numThreads )
38+ worker := getDummyWorker ("worker-transition-1.php" )
39+ convertToWorkerThread (phpThreads [0 ], worker )
40+ assert .IsType (t , & workerThread {}, phpThreads [0 ].handler )
41+ assert .Len (t , worker .threads , 1 )
4142
4243 // transition back to regular thread
43- for i := 0 ; i < numThreads ; i ++ {
44- convertToRegularThread (phpThreads [i ])
45- assert .IsType (t , & regularThread {}, phpThreads [i ].handler )
46- }
44+ convertToRegularThread (phpThreads [0 ])
45+ assert .IsType (t , & regularThread {}, phpThreads [0 ].handler )
4746 assert .Len (t , worker .threads , 0 )
4847
4948 drainPHPThreads ()
5049 assert .Nil (t , phpThreads )
5150}
5251
5352func TestTransitionAThreadBetween2DifferentWorkers (t * testing.T ) {
54- logger , _ = zap .NewDevelopment ()
53+ logger = zap .NewNop ()
5554 assert .NoError (t , initPHPThreads (1 ))
55+ firstWorker := getDummyWorker ("worker-transition-1.php" )
56+ secondWorker := getDummyWorker ("worker-transition-2.php" )
5657
5758 // convert to first worker thread
58- firstWorker := getDummyWorker ()
5959 convertToWorkerThread (phpThreads [0 ], firstWorker )
6060 firstHandler := phpThreads [0 ].handler .(* workerThread )
6161 assert .Same (t , firstWorker , firstHandler .worker )
62+ assert .Len (t , firstWorker .threads , 1 )
63+ assert .Len (t , secondWorker .threads , 0 )
6264
6365 // convert to second worker thread
64- secondWorker := getDummyWorker ()
6566 convertToWorkerThread (phpThreads [0 ], secondWorker )
6667 secondHandler := phpThreads [0 ].handler .(* workerThread )
6768 assert .Same (t , secondWorker , secondHandler .worker )
69+ assert .Len (t , firstWorker .threads , 0 )
70+ assert .Len (t , secondWorker .threads , 1 )
6871
6972 drainPHPThreads ()
7073 assert .Nil (t , phpThreads )
7174}
7275
73- func getDummyWorker () * worker {
74- path , _ := filepath .Abs ("./testdata/index.php" )
75- return & worker {fileName : path }
76+ func TestTransitionThreadsWhileDoingRequests (t * testing.T ) {
77+ numThreads := 10
78+ numRequestsPerThread := 100
79+ isRunning := atomic.Bool {}
80+ isRunning .Store (true )
81+ wg := sync.WaitGroup {}
82+ worker1Path , _ := filepath .Abs ("./testdata/transition-worker-1.php" )
83+ worker2Path , _ := filepath .Abs ("./testdata/transition-worker-2.php" )
84+
85+ Init (
86+ WithNumThreads (numThreads ),
87+ WithWorkers (worker1Path , 4 , map [string ]string {"ENV1" : "foo" }, []string {}),
88+ WithWorkers (worker2Path , 4 , map [string ]string {"ENV1" : "foo" }, []string {}),
89+ WithLogger (zap .NewNop ()),
90+ )
91+
92+ // randomly transition threads between regular and 2 worker threads
93+ go func () {
94+ for {
95+ for i := 0 ; i < numThreads ; i ++ {
96+ switch rand .IntN (3 ) {
97+ case 0 :
98+ convertToRegularThread (phpThreads [i ])
99+ case 1 :
100+ convertToWorkerThread (phpThreads [i ], workers [worker1Path ])
101+ case 2 :
102+ convertToWorkerThread (phpThreads [i ], workers [worker2Path ])
103+ }
104+ time .Sleep (time .Millisecond )
105+ if ! isRunning .Load () {
106+ return
107+ }
108+ }
109+ }
110+ }()
111+
112+ // randomly do requests to the 3 endpoints
113+ wg .Add (numThreads )
114+ for i := 0 ; i < numThreads ; i ++ {
115+ go func (i int ) {
116+ for j := 0 ; j < numRequestsPerThread ; j ++ {
117+ switch rand .IntN (3 ) {
118+ case 0 :
119+ assertRequestBody (t , "http://localhost/transition-worker-1.php" , "Hello from worker 1" )
120+ case 1 :
121+ assertRequestBody (t , "http://localhost/transition-worker-2.php" , "Hello from worker 2" )
122+ case 2 :
123+ assertRequestBody (t , "http://localhost/transition-regular.php" , "Hello from regular thread" )
124+ }
125+ }
126+ wg .Done ()
127+ }(i )
128+ }
129+
130+ wg .Wait ()
131+ isRunning .Store (false )
132+ Shutdown ()
133+ }
134+
135+ func getDummyWorker (fileName string ) * worker {
136+ if workers == nil {
137+ workers = make (map [string ]* worker )
138+ }
139+ absFileName , _ := filepath .Abs ("./testdata/" + fileName )
140+ worker , _ := newWorker (workerOpt {
141+ fileName : absFileName ,
142+ num : 1 ,
143+ })
144+ return worker
145+ }
146+
147+ func assertRequestBody (t * testing.T , url string , expected string ) {
148+ r := httptest .NewRequest ("GET" , url , nil )
149+ w := httptest .NewRecorder ()
150+ req , err := NewRequestWithContext (r , WithRequestDocumentRoot ("/go/src/app/testdata" , false ))
151+ assert .NoError (t , err )
152+ err = ServeHTTP (w , req )
153+ assert .NoError (t , err )
154+ resp := w .Result ()
155+ body , _ := io .ReadAll (resp .Body )
156+ assert .Equal (t , expected , string (body ))
76157}
0 commit comments