Skip to content

Commit 8b5f244

Browse files
committed
test(worker): improve worker health scenarios
Signed-off-by: Vitor Mattos <1079143+vitormattos@users.noreply.github.com>
1 parent 693434f commit 8b5f244

1 file changed

Lines changed: 61 additions & 145 deletions

File tree

tests/php/Unit/Service/Worker/WorkerHealthServiceTest.php

Lines changed: 61 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,20 @@
88

99
namespace OCA\Libresign\Tests\Unit\Service\Worker;
1010

11+
use OCA\Libresign\Service\Process\ProcessManager;
1112
use OCA\Libresign\Service\Worker\StartThrottlePolicy;
1213
use OCA\Libresign\Service\Worker\WorkerConfiguration;
13-
use OCA\Libresign\Service\Worker\WorkerCounter;
1414
use OCA\Libresign\Service\Worker\WorkerHealthService;
1515
use OCA\Libresign\Service\Worker\WorkerJobCounter;
1616
use OCA\Libresign\Service\Worker\WorkerStarter;
1717
use OCA\Libresign\Tests\Unit\TestCase;
18+
use PHPUnit\Framework\Attributes\DataProvider;
1819
use PHPUnit\Framework\MockObject\MockObject;
1920
use Psr\Log\LoggerInterface;
2021

2122
class 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

Comments
 (0)