diff --git a/.gitignore b/.gitignore index 3534a65..e232598 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /build/ +/cache/ /composer.lock +/docs/api/ /.env /.phpcheck/ /.php_cs.cache diff --git a/Makefile b/Makefile index 7401ef4..e031422 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,12 @@ watch: watch-playground: while inotifywait -e close_write -r composer.* ./src ./checks ./functions ./playground ./tests; do php playground/test.php; done +docs-build: + php sami.phar update sami.config.php + +docs-clean: + rm -rf build/ cache/ + phpcheck: @phpcheck diff --git a/checks/GeneratorCheck.php b/checks/GeneratorCheck.php index c2980a2..447c029 100644 --- a/checks/GeneratorCheck.php +++ b/checks/GeneratorCheck.php @@ -201,7 +201,7 @@ public function checkFakerWithArgs(int $int): bool } /** - * @param int $int {@gen variant(123, choose(0, 1000000))} + * @param int $int {@gen variant("123", choose(0, 1000000))} */ public function checkVariant(int $int): bool { diff --git a/composer.json b/composer.json index 0244277..bff02ae 100644 --- a/composer.json +++ b/composer.json @@ -39,6 +39,7 @@ "require-dev": { "friendsofphp/php-cs-fixer": "^2.14", "phpmd/phpmd": "^2.6", + "phpstan/phpstan": "^0.11.5", "phpunit/phpunit": "^8.1" }, "config": { diff --git a/sami.config.php b/sami.config.php new file mode 100644 index 0000000..ce988df --- /dev/null +++ b/sami.config.php @@ -0,0 +1,24 @@ +files() + ->name('*.php') + ->in($dir = __DIR__.'/src'); + +return new Sami( + $iterator, + [ + 'default_opened_level' => 2, + 'remote_repository' => new GitHubRemoteRepository('datashaman/phpcheck', dirname($dir)), + 'sort_class_constants' => true, + 'sort_class_interfaces' => true, + 'sort_class_methods' => true, + 'sort_class_properties' => true, + 'sort_class_traits' => true, + 'title' => 'PHPCheck API', + ] +); diff --git a/sami.phar b/sami.phar new file mode 100644 index 0000000..6a0a951 Binary files /dev/null and b/sami.phar differ diff --git a/src/Coverage/Coverage.php b/src/Coverage/Coverage.php index 4f6c4f3..e051034 100644 --- a/src/Coverage/Coverage.php +++ b/src/Coverage/Coverage.php @@ -9,6 +9,9 @@ */ namespace Datashaman\PHPCheck\Coverage; +/** + * Abstract base class for coverage classes. + */ abstract class Coverage { public function __destruct() diff --git a/src/Coverage/HtmlCoverage.php b/src/Coverage/HtmlCoverage.php index 370e03a..367579c 100644 --- a/src/Coverage/HtmlCoverage.php +++ b/src/Coverage/HtmlCoverage.php @@ -11,8 +11,22 @@ use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlFacade; +/** + * This class produces an HTML coverage report to a specified folder. + */ class HtmlCoverage extends Coverage { + private $folder; + + public function __construct(string $folder) + { + $this->folder = $folder; + } + + /** + * Processing is done in the __destruct method to ensure maximum coverage + * results. + */ public function __destruct() { global $coverage; @@ -20,6 +34,6 @@ public function __destruct() parent::__destruct(); $writer = new HtmlFacade(); - $writer->process($coverage, $this->input->getOption('coverage-html')); + $writer->process($coverage, $this->folder); } } diff --git a/src/Coverage/TextCoverage.php b/src/Coverage/TextCoverage.php index dc9d5b6..8b4df82 100644 --- a/src/Coverage/TextCoverage.php +++ b/src/Coverage/TextCoverage.php @@ -9,10 +9,35 @@ */ namespace Datashaman\PHPCheck\Coverage; +use function Datashaman\PHPCheck\{ + app, +}; use SebastianBergmann\CodeCoverage\Report\Text; +/** + * This class produces a text coverage report to standard output or a specified file. + */ class TextCoverage extends Coverage { + private $_output; + private $_noAnsi; + + /** + * @param null|string $output + * @param null|bool $noAnsi + */ + public function __construct( + string $output = null, + bool $noAnsi = null + ) { + $this->_output = $output; + $this->_noAnsi = $noAnsi; + } + + /** + * Processing is done in the __destruct method to ensure maximum coverage + * results. + */ public function __destruct() { global $coverage; @@ -21,16 +46,16 @@ public function __destruct() $writer = new Text(); - if ($this->input->getOption('coverage-text')) { + if ($this->_output) { $output = $writer->process($coverage, false); - \file_put_contents($this->input->getOption('coverage-text'), $output); + \file_put_contents($this->_output, $output); return; } $color = true; - if ($this->input->getOption('no-ansi') !== false) { + if ($this->_noAnsi !== false) { $color = false; } diff --git a/src/CheckError.php b/src/Exceptions/CheckError.php similarity index 93% rename from src/CheckError.php rename to src/Exceptions/CheckError.php index e7f2d2b..8592b72 100644 --- a/src/CheckError.php +++ b/src/Exceptions/CheckError.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Datashaman\PHPCheck; +namespace Datashaman\PHPCheck\Exceptions; use Exception; diff --git a/src/Example.php b/src/Exceptions/Example.php similarity index 91% rename from src/Example.php rename to src/Exceptions/Example.php index 4703eb6..a69abcb 100644 --- a/src/Example.php +++ b/src/Exceptions/Example.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Datashaman\PHPCheck; +namespace Datashaman\PHPCheck\Exceptions; use Exception; diff --git a/src/ExecutionError.php b/src/Exceptions/ExecutionError.php similarity index 95% rename from src/ExecutionError.php rename to src/Exceptions/ExecutionError.php index f1c8a2d..2da1787 100644 --- a/src/ExecutionError.php +++ b/src/Exceptions/ExecutionError.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Datashaman\PHPCheck; +namespace Datashaman\PHPCheck\Exceptions; use Exception; use Throwable; diff --git a/src/ExecutionFailure.php b/src/Exceptions/ExecutionFailure.php similarity index 94% rename from src/ExecutionFailure.php rename to src/Exceptions/ExecutionFailure.php index ef9e50f..8048b45 100644 --- a/src/ExecutionFailure.php +++ b/src/Exceptions/ExecutionFailure.php @@ -7,7 +7,7 @@ * For the full copyright and license information, please view the LICENSE * file that was distributed with this source code. */ -namespace Datashaman\PHPCheck; +namespace Datashaman\PHPCheck\Exceptions; use Exception; diff --git a/src/Reflection.php b/src/Reflection.php index 3671e85..2b769c7 100644 --- a/src/Reflection.php +++ b/src/Reflection.php @@ -24,11 +24,6 @@ public function getClass($class) return new ReflectionClass($class); } - public function getMethod($class, $method) - { - return new ReflectionMethod($class, $method); - } - public function getFunctionSignature(ReflectionFunction $function): string { return $function->getName(); @@ -39,34 +34,6 @@ public function getMethodSignature(ReflectionMethod $method): string return $method->getDeclaringClass()->getName() . '::' . $method->getName(); } - public function getParamAnnotations($reflectionCallable): array - { - $factory = DocBlockFactory::createInstance(); - $docComment = $reflectionCallable->getDocComment(); - - if ($docComment === false) { - return []; - } - - $docBlock = $factory->create($docComment); - - return $docBlock->getTagsByName('param'); - } - - public function getParamAnnotation(ReflectionParameter $param): ?Param - { - $method = $param->getDeclaringFunction(); - $annotations = $this->getParamAnnotations($method); - - foreach ($annotations as $annotation) { - if ($annotation->getVariableName() === $param->getName()) { - return $annotation; - } - } - - return null; - } - public function getParamTags(ReflectionParameter $param): array { $annotation = $this->getParamAnnotation($param); @@ -95,4 +62,32 @@ public function reflect($subject) return $subject; } + + private function getParamAnnotations($reflectionCallable): array + { + $factory = DocBlockFactory::createInstance(); + $docComment = $reflectionCallable->getDocComment(); + + if ($docComment === false) { + return []; + } + + $docBlock = $factory->create($docComment); + + return $docBlock->getTagsByName('param'); + } + + private function getParamAnnotation(ReflectionParameter $param): ?Param + { + $method = $param->getDeclaringFunction(); + $annotations = $this->getParamAnnotations($method); + + foreach ($annotations as $annotation) { + if ($annotation->getVariableName() === $param->getName()) { + return $annotation; + } + } + + return null; + } } diff --git a/src/Results/Failure.php b/src/Results/Failure.php index d44d91e..abe2309 100644 --- a/src/Results/Failure.php +++ b/src/Results/Failure.php @@ -9,6 +9,8 @@ */ namespace Datashaman\PHPCheck\Results; +use Exception; + class Failure { /** diff --git a/src/RunState.php b/src/RunState.php index 4f96477..4eb0fe0 100644 --- a/src/RunState.php +++ b/src/RunState.php @@ -28,7 +28,7 @@ class TEXT NOT NULL, ) EOT; - protected const INSERT_RESULT_SQL = <<<'EOT' + private const INSERT_RESULT_SQL = <<<'EOT' INSERT OR REPLACE INTO results ( class, method, @@ -44,17 +44,17 @@ class, ) EOT; - protected const SELECT_DEFECT_SQL = <<<'EOT' + private const SELECT_DEFECT_SQL = <<<'EOT' SELECT args FROM results WHERE class = :class AND method = :method AND status IN ('ERROR', 'FAILURE') EOT; - protected $errors = []; + private $errors = []; - protected $failures = []; + private $failures = []; - protected $startTime; + private $startTime; - protected $successes = []; + private $successes = []; public static function getSubscribedEvents(): array { @@ -90,22 +90,22 @@ public function getDefectArgs(ReflectionMethod $method): ?array return null; } - public function getErrors() + public function getErrors(): array { return $this->errors; } - public function getFailures() + public function getFailures(): array { return $this->failures; } - public function getSuccesses() + public function getSuccesses(): array { return $this->successes; } - public function getStartTime() + public function getStartTime(): float { return $this->startTime; } @@ -136,7 +136,7 @@ public function onSuccess(Events\SuccessEvent $event): void $this->saveResult($event); } - protected function saveResult(Events\ResultEvent $event): void + private function saveResult(Events\ResultEvent $event): void { $args = $event->args ? (\json_encode($event->args) ?: '') : ''; @@ -151,7 +151,7 @@ protected function saveResult(Events\ResultEvent $event): void $statement->execute(); } - protected function formatMicrotime(float $microtime): string + private function formatMicrotime(float $microtime): string { $decimal = \preg_match('/^[0-9]*\\.([0-9]+)$/', (string) $microtime, $reg) ? \mb_substr(\str_pad($reg[1], 6, '0'), 0, 6) diff --git a/src/Runner.php b/src/Runner.php index 773c0fd..c636f5e 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -11,8 +11,8 @@ */ namespace Datashaman\PHPCheck; +use DateTime; use Exception; -use InvalidArgumentException; use phpDocumentor\Reflection\DocBlockFactory; use ReflectionFunctionAbstract; use ReflectionMethod; @@ -38,6 +38,8 @@ class Runner implements EventSubscriberInterface private $input; + private $maxSuccess; + private $output; private $totalIterations = 0; @@ -96,11 +98,14 @@ public function execute(InputInterface $input, OutputInterface $output): void $output->writeln('You must specify a directory for coverage-html'); exit(1); } - $coverage = new Coverage\HtmlCoverage($this); + $coverage = new Coverage\HtmlCoverage($input->getOption('coverage-html')); } if ($input->getOption('coverage-text') !== false) { - $coverage = new Coverage\TextCoverage($this); + $coverage = new Coverage\TextCoverage( + $input->getOption('coverage-text'), + $input->getOption('no-ansi') + ); } $config = $this->getConfig(); @@ -114,7 +119,7 @@ public function execute(InputInterface $input, OutputInterface $output): void $output->writeln('You must specify a filename for log-junit'); exit(1); } - $reporter = new Subscribers\JUnitReporter($this); + $reporter = new Subscribers\JUnitReporter(); $dispatcher->addSubscriber($reporter); } @@ -123,7 +128,7 @@ public function execute(InputInterface $input, OutputInterface $output): void $output->writeln('You must specify a filename for log-text'); exit(1); } - $reporter = new Subscribers\TextReporter($this); + $reporter = new Subscribers\TextReporter(); $dispatcher->addSubscriber($reporter); } @@ -156,10 +161,7 @@ public function execute(InputInterface $input, OutputInterface $output): void $classes = \array_diff( \get_declared_classes(), - $classes, - [ - Check::class, - ] + $classes ); $classes = \array_filter( @@ -207,10 +209,8 @@ function ($class) { if (!$noDefects && $defectArgs) { try { \call_user_func($closure, ...$defectArgs); - } catch (InvalidArgumentException $exception) { - throw new ExecutionFailure($defectArgs, $exception); } catch (Throwable $throwable) { - throw new ExecutionError($defectArgs, $throwable); + throw new Exceptions\ExecutionError($defectArgs, $throwable); } } @@ -221,14 +221,14 @@ function ($class) { ); if (!$result['passed']) { - throw new ExecutionFailure($result['input'], $result['error']); + throw new Exceptions\ExecutionFailure($result['input']); } } $event = new Events\SuccessEvent($method, $tags); $dispatcher->dispatch(CheckEvents::SUCCESS, $event); $status = 'SUCCESS'; - } catch (ExecutionFailure $failure) { + } catch (Exceptions\ExecutionFailure $failure) { $event = new Events\FailureEvent( $method, $tags, @@ -236,7 +236,7 @@ function ($class) { ); $dispatcher->dispatch(CheckEvents::FAILURE, $event); $status = 'FAILURE'; - } catch (ExecutionError $error) { + } catch (Exceptions\ExecutionError $error) { $event = new Events\ErrorEvent( $method, $tags, @@ -256,7 +256,7 @@ function ($class) { $dispatcher->dispatch(CheckEvents::END_ALL, $event); } - public function checks( + private function checks( int $size, callable $subject, callable $check = null, @@ -289,7 +289,7 @@ public function checks( while (!$passed) { try { $input = $this->shrink($input); - } catch (Example $e) { + } catch (Exceptions\Example $e) { $input = $e->args; $exhausted = true; } @@ -315,7 +315,7 @@ public function checks( return $result; } - protected function getConfig(string $filename = null): ?SimpleXMLElement + private function getConfig(string $filename = null): ?SimpleXMLElement { $filename = self::CONFIG_FILE; @@ -333,7 +333,7 @@ protected function getConfig(string $filename = null): ?SimpleXMLElement return null; } - protected function gatherPaths(OutputInterface $output, string $pathArgument): array + private function gatherPaths(OutputInterface $output, string $pathArgument): array { $path = \realpath($pathArgument); @@ -365,7 +365,7 @@ protected function gatherPaths(OutputInterface $output, string $pathArgument): a return $paths; } - protected function getMethodTags(ReflectionMethod $method) + private function getMethodTags(ReflectionMethod $method) { $factory = DocBlockFactory::createInstance(); $docComment = $method->getDocComment(); @@ -412,7 +412,7 @@ protected function getMethodTags(ReflectionMethod $method) return $result; } - protected function getFilter(InputInterface $input) + private function getFilter(InputInterface $input) { $filter = $input->getOption('filter'); @@ -431,7 +431,7 @@ protected function getFilter(InputInterface $input) return $parts; } - protected function shrink($args) + private function shrink($args) { foreach ($args as &$arg) { if (\is_string($arg)) { @@ -476,10 +476,10 @@ protected function shrink($args) } } - throw new Example($args); + throw new Exceptions\Example($args); } - protected function passed( + private function passed( ReflectionFunctionAbstract $subject, ReflectionFunctionAbstract $check, array $input @@ -491,7 +491,7 @@ protected function passed( try { \set_error_handler( function ($code, $message, $file, $line): void { - throw new CheckError( + throw new Exceptions\CheckError( $message, $code, $file, @@ -506,7 +506,7 @@ function ($code, $message, $file, $line): void { $output = $subject->invoke(...$input); $passed = $check->invoke($input, $output); } - } catch (CheckError $error) { + } catch (Exceptions\CheckError $error) { $passed = false; } diff --git a/src/Subscribers/ConsoleReporter.php b/src/Subscribers/ConsoleReporter.php index 89d21e9..6b33dc1 100644 --- a/src/Subscribers/ConsoleReporter.php +++ b/src/Subscribers/ConsoleReporter.php @@ -12,18 +12,17 @@ namespace Datashaman\PHPCheck\Subscribers; use function Datashaman\PHPCheck\app; - use Datashaman\PHPCheck\CheckCommand; use Datashaman\PHPCheck\CheckEvents; + use Datashaman\PHPCheck\Events; -use Datashaman\PHPCheck\Traits\LogTrait; +use function Datashaman\PHPCheck\reflection; +use function Datashaman\PHPCheck\repr; use NunoMaduro\Collision\Writer; use Whoops\Exception\Inspector; -class ConsoleReporter extends Reporter +class ConsoleReporter extends Subscriber { - use LogTrait; - protected const HEADER = 'PHPCheck %s by Marlin Forbes and contributors.'; protected const STATUS_CHARACTERS = [ @@ -38,9 +37,9 @@ class ConsoleReporter extends Reporter 'SUCCESS' => 'info', ]; - protected $output; + private $output; - protected $writer; + private $writer; public static function getSubscribedEvents(): array { @@ -62,7 +61,6 @@ public function __construct() '#' . $baseDir . '/src/*#', '#' . $baseDir . '/bin/*#', '#vendor/symfony/console.*#', - '#vendor/webmozart/assert.*#', ] ); } @@ -80,7 +78,7 @@ public function onStartAll(Events\StartAllEvent $event): void public function onStart(Events\StartEvent $event): void { if ($this->output->isDebug()) { - $signature = $this->getMethodSignature($event->method); + $signature = reflection()->getMethodSignature($event->method); $this->output->writeln("Check '$signature' started"); } } @@ -88,7 +86,7 @@ public function onStart(Events\StartEvent $event): void public function onEnd(Events\EndEvent $event): void { if ($this->output->isDebug()) { - $signature = $this->getMethodSignature($event->method); + $signature = reflection()->getMethodSignature($event->method); $this->output->writeln("Check '$signature' ended"); return; @@ -145,11 +143,11 @@ public function onEndAll(Events\EndAllEvent $event): void foreach ($failures as $index => $failure) { $number = $index + 1; - $signature = $this->getMethodSignature($failure->method); + $signature = reflection()->getMethodSignature($failure->method); $this->output->writeln("$number) $signature"); $this->output->writeln(''); - $this->output->writeln('ARGS: ' . $this->repr($failure->args)); + $this->output->writeln('ARGS: ' . repr($failure->args)); $this->output->writeln(''); // $inspector = new Inspector($failure->cause); @@ -169,7 +167,7 @@ public function onEndAll(Events\EndAllEvent $event): void foreach ($errors as $index => $error) { $number = $index + 1; - $signature = $this->getMethodSignature($error->method); + $signature = reflection()->getMethodSignature($error->method); $this->output->writeln("$number) $signature"); $inspector = new Inspector($error->cause); @@ -189,4 +187,16 @@ public function onEndAll(Events\EndAllEvent $event): void : "OK $stats" ); } + + private function convertBytes(int $bytes): string + { + if ($bytes == 0) { + return '0.00 B'; + } + + $suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; + $exponent = (int) \floor(\log($bytes, 1024)); + + return \sprintf('%.2f %s', \round($bytes / 1024 ** $exponent, 2), $suffixes[$exponent]); + } } diff --git a/src/Subscribers/JUnitReporter.php b/src/Subscribers/JUnitReporter.php index 6cbb522..d83729d 100644 --- a/src/Subscribers/JUnitReporter.php +++ b/src/Subscribers/JUnitReporter.php @@ -13,13 +13,16 @@ use Datashaman\PHPCheck\CheckEvents; use Datashaman\PHPCheck\Events; +use function Datashaman\PHPCheck\{ + app +}; use SimpleXMLElement; -class JUnitReporter extends Reporter +class JUnitReporter extends Subscriber { - protected $testsuite; + private $testsuite; - protected $testcase; + private $testcase; public static function getSubscribedEvents(): array { @@ -69,6 +72,6 @@ public function onFailure(Events\FailureEvent $event): void public function onEndAll(): void { - $this->testsuite->asXML($this->input->getOption('log-junit')); + $this->testsuite->asXML(app('runner')->getInput()->getOption('log-junit')); } } diff --git a/src/Subscribers/Reporter.php b/src/Subscribers/Reporter.php deleted file mode 100644 index 7c39673..0000000 --- a/src/Subscribers/Reporter.php +++ /dev/null @@ -1,16 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Datashaman\PHPCheck\Subscribers; - -abstract class Reporter extends Subscriber -{ -} diff --git a/src/Subscribers/Subscriber.php b/src/Subscribers/Subscriber.php index 830f791..2358a2b 100644 --- a/src/Subscribers/Subscriber.php +++ b/src/Subscribers/Subscriber.php @@ -9,27 +9,8 @@ */ namespace Datashaman\PHPCheck\Subscribers; -use function Datashaman\PHPCheck\reflection; - -use ReflectionMethod; use Symfony\Component\EventDispatcher\EventSubscriberInterface; abstract class Subscriber implements EventSubscriberInterface { - protected function getMethodSignature(ReflectionMethod $method): string - { - return reflection()->getMethodSignature($method); - } - - protected function convertBytes(int $bytes): string - { - if ($bytes == 0) { - return '0.00 B'; - } - - $suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; - $exponent = (int) \floor(\log($bytes, 1024)); - - return \sprintf('%.2f %s', \round($bytes / 1024 ** $exponent, 2), $suffixes[$exponent]); - } } diff --git a/src/Subscribers/Tabulator.php b/src/Subscribers/Tabulator.php index 12f1cea..44fa4b0 100644 --- a/src/Subscribers/Tabulator.php +++ b/src/Subscribers/Tabulator.php @@ -13,22 +13,21 @@ use function Datashaman\PHPCheck\app; use Datashaman\PHPCheck\CheckEvents; - use function Datashaman\PHPCheck\evalWithArgs; use Datashaman\PHPCheck\Events; -use Datashaman\PHPCheck\Traits\LogTrait; + +use function Datashaman\PHPCheck\reflection; +use function Datashaman\PHPCheck\repr; use Ds\Map; use Exception; class Tabulator extends Subscriber { - use LogTrait; - - protected $names; + private $names; - protected $tables; + private $tables; - protected $stats; + private $stats; public static function getSubscribedEvents(): array { @@ -122,7 +121,7 @@ public function onEndAll(Events\EndAllEvent $event): void $output->writeln(''); foreach ($this->tables->keys() as $index => $method) { - $output->writeln($index + 1 . ') ' . $this->getMethodSignature($method)); + $output->writeln($index + 1 . ') ' . reflection()->getMethodSignature($method)); $output->writeln(''); ['tables' => $tables, 'tags' => $tags] = $this->tables->get($method); @@ -164,13 +163,13 @@ function ($value, $count) use ($total) { \preg_replace( '/(^\[|\]$)/', '', - $this->repr($value) + repr($value) ) ) ); if ($cover->hasKey($value)) { - $expected = $cover[$value]; + $expected = $cover->get($value); if ($percentage < $expected) { $warnings[] = [$value, $expected, $percentage]; @@ -189,7 +188,7 @@ function ($value, $count) use ($total) { "Table '%s' had only %.1f%% %s, but expected %.1f%%", $label, $percentage, - $this->repr($value), + repr($value), $expected ) ); diff --git a/src/Subscribers/TextReporter.php b/src/Subscribers/TextReporter.php index 1208446..bbfaeb6 100644 --- a/src/Subscribers/TextReporter.php +++ b/src/Subscribers/TextReporter.php @@ -9,10 +9,12 @@ */ namespace Datashaman\PHPCheck\Subscribers; +use function Datashaman\PHPCheck\app; use Datashaman\PHPCheck\CheckEvents; use Datashaman\PHPCheck\Events; +use function Datashaman\PHPCheck\reflection; -class TextReporter extends Reporter +class TextReporter extends Subscriber { protected $file; @@ -35,7 +37,7 @@ public function __call(string $name, array $args): void public function onStartAll(Events\StartAllEvent $event): void { - $this->file = \fopen($this->input->getOption('log-text'), 'a'); + $this->file = \fopen(app('runner')->getInput()->getOption('log-text'), 'a'); $this->report('onStartAll', $event); } diff --git a/src/Traits/LogTrait.php b/src/Traits/LogTrait.php deleted file mode 100644 index 7847e33..0000000 --- a/src/Traits/LogTrait.php +++ /dev/null @@ -1,96 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -namespace Datashaman\PHPCheck\Traits; - -use Ds\Map; - -trait LogTrait -{ - public function repr($value) - { - if ($value instanceof Map) { - return \get_class($value) . ' {#' . \spl_object_id($value) . '}'; - } - - if (\is_string($value)) { - return '"' . $value . '"'; - } - - if (\is_numeric($value)) { - return $value; - } - - if (\is_array($value)) { - if (\count($value)) { - $keys = \array_keys($value); - - if (\is_int($keys[0])) { - return '[' . \implode(', ', \array_map( - function ($item) use ($value) { - return $this->repr($item); - }, - $value - )) . ']'; - } else { - return '[' . \implode(', ', \array_map( - function ($key) use ($value) { - return "$key=" . $this->repr($value[$key]); - }, - $keys - )) . ']'; - } - } else { - return '[]'; - } - } - - return \json_encode($value); - } - - public function logExecution($subject, $method, $values = null): void - { - static $counter = 0; - static $lastArgs; - - if (\getenv('APP_ENV') !== 'dev') { - return; - } - - $parts = []; - - if (\is_array($values)) { - foreach ($values as $key => $value) { - if (\is_int($key)) { - $parts[] = $this->repr($value); - } else { - $parts[] = $key . '=' . $this->repr($value); - } - } - } else { - $parts = null === $values - ? [] - : [$this->repr($values)]; - } - - if (isset($lastArgs)) { - if (\func_get_args() == $lastArgs) { - $counter++; - - return; - } - $counter++; - $times = $counter > 1 ? "$counter times" : ''; - \file_put_contents(LOG, $subject . ' ' . $method . '(' . ($parts ? \implode(', ', $parts) : '') . ") $times\n", \FILE_APPEND); - $counter = 0; - } - - $lastArgs = \func_get_args(); - } -} diff --git a/src/bootstrap.php b/src/bootstrap.php index 769156b..57bce15 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -50,7 +50,7 @@ $c['dispatcher']->addSubscriber($c['state']); $c['dispatcher']->addSubscriber($c['tabulator']); - $c['dispatcher']->addSubscriber(new ConsoleReporter($runner)); + $c['dispatcher']->addSubscriber(new ConsoleReporter()); return $runner; }); diff --git a/src/generators.php b/src/generators.php index bc9bf5c..f660319 100644 --- a/src/generators.php +++ b/src/generators.php @@ -15,6 +15,7 @@ use DateTime; use DateTimeZone; use Ds\Map; +use Exception; use Generator; use Webmozart\Assert\Assert; @@ -369,7 +370,7 @@ function () use ($attr, $args, $faker) { ); } -function floats(float $min, float $max): Generator +function floats(float $min = \PHP_FLOAT_MIN, float $max = \PHP_FLOAT_MAX): Generator { logExecution('mkGen', 'floats', [$min, $max]); @@ -401,7 +402,35 @@ function frequency(array $frequencies): Generator $map->put($gen, $weighted); } - return pick($map); + return makeGen( + function (Random $r) use ($map) { + $count = $map->count(); + + if ($count <= 1) { + return $map->keys()->first(); + } + + $sum = $map->sum(); + + if ($sum < 1) { + throw new Exception('Negative or all-zero weights not allowed'); + } + + $targetWeight = $r->random(1, $sum); + + foreach ($map as $key => $weight) { + if ($weight < 0) { + throw new Exception('Negative weights not allowed'); + } + + $targetWeight -= $weight; + + if ($targetWeight <= 0) { + return $key; + } + } + } + ); } function growingElements(array $array): Generator @@ -537,17 +566,6 @@ function (Random $r) use ($gens) { ); } -function pick(Map $weighted): Generator -{ - logExecution('mkGen', 'pick', $weighted); - - return makeGen( - function (Random $r) use ($weighted) { - return $r->arrayWeightRand($weighted); - } - ); -} - function resize(int $n, Generator $gen): Generator { logExecution('mkGen', 'resize', [$n, $gen]); @@ -667,7 +685,7 @@ function suchThatMaybe(Generator $gen, callable $f): Generator }; return makeGen( - function (Random $r, $n) use ($gen, $try) { + function (Random $r, $n) use ($try) { return $try($n, $n * 2, $r); } ); @@ -689,7 +707,7 @@ function (Random $r) use ($positions, $zones) { ); } -function variant(int $seed, Generator $gen): Generator +function variant(string $seed, Generator $gen): Generator { logExecution('mkGen', 'variant', [$seed, $gen]); diff --git a/src/helpers.php b/src/helpers.php index a2053eb..0a1906f 100644 --- a/src/helpers.php +++ b/src/helpers.php @@ -9,6 +9,8 @@ */ namespace Datashaman\PHPCheck; +use Ds\Map; + \define('LOG', 'phpcheck.log'); function app($name, callable $f = null) @@ -65,7 +67,7 @@ function repr($value) if (\is_int($keys[0])) { return '[' . \implode(', ', \array_map( - function ($item) use ($value) { + function ($item) { return repr($item); }, $value