Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add configuration maxScoreThreshold #306

Merged
merged 1 commit into from
Feb 15, 2021
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
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ filesToShow: 10
# Default: 0.1
minScoreToShow: 0

# The command returns an 1 exit code if the highest score is greater than the threshold.
# Disabled if null.
# Default: null
maxScoreThreshold: 0.9

# The number of parallel jobs to use when processing files.
# Default: 10
parallelJobs: 10
Expand Down Expand Up @@ -126,12 +131,12 @@ hooks:
# The version control system used for your project.
# Accepted values: fossil, git, mercurial, subversion, none
# Default: git
vcs: git
vcs: git

# The path of the cache file. It doesn't need to exist before running churn.
# Disabled if null.
# Default: null
cachePath: .churn.cache
cachePath: .churn.cache
```

If a `churn.yml` file is omitted or an individual setting is omitted the default values above will be used.
Expand Down
49 changes: 49 additions & 0 deletions src/Command/Helper/MaxScoreChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Churn\Command\Helper;

use Churn\Result\ResultAccumulator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

/**
* @internal
*/
class MaxScoreChecker
{

/**
* @var float|null
*/
private $maxScoreThreshold;

/**
* @param float|null $maxScoreThreshold The max score threshold.
*/
public function __construct(?float $maxScoreThreshold)
{
$this->maxScoreThreshold = $maxScoreThreshold;
}

/**
* @param InputInterface $input Input.
* @param OutputInterface $output Output.
* @param ResultAccumulator $report The report containing the scores.
*/
public function isOverThreshold(InputInterface $input, OutputInterface $output, ResultAccumulator $report): bool
{
$maxScore = $report->getMaxScore();

if (null === $this->maxScoreThreshold || null === $maxScore || $maxScore <= $this->maxScoreThreshold) {
return false;
}

if ('text' === $input->getOption('format') || !empty($input->getOption('output'))) {
$output->writeln('<error>Max score is over the threshold</>');
}

return true;
}
}
25 changes: 3 additions & 22 deletions src/Command/RunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Churn\Command;

use Churn\Command\Helper\MaxScoreChecker;
use Churn\Command\Helper\ProgressBarSubscriber;
use Churn\Configuration\Config;
use Churn\Configuration\Loader;
Expand Down Expand Up @@ -109,15 +110,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int
}
$config = $this->getConfiguration($input);
$broker = new Broker();
$this->attachHooks($config, $broker);
(new HookLoader($config->getDirPath()))->attachHooks($config->getHooks(), $broker);
$this->printLogo($input, $output);
if (true === $input->getOption('progress')) {
$broker->subscribe(new ProgressBarSubscriber($output));
}
$report = $this->analyze($input, $config, $broker);
$this->writeResult($input, $output, $report);

return 0;
return (int) (new MaxScoreChecker($config->getMaxScoreThreshold()))->isOverThreshold($input, $output, $report);
}

/**
Expand Down Expand Up @@ -147,26 +148,6 @@ private function getConfiguration(InputInterface $input): Config
return $config;
}

/**
* @param Config $config The configuration object.
* @param Broker $broker The event broker.
* @throws InvalidArgumentException If a hook is invalid.
*/
private function attachHooks(Config $config, Broker $broker): void
{
if ([] === $config->getHooks()) {
return;
}

$loader = new HookLoader($config->getDirPath());

foreach ($config->getHooks() as $hook) {
if (!$loader->attach($hook, $broker)) {
throw new InvalidArgumentException('Invalid hook: ' . $hook);
}
}
}

/**
* Run the actual analysis.
*
Expand Down
13 changes: 13 additions & 0 deletions src/Configuration/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class Config
private const DIRECTORIES_TO_SCAN = [];
private const FILES_TO_SHOW = 10;
private const MINIMUM_SCORE_TO_SHOW = 0.1;
private const MAXIMUM_SCORE_THRESHOLD = null;
private const AMOUNT_OF_PARALLEL_JOBS = 10;
private const SHOW_COMMITS_SINCE = '10 years ago';
private const FILES_TO_IGNORE = [];
Expand Down Expand Up @@ -112,6 +113,18 @@ public function getMinScoreToShow(): ?float
return self::MINIMUM_SCORE_TO_SHOW;
}

/**
* Get the maximum score threshold.
*/
public function getMaxScoreThreshold(): ?float
{
if (\array_key_exists('maxScoreThreshold', $this->configuration)) {
return $this->configuration['maxScoreThreshold'];
}

return self::MAXIMUM_SCORE_THRESHOLD;
}

/**
* Get the number of parallel jobs to use to process the files.
*/
Expand Down
15 changes: 14 additions & 1 deletion src/Configuration/Validator.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public function validateConfigurationValues(array $configuration): void
$this->validateDirectoriesToScan($configuration);
$this->validateFilesToShow($configuration);
$this->validateMinScoreToShow($configuration);
$this->validateMaxScoreThreshold($configuration);
$this->validateParallelJobs($configuration);
$this->validateCommitsSince($configuration);
$this->validateFilesToIgnore($configuration);
Expand Down Expand Up @@ -59,13 +60,25 @@ private function validateFilesToShow(array $configuration): void
*/
private function validateMinScoreToShow(array $configuration): void
{
if (!\array_key_exists('minScoreToShow', $configuration) || null === $configuration['minScoreToShow']) {
if (!isset($configuration['minScoreToShow'])) {
return;
}

Assert::numeric($configuration['minScoreToShow'], 'Minimum score to show should be a number');
}

/**
* @param array<mixed> $configuration The array containing the configuration values.
*/
private function validateMaxScoreThreshold(array $configuration): void
{
if (!isset($configuration['maxScoreThreshold'])) {
return;
}

Assert::numeric($configuration['maxScoreThreshold'], 'Maximum score threshold should be a number');
}

/**
* @param array<mixed> $configuration The array containing the configuration values.
*/
Expand Down
19 changes: 19 additions & 0 deletions src/Event/HookLoader.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Churn\Event\Subscriber\AfterFileAnalysisHookDecorator;
use Churn\Event\Subscriber\BeforeAnalysisHookDecorator;
use Churn\File\FileHelper;
use InvalidArgumentException;

/**
* @internal
Expand Down Expand Up @@ -41,6 +42,24 @@ public function __construct(string $basePath)
$this->basePath = $basePath;
}

/**
* @param array<string> $hooks The list of hooks to attach.
* @param Broker $broker The event broker.
* @throws InvalidArgumentException If a hook is invalid.
*/
public function attachHooks(array $hooks, Broker $broker): void
{
if ([] === $hooks) {
return;
}

foreach ($hooks as $hook) {
if (!$this->attach($hook, $broker)) {
throw new InvalidArgumentException('Invalid hook: ' . $hook);
}
}
}

/**
* @param string $hookPath The class name or the file path of the hook.
* @param Broker $broker The event broker.
Expand Down
13 changes: 13 additions & 0 deletions tests/Integration/Command/RunCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,17 @@ public function it_can_suppress_normal_output(): void
$this->assertEquals(0, $exitCode);
$this->assertEquals('Churn: DONE', $display);
}

/** @test */
public function it_can_return_one_as_exit_code(): void
{
$exitCode = $this->commandTester->execute([
'paths' => [__FILE__],
'-c' => __DIR__ . '/config/test-threshold.yml',
]);
$display = $this->commandTester->getDisplay();

$this->assertEquals(1, $exitCode);
$this->assertStringContainsString('Max score is over the threshold', $display);
}
}
1 change: 1 addition & 0 deletions tests/Integration/Command/config/test-threshold.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
maxScoreThreshold: 0.9
55 changes: 55 additions & 0 deletions tests/Unit/Command/Helper/MaxScoreCheckerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php

declare(strict_types=1);

namespace Churn\Tests\Unit\Command\Helper;

use Churn\Command\Helper\MaxScoreChecker;
use Churn\Result\ResultAccumulator;
use Churn\Tests\BaseTestCase;
use Mockery as m;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class MaxScoreCheckerTest extends BaseTestCase
{

/**
* @test
* @dataProvider provide_arguments
*/
public function it_can_check_the_max_score(
bool $expectedResult,
?float $threshold,
?float $maxScore,
string $format = 'text',
?string $output = null
): void {
$input = m::mock(InputInterface::class);
$input->shouldReceive('getOption')->with('format')->andReturn($format);
$input->shouldReceive('getOption')->with('output')->andReturn($output);

$output = m::mock(OutputInterface::class);
$output->shouldReceive('writeln');

$report = m::mock(ResultAccumulator::class);
$report->shouldReceive('getMaxScore')->andReturn($maxScore);

$checker = new MaxScoreChecker($threshold);

$this->assertEquals(
$expectedResult,
$checker->isOverThreshold($input, $output, $report)
);
}

public function provide_arguments(): iterable
{
yield [false, null, null];
yield [false, 1, null];
yield [false, null, 1];
yield [false, 1, 0.1];
yield [true, 0.1, 1];
yield [true, 0.1, 1, 'text', '/tmp'];
}
}
4 changes: 4 additions & 0 deletions tests/Unit/Configuration/ConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public function it_can_return_its_default_values_when_instantiated_without_any_p
$this->assertSame([], $config->getDirectoriesToScan());
$this->assertSame(10, $config->getFilesToShow());
$this->assertSame(0.1, $config->getMinScoreToShow());
$this->assertSame(null, $config->getMaxScoreThreshold());
$this->assertSame(10, $config->getParallelJobs());
$this->assertSame('10 years ago', $config->getCommitsSince());
$this->assertSame([], $config->getFilesToIgnore());
Expand All @@ -67,6 +68,7 @@ public function it_can_return_its_values_when_instantiated_parameters()
$filesToShow = 13;
$directoriesToScan = ['src', 'tests'];
$minScoreToShow = 5;
$maxScoreThreshold = 9.5;
$parallelJobs = 7;
$commitsSince = '4 years ago';
$filesToIgnore = ['foo.php', 'bar.php', 'baz.php'];
Expand All @@ -79,6 +81,7 @@ public function it_can_return_its_values_when_instantiated_parameters()
'directoriesToScan' => $directoriesToScan,
'filesToShow' => $filesToShow,
'minScoreToShow' => $minScoreToShow,
'maxScoreThreshold' => $maxScoreThreshold,
'parallelJobs' => $parallelJobs,
'commitsSince' => $commitsSince,
'filesToIgnore' => $filesToIgnore,
Expand All @@ -91,6 +94,7 @@ public function it_can_return_its_values_when_instantiated_parameters()
$this->assertSame($directoriesToScan, $config->getDirectoriesToScan());
$this->assertSame($filesToShow, $config->getFilesToShow());
$this->assertEquals($minScoreToShow, $config->getMinScoreToShow());
$this->assertSame($maxScoreThreshold, $config->getMaxScoreThreshold());
$this->assertSame($parallelJobs, $config->getParallelJobs());
$this->assertSame($commitsSince, $config->getCommitsSince());
$this->assertSame($filesToIgnore, $config->getFilesToIgnore());
Expand Down