Skip to content

Commit ba7a752

Browse files
committed
#119: Log per-module progress in Drupal phpunit task
- Print a progress line after each affected Drupal module’s phpunit run, including index, total, module path, and OK/FAILED status - Keep per-module execution behavior while maintaining early exit on the first failing module Refs: src/Task/PhpUnitDrupalModules/PhpUnitDrupalModulesTask.php
1 parent 478d369 commit ba7a752

2 files changed

Lines changed: 130 additions & 1 deletion

File tree

src/Task/PhpUnitDrupalModules/PhpUnitDrupalModulesTask.php

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,25 @@ public function run(ContextInterface $context): TaskResultInterface {
4343
// is honoured even when a testsuite is used in the configuration file.
4444
$this->printModulesWithTests($modulesWithTests);
4545

46-
foreach ($modulesWithTests as $modulePath) {
46+
$moduleCount = count($modulesWithTests);
47+
48+
foreach ($modulesWithTests as $index => $modulePath) {
4749
$process = $this->processBuilder->buildProcess($this->buildArguments([$modulePath]));
4850
$process->run();
4951

5052
$result = $this->getTaskResult($process, $context);
53+
54+
fwrite(
55+
STDOUT,
56+
sprintf(
57+
"phpunit_drupal_modules: finished module %d/%d: %s [%s]\n\n",
58+
$index + 1,
59+
$moduleCount,
60+
$modulePath,
61+
$result->isPassed() ? 'OK' : 'FAILED'
62+
)
63+
);
64+
5165
if (!$result->isPassed()) {
5266
// Stop on first failure/error to keep feedback fast and clear.
5367
return $result;

tests/PhpUnitDrupalModules/PhpUnitDrupalModulesTaskTest.php

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
use GrumPHP\Collection\ProcessArgumentsCollection;
1111
use GrumPHP\Formatter\ProcessFormatterInterface;
1212
use GrumPHP\Process\ProcessBuilder;
13+
use GrumPHP\Runner\TaskResultInterface;
1314
use GrumPHP\Task\Config\TaskConfigInterface;
15+
use GrumPHP\Task\Context\ContextInterface;
16+
use Symfony\Component\Process\Process;
1417
use PHPUnit\Framework\TestCase;
1518
use Symfony\Component\Yaml\Yaml;
1619
use Wunderio\GrumPHP\Task\PhpUnitDrupalModules\PhpUnitDrupalModulesTask;
@@ -130,6 +133,118 @@ public function testBuildsProcessArgumentsWithOnlyModules(): void {
130133
$this->assertInstanceOf(ProcessArgumentsCollection::class, $actual);
131134
}
132135

136+
/**
137+
* Ensure run() skips when there are no modules with tests.
138+
*
139+
* @covers \Wunderio\GrumPHP\Task\PhpUnitDrupalModules\PhpUnitDrupalModulesTask::run
140+
*/
141+
public function testRunSkipsWhenNoModulesWithTests(): void {
142+
$processBuilder = $this->createMock(ProcessBuilder::class);
143+
$formatter = $this->createMock(ProcessFormatterInterface::class);
144+
145+
/** @var \PHPUnit\Framework\MockObject\MockObject|PhpUnitDrupalModulesTask $task */
146+
$task = $this->getMockBuilder(PhpUnitDrupalModulesTask::class)
147+
->setConstructorArgs([$processBuilder, $formatter])
148+
->onlyMethods(['getConfig', 'getPathsOrResult', 'collectModulesFromPaths', 'splitModulesByTests'])
149+
->getMock();
150+
151+
$config = [];
152+
foreach ($this->getConfigurations() as $name => $option) {
153+
$config[$name] = $option['defaults'];
154+
}
155+
156+
$taskConfig = $this->createMock(TaskConfigInterface::class);
157+
$task->method('getConfig')->willReturn($taskConfig);
158+
$taskConfig->method('getOptions')->willReturn($config);
159+
160+
$context = $this->createMock(ContextInterface::class);
161+
162+
$paths = new \ArrayObject(['web/modules/custom/foo/src/Foo.php']);
163+
$task->method('getPathsOrResult')->willReturn($paths);
164+
$task->method('collectModulesFromPaths')->willReturn([
165+
'web/modules/custom/foo' => 'web/modules/custom/foo',
166+
]);
167+
168+
// No modules with tests, one without.
169+
$task->method('splitModulesByTests')->willReturn([[], ['web/modules/custom/foo']]);
170+
171+
$result = $task->run($context);
172+
$this->assertInstanceOf(TaskResultInterface::class, $result);
173+
$this->assertFalse($result->isPassed());
174+
}
175+
176+
/**
177+
* Ensure run() executes phpunit once per module and stops on first failure.
178+
*
179+
* @covers \Wunderio\GrumPHP\Task\PhpUnitDrupalModules\PhpUnitDrupalModulesTask::run
180+
*/
181+
public function testRunExecutesPhpunitPerModuleAndStopsOnFailure(): void {
182+
$processBuilder = $this->createMock(ProcessBuilder::class);
183+
$formatter = $this->createMock(ProcessFormatterInterface::class);
184+
185+
/** @var \PHPUnit\Framework\MockObject\MockObject|PhpUnitDrupalModulesTask $task */
186+
$task = $this->getMockBuilder(PhpUnitDrupalModulesTask::class)
187+
->setConstructorArgs([$processBuilder, $formatter])
188+
->onlyMethods([
189+
'getConfig',
190+
'getPathsOrResult',
191+
'collectModulesFromPaths',
192+
'splitModulesByTests',
193+
'buildArguments',
194+
'getTaskResult',
195+
])
196+
->getMock();
197+
198+
$config = [];
199+
foreach ($this->getConfigurations() as $name => $option) {
200+
$config[$name] = $option['defaults'];
201+
}
202+
203+
$taskConfig = $this->createMock(TaskConfigInterface::class);
204+
$task->method('getConfig')->willReturn($taskConfig);
205+
$taskConfig->method('getOptions')->willReturn($config);
206+
207+
$context = $this->createMock(ContextInterface::class);
208+
209+
$paths = new \ArrayObject([
210+
'web/modules/custom/foo/src/Foo.php',
211+
'web/modules/custom/bar/src/Bar.php',
212+
]);
213+
$task->method('getPathsOrResult')->willReturn($paths);
214+
$task->method('collectModulesFromPaths')->willReturn([
215+
'web/modules/custom/foo' => 'web/modules/custom/foo',
216+
'web/modules/custom/bar' => 'web/modules/custom/bar',
217+
]);
218+
219+
$modulesWithTests = ['web/modules/custom/foo', 'web/modules/custom/bar'];
220+
$modulesWithoutTests = [];
221+
$task->method('splitModulesByTests')->willReturn([$modulesWithTests, $modulesWithoutTests]);
222+
223+
// Expect buildArguments to be called once per module with a single-element
224+
// array.
225+
$task->expects($this->exactly(2))
226+
->method('buildArguments')
227+
->withConsecutive(
228+
[['web/modules/custom/foo']],
229+
[['web/modules/custom/bar']]
230+
)
231+
->willReturn($this->createMock(ProcessArgumentsCollection::class));
232+
233+
$processBuilder->method('buildProcess')
234+
->willReturn($this->createMock(Process::class));
235+
236+
// Simulate first module passing, second failing.
237+
$passingResult = $this->createConfiguredMock(TaskResultInterface::class, ['isPassed' => TRUE]);
238+
$failingResult = $this->createConfiguredMock(TaskResultInterface::class, ['isPassed' => FALSE]);
239+
240+
$task->expects($this->exactly(2))
241+
->method('getTaskResult')
242+
->willReturnOnConsecutiveCalls($passingResult, $failingResult);
243+
244+
$result = $task->run($context);
245+
$this->assertSame($failingResult, $result);
246+
}
247+
133248
/**
134249
* Gets task configurations.
135250
*

0 commit comments

Comments
 (0)