Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Latest
## Changes
* [#190](https://github.com/cleverage/process-bundle/issues/190) Update quality stack: use Rector `withComposerBased()` sets (removed `SYMFONY_64` / `PHPUNIT_100` sets), declare used Symfony packages and PHPUnit range in composer.json, apply quality tools fixes
* [#145](https://github.com/cleverage/process-bundle/issues/145) Add missing documentations: reference pages for every Task & Transformer, ConditionTrait & GenericTransformer, complete guides and cookbooks. Harmonize and fix existing documentation.
* [#229](https://github.com/cleverage/process-bundle/issues/229) StatCounterTask and AdvancedStatCounterTask pass their input to their output on every execution (they used to output `null`, and AdvancedStatCounterTask was skipped when not logging), so they can be placed anywhere in a branch. Update documentation, add tests.

## Fixes
* [#192](https://github.com/cleverage/process-bundle/issues/192) Fix CommandRunnerTask: only pass the `options` option to `Process::setOptions()`, support string `commandline` through `Process::fromShellCommandline()`, validate option types. Update documentation, add tests.
Expand Down
8 changes: 4 additions & 4 deletions docs/reference/tasks/advanced_stat_counter_task.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ AdvancedStatCounterTask
=======================

Logs performance statistics (`info` level) every N executions: time since the last log, processing rate, number of
processed items and total elapsed time.
processed items and total elapsed time. The input is passed to the output, so the task can be placed anywhere in a
branch.

Task reference
--------------
Expand All @@ -12,13 +13,12 @@ Task reference
Accepted inputs
---------------

Input is ignored, only the number of executions matters.
`any`: only the number of executions matters.

Possible outputs
----------------

`null` when statistics are logged (with `show_every: 1`, on every counted execution), otherwise the task is skipped (nothing is sent to the outputs). It is meant to be
used as the last task of a branch.
`any`: the input, unchanged, on every execution (whether statistics are logged or not).

The logged message has the following format:

Expand Down
7 changes: 4 additions & 3 deletions docs/reference/tasks/stat_counter_task.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
StatCounterTask
===============

Counts the number of times the task is executed and logs the total (`info` level) when the process ends.
Counts the number of times the task is executed and logs the total (`info` level) when the process ends. The input is
passed to the output, so the task can be placed anywhere in a branch.

Task reference
--------------
Expand All @@ -11,12 +12,12 @@ Task reference
Accepted inputs
---------------

Input is ignored.
`any`: only the number of executions matters.

Possible outputs
----------------

`null`: the task does not set any output. It is meant to be used as the last task of a branch.
`any`: the input, unchanged.

At finalization, the message `Processed item count: <count>` is logged.

Expand Down
7 changes: 3 additions & 4 deletions src/Task/Reporting/AdvancedStatCounterTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
use Symfony\Component\OptionsResolver\OptionsResolver;

/**
* Count the time between 2 iterations.
* Count the time between 2 iterations, and log statistics every N executions. The input is passed to the output.
*/
class AdvancedStatCounterTask extends AbstractConfigurableTask
{
Expand All @@ -43,9 +43,10 @@ public function execute(ProcessState $state): void
$this->startedAt = $now;
$this->lastUpdate = $now;
}
// The input is always passed to the output, the task only logs statistics
$state->setOutput($state->getInput());
if ($this->preInitCounter < $this->getOption($state, 'skip_first')) {
++$this->preInitCounter;
$state->setSkipped(true);

return;
}
Expand All @@ -65,8 +66,6 @@ public function execute(ProcessState $state): void

$this->lastUpdate = $now;
$this->logger->info($fullText);
} else {
$state->setSkipped(true);
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/Task/Reporting/StatCounterTask.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
use Psr\Log\LoggerInterface;

/**
* Count the number of times the task was executed.
* Count the number of times the task was executed, and log it when the process ends. The input is passed to the output.
*/
class StatCounterTask implements FinalizableTaskInterface
{
Expand All @@ -37,5 +37,6 @@ public function finalize(ProcessState $state): void
public function execute(ProcessState $state): void
{
++$this->counter;
$state->setOutput($state->getInput());
}
}
23 changes: 13 additions & 10 deletions tests/Task/Reporting/AdvancedStatCounterTaskTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ class AdvancedStatCounterTaskTest extends TestCase
{
public function testEveryExecutionIsLoggedWithShowEveryOne(): void
{
[$messages, $skipped] = $this->runTask(3, ['show_every' => 1]);
[$messages, $outputs] = $this->runTask(3, ['show_every' => 1]);

self::assertSame([false, false, false], $skipped);
self::assertSame([0, 1, 2], $outputs);
self::assertCount(3, $messages);
self::assertStringContainsString(' 1 items processed', $messages[0]);
self::assertStringContainsString(' 2 items processed', $messages[1]);
Expand All @@ -45,27 +45,29 @@ public function testEveryExecutionIsLoggedWithShowEveryOne(): void

public function testEveryNthExecutionIsLogged(): void
{
[$messages, $skipped] = $this->runTask(7, ['show_every' => 3, 'num_items' => 10]);
[$messages, $outputs] = $this->runTask(7, ['show_every' => 3, 'num_items' => 10]);

self::assertSame([true, true, false, true, true, false, true], $skipped);
self::assertSame([0, 1, 2, 3, 4, 5, 6], $outputs);
self::assertCount(2, $messages);
self::assertStringContainsString(' 30 items processed', $messages[0]);
self::assertStringContainsString(' 60 items processed', $messages[1]);
}

public function testSkipFirstExecutionsAreNotCounted(): void
{
[$messages, $skipped] = $this->runTask(4, ['show_every' => 2, 'skip_first' => 1]);
[$messages, $outputs] = $this->runTask(4, ['show_every' => 2, 'skip_first' => 1]);

self::assertSame([true, true, false, true], $skipped);
self::assertSame([0, 1, 2, 3], $outputs);
self::assertCount(1, $messages);
self::assertStringContainsString(' 2 items processed', $messages[0]);
}

/**
* @param array<string, int> $options
*
* @return array{list<string>, list<bool>}
* Execute the task $executions times with the inputs 0, 1, 2...
*
* @return array{list<string>, list<mixed>} logged messages, and outputs (null when skipped)
*/
private function runTask(int $executions, array $options): array
{
Expand All @@ -83,14 +85,15 @@ public function log($level, string|\Stringable $message, array $context = []): v
$task = new AdvancedStatCounterTask($logger);
$task->initialize($state);

$skipped = [];
$outputs = [];
for ($i = 0; $i < $executions; ++$i) {
$state->reset(false);
$state->setInput($i);
$task->execute($state);
$skipped[] = $state->isSkipped();
$outputs[] = $state->isSkipped() ? null : $state->getOutput();
}

return [$logger->messages, $skipped];
return [$logger->messages, $outputs];
}

private function createState(string $class, array $options): ProcessState
Expand Down
56 changes: 56 additions & 0 deletions tests/Task/Reporting/StatCounterTaskTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?php

declare(strict_types=1);

/*
* This file is part of the CleverAge/ProcessBundle package.
*
* Copyright (c) Clever-Age
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace CleverAge\ProcessBundle\Tests\Task\Reporting;

use CleverAge\ProcessBundle\Configuration\ProcessConfiguration;
use CleverAge\ProcessBundle\Model\ProcessHistory;
use CleverAge\ProcessBundle\Model\ProcessState;
use CleverAge\ProcessBundle\Task\Reporting\StatCounterTask;
use PHPUnit\Framework\TestCase;
use Psr\Log\AbstractLogger;

#[\PHPUnit\Framework\Attributes\CoversClass(StatCounterTask::class)]
#[\PHPUnit\Framework\Attributes\UsesClass(ProcessConfiguration::class)]
#[\PHPUnit\Framework\Attributes\UsesClass(ProcessHistory::class)]
#[\PHPUnit\Framework\Attributes\UsesClass(ProcessState::class)]
class StatCounterTaskTest extends TestCase
{
public function testInputIsPassedToTheOutputAndCountIsLoggedOnFinalize(): void
{
$logger = new class extends AbstractLogger {
/** @var list<string> */
public array $messages = [];

public function log($level, string|\Stringable $message, array $context = []): void
{
$this->messages[] = (string) $message;
}
};
$processConfiguration = new ProcessConfiguration('test', []);
$state = new ProcessState($processConfiguration, new ProcessHistory($processConfiguration));
$task = new StatCounterTask($logger);

$outputs = [];
foreach (['a', ['b'], null] as $input) {
$state->reset(false);
$state->setInput($input);
$task->execute($state);
$outputs[] = $state->isSkipped() ? 'skipped' : $state->getOutput();
}
$task->finalize($state);

self::assertSame(['a', ['b'], null], $outputs);
self::assertSame(['Processed item count: 3'], $logger->messages);
}
}
Loading