88
99namespace OCA \Libresign \Tests \Unit \Service \Worker ;
1010
11+ use OCA \Libresign \Service \Process \ProcessManager ;
1112use OCA \Libresign \Service \Worker \StartThrottlePolicy ;
1213use OCA \Libresign \Service \Worker \WorkerConfiguration ;
13- use OCA \Libresign \Service \Worker \WorkerCounter ;
1414use OCA \Libresign \Service \Worker \WorkerHealthService ;
1515use OCA \Libresign \Service \Worker \WorkerJobCounter ;
1616use OCA \Libresign \Service \Worker \WorkerStarter ;
1717use OCA \Libresign \Tests \Unit \TestCase ;
18+ use PHPUnit \Framework \Attributes \DataProvider ;
1819use PHPUnit \Framework \MockObject \MockObject ;
1920use Psr \Log \LoggerInterface ;
2021
2122class WorkerHealthServiceTest extends TestCase {
2223 private WorkerConfiguration &MockObject $ workerConfiguration ;
23- private WorkerCounter &MockObject $ workerCounter ;
24+ private ProcessManager &MockObject $ processManager ;
2425 private WorkerJobCounter &MockObject $ workerJobCounter ;
2526 private StartThrottlePolicy &MockObject $ startThrottlePolicy ;
2627 private WorkerStarter &MockObject $ workerStarter ;
@@ -29,7 +30,7 @@ class WorkerHealthServiceTest extends TestCase {
2930 public function setUp (): void {
3031 parent ::setUp ();
3132 $ this ->workerConfiguration = $ this ->createMock (WorkerConfiguration::class);
32- $ this ->workerCounter = $ this ->createMock (WorkerCounter ::class);
33+ $ this ->processManager = $ this ->createMock (ProcessManager ::class);
3334 $ this ->workerJobCounter = $ this ->createMock (WorkerJobCounter::class);
3435 $ this ->startThrottlePolicy = $ this ->createMock (StartThrottlePolicy::class);
3536 $ this ->workerStarter = $ this ->createMock (WorkerStarter::class);
@@ -39,7 +40,7 @@ public function setUp(): void {
3940 private function makeService (): WorkerHealthService {
4041 return new WorkerHealthService (
4142 $ this ->workerConfiguration ,
42- $ this ->workerCounter ,
43+ $ this ->processManager ,
4344 $ this ->workerJobCounter ,
4445 $ this ->startThrottlePolicy ,
4546 $ this ->workerStarter ,
@@ -56,22 +57,24 @@ public function testEnsureWorkerRunningReturnsFalseWhenAsyncDisabled(): void {
5657 $ this ->assertFalse ($ service ->ensureWorkerRunning ());
5758 }
5859
59- public function testEnsureWorkerRunningNoStartWhenNoPendingJobs (): void {
60+ #[DataProvider('providerNoStartScenarios ' )]
61+ public function testEnsureWorkerRunningDoesNotStartWorkers (int $ desired , int $ running , int $ pendingJobs ): void {
6062 $ this ->workerConfiguration ->expects ($ this ->once ())
6163 ->method ('isAsyncLocalEnabled ' )
6264 ->willReturn (true );
6365
6466 $ this ->workerConfiguration ->expects ($ this ->once ())
6567 ->method ('getDesiredWorkerCount ' )
66- ->willReturn (4 );
68+ ->willReturn ($ desired );
6769
68- $ this ->workerCounter ->expects ($ this ->once ())
70+ $ this ->processManager ->expects ($ this ->once ())
6971 ->method ('countRunning ' )
70- ->willReturn (0 );
72+ ->with ('worker ' )
73+ ->willReturn ($ running );
7174
7275 $ this ->workerJobCounter ->expects ($ this ->once ())
7376 ->method ('countPendingJobs ' )
74- ->willReturn (0 );
77+ ->willReturn ($ pendingJobs );
7578
7679 $ this ->startThrottlePolicy ->expects ($ this ->never ())
7780 ->method ('isThrottled ' );
@@ -83,31 +86,16 @@ public function testEnsureWorkerRunningNoStartWhenNoPendingJobs(): void {
8386 $ this ->assertTrue ($ service ->ensureWorkerRunning ());
8487 }
8588
86- public function testEnsureWorkerRunningNoStartWhenEnoughWorkers (): void {
87- $ this ->workerConfiguration ->expects ($ this ->once ())
88- ->method ('isAsyncLocalEnabled ' )
89- ->willReturn (true );
90-
91- $ this ->workerConfiguration ->expects ($ this ->once ())
92- ->method ('getDesiredWorkerCount ' )
93- ->willReturn (4 );
94-
95- $ this ->workerCounter ->expects ($ this ->once ())
96- ->method ('countRunning ' )
97- ->willReturn (4 );
98-
99- $ this ->workerJobCounter ->expects ($ this ->once ())
100- ->method ('countPendingJobs ' )
101- ->willReturn (10 );
102-
103- $ this ->startThrottlePolicy ->expects ($ this ->never ())
104- ->method ('isThrottled ' );
105-
106- $ service = $ this ->makeService ();
107- $ this ->assertTrue ($ service ->ensureWorkerRunning ());
89+ public static function providerNoStartScenarios (): array {
90+ return [
91+ 'no pending jobs ' => [4 , 0 , 0 ],
92+ 'enough workers already running ' => [4 , 4 , 10 ],
93+ 'running workers exceeds desired ' => [2 , 5 , 10 ],
94+ 'desired workers is zero ' => [0 , 0 , 10 ],
95+ ];
10896 }
10997
110- public function testEnsureWorkerRunningRespectThrottlePolicy (): void {
98+ public function testEnsureWorkerRunningRespectsThrottlePolicy (): void {
11199 $ this ->workerConfiguration ->expects ($ this ->once ())
112100 ->method ('isAsyncLocalEnabled ' )
113101 ->willReturn (true );
@@ -116,8 +104,9 @@ public function testEnsureWorkerRunningRespectThrottlePolicy(): void {
116104 ->method ('getDesiredWorkerCount ' )
117105 ->willReturn (4 );
118106
119- $ this ->workerCounter ->expects ($ this ->once ())
107+ $ this ->processManager ->expects ($ this ->once ())
120108 ->method ('countRunning ' )
109+ ->with ('worker ' )
121110 ->willReturn (0 );
122111
123112 $ this ->workerJobCounter ->expects ($ this ->once ())
@@ -138,22 +127,24 @@ public function testEnsureWorkerRunningRespectThrottlePolicy(): void {
138127 $ this ->assertTrue ($ service ->ensureWorkerRunning ());
139128 }
140129
141- public function testEnsureWorkerRunningStartsWhenNotThrottled (): void {
130+ #[DataProvider('providerStartWorkerScenarios ' )]
131+ public function testEnsureWorkerRunningStartsExpectedNumberOfWorkers (int $ desired , int $ running , int $ pendingJobs , int $ expectedStarts ): void {
142132 $ this ->workerConfiguration ->expects ($ this ->once ())
143133 ->method ('isAsyncLocalEnabled ' )
144134 ->willReturn (true );
145135
146136 $ this ->workerConfiguration ->expects ($ this ->once ())
147137 ->method ('getDesiredWorkerCount ' )
148- ->willReturn (4 );
138+ ->willReturn ($ desired );
149139
150- $ this ->workerCounter ->expects ($ this ->once ())
140+ $ this ->processManager ->expects ($ this ->once ())
151141 ->method ('countRunning ' )
152- ->willReturn (2 );
142+ ->with ('worker ' )
143+ ->willReturn ($ running );
153144
154145 $ this ->workerJobCounter ->expects ($ this ->once ())
155146 ->method ('countPendingJobs ' )
156- ->willReturn (10 );
147+ ->willReturn ($ pendingJobs );
157148
158149 $ this ->startThrottlePolicy ->expects ($ this ->once ())
159150 ->method ('isThrottled ' )
@@ -164,130 +155,52 @@ public function testEnsureWorkerRunningStartsWhenNotThrottled(): void {
164155
165156 $ this ->workerStarter ->expects ($ this ->once ())
166157 ->method ('startWorkers ' )
167- ->with (2 );
158+ ->with ($ expectedStarts );
168159
169160 $ service = $ this ->makeService ();
170161 $ this ->assertTrue ($ service ->ensureWorkerRunning ());
171162 }
172163
173- public function testEnsureWorkerRunningStartsOnlyNeededWorkersBasedOnPendingJobs (): void {
174- $ this ->workerConfiguration ->expects ($ this ->once ())
175- ->method ('isAsyncLocalEnabled ' )
176- ->willReturn (true );
177-
178- $ this ->workerConfiguration ->expects ($ this ->once ())
179- ->method ('getDesiredWorkerCount ' )
180- ->willReturn (10 );
181-
182- $ this ->workerCounter ->expects ($ this ->once ())
183- ->method ('countRunning ' )
184- ->willReturn (0 );
185-
186- $ this ->workerJobCounter ->expects ($ this ->once ())
187- ->method ('countPendingJobs ' )
188- ->willReturn (3 );
189-
190- $ this ->startThrottlePolicy ->expects ($ this ->once ())
191- ->method ('isThrottled ' )
192- ->willReturn (false );
193-
194- $ this ->startThrottlePolicy ->expects ($ this ->once ())
195- ->method ('recordAttempt ' );
196-
197- // Should only start 3 workers because there are only 3 pending jobs
198- $ this ->workerStarter ->expects ($ this ->once ())
199- ->method ('startWorkers ' )
200- ->with (3 );
201-
202- $ service = $ this ->makeService ();
203- $ this ->assertTrue ($ service ->ensureWorkerRunning ());
164+ public static function providerStartWorkerScenarios (): array {
165+ return [
166+ 'fills remaining desired capacity ' => [4 , 2 , 10 , 2 ],
167+ 'limited by pending jobs with no running ' => [10 , 0 , 3 , 3 ],
168+ 'limited by pending jobs with running workers ' => [10 , 3 , 5 , 5 ],
169+ 'pending jobs smaller than desired gap ' => [10 , 1 , 2 , 2 ],
170+ 'single pending job starts one worker ' => [4 , 0 , 1 , 1 ],
171+ ];
204172 }
205173
206- public function testEnsureWorkerRunningLimitsWorkersByPendingJobsAndRunningWorkers (): void {
174+ public function testEnsureWorkerRunningHandlesExceptions (): void {
207175 $ this ->workerConfiguration ->expects ($ this ->once ())
208176 ->method ('isAsyncLocalEnabled ' )
209- ->willReturn (true );
210-
211- $ this ->workerConfiguration ->expects ($ this ->once ())
212- ->method ('getDesiredWorkerCount ' )
213- ->willReturn (10 );
214-
215- $ this ->workerCounter ->expects ($ this ->once ())
216- ->method ('countRunning ' )
217- ->willReturn (3 );
218-
219- $ this ->workerJobCounter ->expects ($ this ->once ())
220- ->method ('countPendingJobs ' )
221- ->willReturn (5 );
222-
223- $ this ->startThrottlePolicy ->expects ($ this ->once ())
224- ->method ('isThrottled ' )
225- ->willReturn (false );
226-
227- $ this ->startThrottlePolicy ->expects ($ this ->once ())
228- ->method ('recordAttempt ' );
177+ ->will ($ this ->throwException (new \RuntimeException ('Config error ' )));
229178
230- // Pending jobs = 5, Running = 3, Desired = 10
231- // Should start min(5, 10-3) = min(5, 7) = 5 workers
232- $ this ->workerStarter ->expects ($ this ->once ())
233- ->method ('startWorkers ' )
234- ->with (5 );
179+ $ this ->logger ->expects ($ this ->once ())
180+ ->method ('error ' )
181+ ->with ($ this ->stringContains ('Failed to ensure worker is running ' ));
235182
236183 $ service = $ this ->makeService ();
237- $ this ->assertTrue ($ service ->ensureWorkerRunning ());
184+ $ this ->assertFalse ($ service ->ensureWorkerRunning ());
238185 }
239186
240- public function testEnsureWorkerRunningStartsFewerWorkersWhenLessPendingJobsThanGap (): void {
187+ public function testEnsureWorkerRunningReturnsFalseWhenWorkerStarterFails (): void {
241188 $ this ->workerConfiguration ->expects ($ this ->once ())
242189 ->method ('isAsyncLocalEnabled ' )
243190 ->willReturn (true );
244191
245192 $ this ->workerConfiguration ->expects ($ this ->once ())
246193 ->method ('getDesiredWorkerCount ' )
247- ->willReturn (10 );
248-
249- $ this ->workerCounter ->expects ($ this ->once ())
250- ->method ('countRunning ' )
251- ->willReturn (1 );
252-
253- $ this ->workerJobCounter ->expects ($ this ->once ())
254- ->method ('countPendingJobs ' )
255194 ->willReturn (2 );
256195
257- $ this ->startThrottlePolicy ->expects ($ this ->once ())
258- ->method ('isThrottled ' )
259- ->willReturn (false );
260-
261- $ this ->startThrottlePolicy ->expects ($ this ->once ())
262- ->method ('recordAttempt ' );
263-
264- // Pending jobs = 2, Running = 1, Desired = 10
265- // Gap would be 9, but only 2 jobs pending
266- // Should start min(2, 9) = 2 workers
267- $ this ->workerStarter ->expects ($ this ->once ())
268- ->method ('startWorkers ' )
269- ->with (2 );
270-
271- $ service = $ this ->makeService ();
272- $ this ->assertTrue ($ service ->ensureWorkerRunning ());
273- }
274-
275- public function testEnsureWorkerRunningStartsOneWorkerWhenOnlyOneJobPending (): void {
276- $ this ->workerConfiguration ->expects ($ this ->once ())
277- ->method ('isAsyncLocalEnabled ' )
278- ->willReturn (true );
279-
280- $ this ->workerConfiguration ->expects ($ this ->once ())
281- ->method ('getDesiredWorkerCount ' )
282- ->willReturn (4 );
283-
284- $ this ->workerCounter ->expects ($ this ->once ())
196+ $ this ->processManager ->expects ($ this ->once ())
285197 ->method ('countRunning ' )
198+ ->with ('worker ' )
286199 ->willReturn (0 );
287200
288201 $ this ->workerJobCounter ->expects ($ this ->once ())
289202 ->method ('countPendingJobs ' )
290- ->willReturn (1 );
203+ ->willReturn (2 );
291204
292205 $ this ->startThrottlePolicy ->expects ($ this ->once ())
293206 ->method ('isThrottled ' )
@@ -296,25 +209,28 @@ public function testEnsureWorkerRunningStartsOneWorkerWhenOnlyOneJobPending(): v
296209 $ this ->startThrottlePolicy ->expects ($ this ->once ())
297210 ->method ('recordAttempt ' );
298211
299- // Should only start 1 worker for 1 job
300212 $ this ->workerStarter ->expects ($ this ->once ())
301213 ->method ('startWorkers ' )
302- ->with (1 );
214+ ->with (2 )
215+ ->will ($ this ->throwException (new \RuntimeException ('process launch failed ' )));
216+
217+ $ this ->logger ->expects ($ this ->once ())
218+ ->method ('error ' )
219+ ->with (
220+ $ this ->stringContains ('Failed to ensure worker is running ' ),
221+ $ this ->arrayHasKey ('exception ' )
222+ );
303223
304224 $ service = $ this ->makeService ();
305- $ this ->assertTrue ($ service ->ensureWorkerRunning ());
225+ $ this ->assertFalse ($ service ->ensureWorkerRunning ());
306226 }
307227
308- public function testEnsureWorkerRunningHandlesExceptions (): void {
228+ public function testIsAsyncLocalEnabledDelegatesToConfiguration (): void {
309229 $ this ->workerConfiguration ->expects ($ this ->once ())
310230 ->method ('isAsyncLocalEnabled ' )
311- ->will ($ this ->throwException (new \RuntimeException ('Config error ' )));
312-
313- $ this ->logger ->expects ($ this ->once ())
314- ->method ('error ' )
315- ->with ($ this ->stringContains ('Failed to ensure worker is running ' ));
231+ ->willReturn (true );
316232
317233 $ service = $ this ->makeService ();
318- $ this ->assertFalse ($ service ->ensureWorkerRunning ());
234+ $ this ->assertTrue ($ service ->isAsyncLocalEnabled ());
319235 }
320236}
0 commit comments