* longOptions['my-switch'] = 'myHandler';
* // my-secondswitch will accept a value - note the equals sign
* $this->longOptions['my-secondswitch='] = 'myOtherHandler';
* }
*
* // --my-switch -> myHandler()
* protected function myHandler()
* {
* }
*
* // --my-secondswitch foo -> myOtherHandler('foo')
* protected function myOtherHandler ($value)
* {
* }
*
* // You will also need this - the static keyword in the
* // PHPUnit\TextUI\Command will mean that it'll be
* // PHPUnit\TextUI\Command that gets instantiated,
* // not MyCommand
* public static function main($exit = true)
* {
* $command = new static;
*
* return $command->run($_SERVER['argv'], $exit);
* }
*
* }
*
*
* @throws Exception
*/
protected function handleArguments(array $argv): void
{
try {
$arguments = (new Builder)->fromParameters($argv, array_keys($this->longOptions));
} catch (ArgumentsException $e) {
$this->exitWithErrorMessage($e->getMessage());
}
assert(isset($arguments) && $arguments instanceof Configuration);
if ($arguments->hasGenerateConfiguration() && $arguments->generateConfiguration()) {
$this->generateConfiguration();
}
if ($arguments->hasAtLeastVersion()) {
if (version_compare(Version::id(), $arguments->atLeastVersion(), '>=')) {
exit(TestRunner::SUCCESS_EXIT);
}
exit(TestRunner::FAILURE_EXIT);
}
if ($arguments->hasVersion() && $arguments->version()) {
$this->printVersionString();
exit(TestRunner::SUCCESS_EXIT);
}
if ($arguments->hasCheckVersion() && $arguments->checkVersion()) {
$this->handleVersionCheck();
}
if ($arguments->hasHelp()) {
$this->showHelp();
exit(TestRunner::SUCCESS_EXIT);
}
if ($arguments->hasUnrecognizedOrderBy()) {
$this->exitWithErrorMessage(
sprintf(
'unrecognized --order-by option: %s',
$arguments->unrecognizedOrderBy()
)
);
}
if ($arguments->hasIniSettings()) {
foreach ($arguments->iniSettings() as $name => $value) {
ini_set($name, $value);
}
}
if ($arguments->hasIncludePath()) {
ini_set(
'include_path',
$arguments->includePath() . PATH_SEPARATOR . ini_get('include_path')
);
}
$this->arguments = (new Mapper)->mapToLegacyArray($arguments);
$this->handleCustomOptions($arguments->unrecognizedOptions());
$this->handleCustomTestSuite();
if (!isset($this->arguments['testSuffixes'])) {
$this->arguments['testSuffixes'] = ['Test.php', '.phpt'];
}
if (!isset($this->arguments['test']) && $arguments->hasArgument()) {
$this->arguments['test'] = realpath($arguments->argument());
if ($this->arguments['test'] === false) {
$this->exitWithErrorMessage(
sprintf(
'Cannot open file "%s".',
$arguments->argument()
)
);
}
}
if ($this->arguments['loader'] !== null) {
$this->arguments['loader'] = $this->handleLoader($this->arguments['loader']);
}
if (isset($this->arguments['configuration'])) {
if (is_dir($this->arguments['configuration'])) {
$candidate = $this->configurationFileInDirectory($this->arguments['configuration']);
if ($candidate !== null) {
$this->arguments['configuration'] = $candidate;
}
}
} elseif ($this->arguments['useDefaultConfiguration']) {
$candidate = $this->configurationFileInDirectory(getcwd());
if ($candidate !== null) {
$this->arguments['configuration'] = $candidate;
}
}
if ($arguments->hasMigrateConfiguration() && $arguments->migrateConfiguration()) {
if (!isset($this->arguments['configuration'])) {
print 'No configuration file found to migrate.' . PHP_EOL;
exit(TestRunner::EXCEPTION_EXIT);
}
$this->migrateConfiguration(realpath($this->arguments['configuration']));
}
if (isset($this->arguments['configuration'])) {
try {
$this->arguments['configurationObject'] = (new Loader)->load($this->arguments['configuration']);
} catch (Throwable $e) {
print $e->getMessage() . PHP_EOL;
exit(TestRunner::FAILURE_EXIT);
}
$phpunitConfiguration = $this->arguments['configurationObject']->phpunit();
(new PhpHandler)->handle($this->arguments['configurationObject']->php());
if (isset($this->arguments['bootstrap'])) {
$this->handleBootstrap($this->arguments['bootstrap']);
} elseif ($phpunitConfiguration->hasBootstrap()) {
$this->handleBootstrap($phpunitConfiguration->bootstrap());
}
if (!isset($this->arguments['stderr'])) {
$this->arguments['stderr'] = $phpunitConfiguration->stderr();
}
if (!isset($this->arguments['noExtensions']) && $phpunitConfiguration->hasExtensionsDirectory() && extension_loaded('phar')) {
$result = (new PharLoader)->loadPharExtensionsInDirectory($phpunitConfiguration->extensionsDirectory());
$this->arguments['loadedExtensions'] = $result['loadedExtensions'];
$this->arguments['notLoadedExtensions'] = $result['notLoadedExtensions'];
unset($result);
}
if (!isset($this->arguments['columns'])) {
$this->arguments['columns'] = $phpunitConfiguration->columns();
}
if (!isset($this->arguments['printer']) && $phpunitConfiguration->hasPrinterClass()) {
$file = $phpunitConfiguration->hasPrinterFile() ? $phpunitConfiguration->printerFile() : '';
$this->arguments['printer'] = $this->handlePrinter(
$phpunitConfiguration->printerClass(),
$file
);
}
if ($phpunitConfiguration->hasTestSuiteLoaderClass()) {
$file = $phpunitConfiguration->hasTestSuiteLoaderFile() ? $phpunitConfiguration->testSuiteLoaderFile() : '';
$this->arguments['loader'] = $this->handleLoader(
$phpunitConfiguration->testSuiteLoaderClass(),
$file
);
}
if (!isset($this->arguments['testsuite']) && $phpunitConfiguration->hasDefaultTestSuite()) {
$this->arguments['testsuite'] = $phpunitConfiguration->defaultTestSuite();
}
if (!isset($this->arguments['test'])) {
try {
$this->arguments['test'] = (new TestSuiteMapper)->map(
$this->arguments['configurationObject']->testSuite(),
$this->arguments['testsuite'] ?? ''
);
} catch (Exception $e) {
$this->printVersionString();
print $e->getMessage() . PHP_EOL;
exit(TestRunner::EXCEPTION_EXIT);
}
}
} elseif (isset($this->arguments['bootstrap'])) {
$this->handleBootstrap($this->arguments['bootstrap']);
}
if (isset($this->arguments['printer']) && is_string($this->arguments['printer'])) {
$this->arguments['printer'] = $this->handlePrinter($this->arguments['printer']);
}
if (isset($this->arguments['configurationObject'], $this->arguments['warmCoverageCache'])) {
$this->handleWarmCoverageCache($this->arguments['configurationObject']);
}
if (!isset($this->arguments['test'])) {
$this->showHelp();
exit(TestRunner::EXCEPTION_EXIT);
}
}
/**
* Handles the loading of the PHPUnit\Runner\TestSuiteLoader implementation.
*
* @deprecated see https://github.com/sebastianbergmann/phpunit/issues/4039
*/
protected function handleLoader(string $loaderClass, string $loaderFile = ''): ?TestSuiteLoader
{
$this->warnings[] = 'Using a custom test suite loader is deprecated';
if (!class_exists($loaderClass, false)) {
if ($loaderFile == '') {
$loaderFile = Filesystem::classNameToFilename(
$loaderClass
);
}
$loaderFile = stream_resolve_include_path($loaderFile);
if ($loaderFile) {
/**
* @noinspection PhpIncludeInspection
*
* @psalm-suppress UnresolvableInclude
*/
require $loaderFile;
}
}
if (class_exists($loaderClass, false)) {
try {
$class = new ReflectionClass($loaderClass);
// @codeCoverageIgnoreStart
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
$e->getCode(),
$e
);
}
// @codeCoverageIgnoreEnd
if ($class->implementsInterface(TestSuiteLoader::class) && $class->isInstantiable()) {
$object = $class->newInstance();
assert($object instanceof TestSuiteLoader);
return $object;
}
}
if ($loaderClass == StandardTestSuiteLoader::class) {
return null;
}
$this->exitWithErrorMessage(
sprintf(
'Could not use "%s" as loader.',
$loaderClass
)
);
return null;
}
/**
* Handles the loading of the PHPUnit\Util\Printer implementation.
*
* @return null|Printer|string
*/
protected function handlePrinter(string $printerClass, string $printerFile = '')
{
if (!class_exists($printerClass, false)) {
if ($printerFile === '') {
$printerFile = Filesystem::classNameToFilename(
$printerClass
);
}
$printerFile = stream_resolve_include_path($printerFile);
if ($printerFile) {
/**
* @noinspection PhpIncludeInspection
*
* @psalm-suppress UnresolvableInclude
*/
require $printerFile;
}
}
if (!class_exists($printerClass)) {
$this->exitWithErrorMessage(
sprintf(
'Could not use "%s" as printer: class does not exist',
$printerClass
)
);
}
try {
$class = new ReflectionClass($printerClass);
// @codeCoverageIgnoreStart
} catch (\ReflectionException $e) {
throw new ReflectionException(
$e->getMessage(),
$e->getCode(),
$e
);
// @codeCoverageIgnoreEnd
}
if (!$class->implementsInterface(ResultPrinter::class)) {
$this->exitWithErrorMessage(
sprintf(
'Could not use "%s" as printer: class does not implement %s',
$printerClass,
ResultPrinter::class
)
);
}
if (!$class->isInstantiable()) {
$this->exitWithErrorMessage(
sprintf(
'Could not use "%s" as printer: class cannot be instantiated',
$printerClass
)
);
}
if ($class->isSubclassOf(ResultPrinter::class)) {
return $printerClass;
}
$outputStream = isset($this->arguments['stderr']) ? 'php://stderr' : null;
return $class->newInstance($outputStream);
}
/**
* Loads a bootstrap file.
*/
protected function handleBootstrap(string $filename): void
{
try {
FileLoader::checkAndLoad($filename);
} catch (Throwable $t) {
if ($t instanceof \PHPUnit\Exception) {
$this->exitWithErrorMessage($t->getMessage());
}
$this->exitWithErrorMessage(
sprintf(
'Error in bootstrap script: %s:%s%s%s%s',
get_class($t),
PHP_EOL,
$t->getMessage(),
PHP_EOL,
$t->getTraceAsString()
)
);
}
}
protected function handleVersionCheck(): void
{
$this->printVersionString();
$latestVersion = file_get_contents('https://phar.phpunit.de/latest-version-of/phpunit');
$isOutdated = version_compare($latestVersion, Version::id(), '>');
if ($isOutdated) {
printf(
'You are not using the latest version of PHPUnit.' . PHP_EOL .
'The latest version is PHPUnit %s.' . PHP_EOL,
$latestVersion
);
} else {
print 'You are using the latest version of PHPUnit.' . PHP_EOL;
}
exit(TestRunner::SUCCESS_EXIT);
}
/**
* Show the help message.
*/
protected function showHelp(): void
{
$this->printVersionString();
(new Help)->writeToConsole();
}
/**
* Custom callback for test suite discovery.
*/
protected function handleCustomTestSuite(): void
{
}
private function printVersionString(): void
{
if ($this->versionStringPrinted) {
return;
}
print Version::getVersionString() . PHP_EOL . PHP_EOL;
$this->versionStringPrinted = true;
}
private function exitWithErrorMessage(string $message): void
{
$this->printVersionString();
print $message . PHP_EOL;
exit(TestRunner::FAILURE_EXIT);
}
private function handleListGroups(TestSuite $suite, bool $exit): int
{
$this->printVersionString();
$this->warnAboutConflictingOptions(
'listGroups',
[
'filter',
'groups',
'excludeGroups',
'testsuite',
]
);
print 'Available test group(s):' . PHP_EOL;
$groups = $suite->getGroups();
sort($groups);
foreach ($groups as $group) {
if (strpos($group, '__phpunit_') === 0) {
continue;
}
printf(
' - %s' . PHP_EOL,
$group
);
}
if ($exit) {
exit(TestRunner::SUCCESS_EXIT);
}
return TestRunner::SUCCESS_EXIT;
}
/**
* @throws \PHPUnit\Framework\Exception
* @throws \PHPUnit\TextUI\XmlConfiguration\Exception
*/
private function handleListSuites(bool $exit): int
{
$this->printVersionString();
$this->warnAboutConflictingOptions(
'listSuites',
[
'filter',
'groups',
'excludeGroups',
'testsuite',
]
);
print 'Available test suite(s):' . PHP_EOL;
foreach ($this->arguments['configurationObject']->testSuite() as $testSuite) {
printf(
' - %s' . PHP_EOL,
$testSuite->name()
);
}
if ($exit) {
exit(TestRunner::SUCCESS_EXIT);
}
return TestRunner::SUCCESS_EXIT;
}
/**
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
private function handleListTests(TestSuite $suite, bool $exit): int
{
$this->printVersionString();
$this->warnAboutConflictingOptions(
'listTests',
[
'filter',
'groups',
'excludeGroups',
]
);
$renderer = new TextTestListRenderer;
print $renderer->render($suite);
if ($exit) {
exit(TestRunner::SUCCESS_EXIT);
}
return TestRunner::SUCCESS_EXIT;
}
/**
* @throws \SebastianBergmann\RecursionContext\InvalidArgumentException
*/
private function handleListTestsXml(TestSuite $suite, string $target, bool $exit): int
{
$this->printVersionString();
$this->warnAboutConflictingOptions(
'listTestsXml',
[
'filter',
'groups',
'excludeGroups',
]
);
$renderer = new XmlTestListRenderer;
file_put_contents($target, $renderer->render($suite));
printf(
'Wrote list of tests that would have been run to %s' . PHP_EOL,
$target
);
if ($exit) {
exit(TestRunner::SUCCESS_EXIT);
}
return TestRunner::SUCCESS_EXIT;
}
private function generateConfiguration(): void
{
$this->printVersionString();
print 'Generating phpunit.xml in ' . getcwd() . PHP_EOL . PHP_EOL;
print 'Bootstrap script (relative to path shown above; default: vendor/autoload.php): ';
$bootstrapScript = trim(fgets(STDIN));
print 'Tests directory (relative to path shown above; default: tests): ';
$testsDirectory = trim(fgets(STDIN));
print 'Source directory (relative to path shown above; default: src): ';
$src = trim(fgets(STDIN));
print 'Cache directory (relative to path shown above; default: .phpunit.cache): ';
$cacheDirectory = trim(fgets(STDIN));
if ($bootstrapScript === '') {
$bootstrapScript = 'vendor/autoload.php';
}
if ($testsDirectory === '') {
$testsDirectory = 'tests';
}
if ($src === '') {
$src = 'src';
}
if ($cacheDirectory === '') {
$cacheDirectory = '.phpunit.cache';
}
$generator = new Generator;
file_put_contents(
'phpunit.xml',
$generator->generateDefaultConfiguration(
Version::series(),
$bootstrapScript,
$testsDirectory,
$src,
$cacheDirectory
)
);
print PHP_EOL . 'Generated phpunit.xml in ' . getcwd() . '.' . PHP_EOL;
print 'Make sure to exclude the ' . $cacheDirectory . ' directory from version control.' . PHP_EOL;
exit(TestRunner::SUCCESS_EXIT);
}
private function migrateConfiguration(string $filename): void
{
$this->printVersionString();
if (!(new SchemaDetector)->detect($filename)->detected()) {
print $filename . ' does not need to be migrated.' . PHP_EOL;
exit(TestRunner::EXCEPTION_EXIT);
}
copy($filename, $filename . '.bak');
print 'Created backup: ' . $filename . '.bak' . PHP_EOL;
try {
file_put_contents(
$filename,
(new Migrator)->migrate($filename)
);
print 'Migrated configuration: ' . $filename . PHP_EOL;
} catch (Throwable $t) {
print 'Migration failed: ' . $t->getMessage() . PHP_EOL;
exit(TestRunner::EXCEPTION_EXIT);
}
exit(TestRunner::SUCCESS_EXIT);
}
private function handleCustomOptions(array $unrecognizedOptions): void
{
foreach ($unrecognizedOptions as $name => $value) {
if (isset($this->longOptions[$name])) {
$handler = $this->longOptions[$name];
}
$name .= '=';
if (isset($this->longOptions[$name])) {
$handler = $this->longOptions[$name];
}
if (isset($handler) && is_callable([$this, $handler])) {
$this->{$handler}($value);
unset($handler);
}
}
}
private function handleWarmCoverageCache(XmlConfiguration\Configuration $configuration): void
{
$this->printVersionString();
if (isset($this->arguments['coverageCacheDirectory'])) {
$cacheDirectory = $this->arguments['coverageCacheDirectory'];
} elseif ($configuration->codeCoverage()->hasCacheDirectory()) {
$cacheDirectory = $configuration->codeCoverage()->cacheDirectory()->path();
} else {
print 'Cache for static analysis has not been configured' . PHP_EOL;
exit(TestRunner::EXCEPTION_EXIT);
}
$filter = new Filter;
if ($configuration->codeCoverage()->hasNonEmptyListOfFilesToBeIncludedInCodeCoverageReport()) {
(new FilterMapper)->map(
$filter,
$configuration->codeCoverage()
);
} elseif (isset($this->arguments['coverageFilter'])) {
if (!is_array($this->arguments['coverageFilter'])) {
$coverageFilterDirectories = [$this->arguments['coverageFilter']];
} else {
$coverageFilterDirectories = $this->arguments['coverageFilter'];
}
foreach ($coverageFilterDirectories as $coverageFilterDirectory) {
$filter->includeDirectory($coverageFilterDirectory);
}
} else {
print 'Filter for code coverage has not been configured' . PHP_EOL;
exit(TestRunner::EXCEPTION_EXIT);
}
$timer = new Timer;
$timer->start();
print 'Warming cache for static analysis ... ';
(new CacheWarmer)->warmCache(
$cacheDirectory,
!$configuration->codeCoverage()->disableCodeCoverageIgnore(),
$configuration->codeCoverage()->ignoreDeprecatedCodeUnits(),
$filter
);
print 'done [' . $timer->stop()->asString() . ']' . PHP_EOL;
exit(TestRunner::SUCCESS_EXIT);
}
private function configurationFileInDirectory(string $directory): ?string
{
$candidates = [
$directory . '/phpunit.xml',
$directory . '/phpunit.xml.dist',
];
foreach ($candidates as $candidate) {
if (is_file($candidate)) {
return realpath($candidate);
}
}
return null;
}
/**
* @psalm-param "listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite" $key
* @psalm-param list<"listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite"> $keys
*/
private function warnAboutConflictingOptions(string $key, array $keys): void
{
$warningPrinted = false;
foreach ($keys as $_key) {
if (!empty($this->arguments[$_key])) {
printf(
'The %s and %s options cannot be combined, %s is ignored' . PHP_EOL,
$this->mapKeyToOptionForWarning($_key),
$this->mapKeyToOptionForWarning($key),
$this->mapKeyToOptionForWarning($_key)
);
$warningPrinted = true;
}
}
if ($warningPrinted) {
print PHP_EOL;
}
}
/**
* @psalm-param "listGroups"|"listSuites"|"listTests"|"listTestsXml"|"filter"|"groups"|"excludeGroups"|"testsuite" $key
*/
private function mapKeyToOptionForWarning(string $key): string
{
switch ($key) {
case 'listGroups':
return '--list-groups';
case 'listSuites':
return '--list-suites';
case 'listTests':
return '--list-tests';
case 'listTestsXml':
return '--list-tests-xml';
case 'filter':
return '--filter';
case 'groups':
return '--group';
case 'excludeGroups':
return '--exclude-group';
case 'testsuite':
return '--testsuite';
}
}
}
phpunit/src/TextUI/TestSuiteMapper.php 0000644 00000006601 15024772077 0014031 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI;
use const PHP_VERSION;
use function explode;
use function in_array;
use function is_dir;
use function is_file;
use function strpos;
use function version_compare;
use PHPUnit\Framework\Exception as FrameworkException;
use PHPUnit\Framework\TestSuite as TestSuiteObject;
use PHPUnit\TextUI\XmlConfiguration\TestSuiteCollection;
use SebastianBergmann\FileIterator\Facade;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class TestSuiteMapper
{
/**
* @throws RuntimeException
* @throws TestDirectoryNotFoundException
* @throws TestFileNotFoundException
*/
public function map(TestSuiteCollection $configuration, string $filter): TestSuiteObject
{
try {
$filterAsArray = $filter ? explode(',', $filter) : [];
$result = new TestSuiteObject;
foreach ($configuration as $testSuiteConfiguration) {
if (!empty($filterAsArray) && !in_array($testSuiteConfiguration->name(), $filterAsArray, true)) {
continue;
}
$testSuite = new TestSuiteObject($testSuiteConfiguration->name());
$testSuiteEmpty = true;
$exclude = [];
foreach ($testSuiteConfiguration->exclude()->asArray() as $file) {
$exclude[] = $file->path();
}
foreach ($testSuiteConfiguration->directories() as $directory) {
if (!version_compare(PHP_VERSION, $directory->phpVersion(), $directory->phpVersionOperator()->asString())) {
continue;
}
$files = (new Facade)->getFilesAsArray(
$directory->path(),
$directory->suffix(),
$directory->prefix(),
$exclude
);
if (!empty($files)) {
$testSuite->addTestFiles($files);
$testSuiteEmpty = false;
} elseif (strpos($directory->path(), '*') === false && !is_dir($directory->path())) {
throw new TestDirectoryNotFoundException($directory->path());
}
}
foreach ($testSuiteConfiguration->files() as $file) {
if (!is_file($file->path())) {
throw new TestFileNotFoundException($file->path());
}
if (!version_compare(PHP_VERSION, $file->phpVersion(), $file->phpVersionOperator()->asString())) {
continue;
}
$testSuite->addTestFile($file->path());
$testSuiteEmpty = false;
}
if (!$testSuiteEmpty) {
$result->addTest($testSuite);
}
}
return $result;
} catch (FrameworkException $e) {
throw new RuntimeException(
$e->getMessage(),
$e->getCode(),
$e
);
}
}
}
phpunit/src/TextUI/DefaultResultPrinter.php 0000644 00000036030 15024772077 0015061 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI;
use const PHP_EOL;
use function array_map;
use function array_reverse;
use function count;
use function floor;
use function implode;
use function in_array;
use function is_int;
use function max;
use function preg_split;
use function sprintf;
use function str_pad;
use function str_repeat;
use function strlen;
use function trim;
use function vsprintf;
use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Exception;
use PHPUnit\Framework\InvalidArgumentException;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestFailure;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\TestSuite;
use PHPUnit\Framework\Warning;
use PHPUnit\Runner\PhptTestCase;
use PHPUnit\Util\Color;
use PHPUnit\Util\Printer;
use SebastianBergmann\Environment\Console;
use SebastianBergmann\Timer\ResourceUsageFormatter;
use SebastianBergmann\Timer\Timer;
use Throwable;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
class DefaultResultPrinter extends Printer implements ResultPrinter
{
public const EVENT_TEST_START = 0;
public const EVENT_TEST_END = 1;
public const EVENT_TESTSUITE_START = 2;
public const EVENT_TESTSUITE_END = 3;
public const COLOR_NEVER = 'never';
public const COLOR_AUTO = 'auto';
public const COLOR_ALWAYS = 'always';
public const COLOR_DEFAULT = self::COLOR_NEVER;
private const AVAILABLE_COLORS = [self::COLOR_NEVER, self::COLOR_AUTO, self::COLOR_ALWAYS];
/**
* @var int
*/
protected $column = 0;
/**
* @var int
*/
protected $maxColumn;
/**
* @var bool
*/
protected $lastTestFailed = false;
/**
* @var int
*/
protected $numAssertions = 0;
/**
* @var int
*/
protected $numTests = -1;
/**
* @var int
*/
protected $numTestsRun = 0;
/**
* @var int
*/
protected $numTestsWidth;
/**
* @var bool
*/
protected $colors = false;
/**
* @var bool
*/
protected $debug = false;
/**
* @var bool
*/
protected $verbose = false;
/**
* @var int
*/
private $numberOfColumns;
/**
* @var bool
*/
private $reverse;
/**
* @var bool
*/
private $defectListPrinted = false;
/**
* @var Timer
*/
private $timer;
/**
* Constructor.
*
* @param null|resource|string $out
* @param int|string $numberOfColumns
*
* @throws Exception
*/
public function __construct($out = null, bool $verbose = false, string $colors = self::COLOR_DEFAULT, bool $debug = false, $numberOfColumns = 80, bool $reverse = false)
{
parent::__construct($out);
if (!in_array($colors, self::AVAILABLE_COLORS, true)) {
throw InvalidArgumentException::create(
3,
vsprintf('value from "%s", "%s" or "%s"', self::AVAILABLE_COLORS)
);
}
if (!is_int($numberOfColumns) && $numberOfColumns !== 'max') {
throw InvalidArgumentException::create(5, 'integer or "max"');
}
$console = new Console;
$maxNumberOfColumns = $console->getNumberOfColumns();
if ($numberOfColumns === 'max' || ($numberOfColumns !== 80 && $numberOfColumns > $maxNumberOfColumns)) {
$numberOfColumns = $maxNumberOfColumns;
}
$this->numberOfColumns = $numberOfColumns;
$this->verbose = $verbose;
$this->debug = $debug;
$this->reverse = $reverse;
if ($colors === self::COLOR_AUTO && $console->hasColorSupport()) {
$this->colors = true;
} else {
$this->colors = (self::COLOR_ALWAYS === $colors);
}
$this->timer = new Timer;
$this->timer->start();
}
public function printResult(TestResult $result): void
{
$this->printHeader($result);
$this->printErrors($result);
$this->printWarnings($result);
$this->printFailures($result);
$this->printRisky($result);
if ($this->verbose) {
$this->printIncompletes($result);
$this->printSkipped($result);
}
$this->printFooter($result);
}
/**
* An error occurred.
*/
public function addError(Test $test, Throwable $t, float $time): void
{
$this->writeProgressWithColor('fg-red, bold', 'E');
$this->lastTestFailed = true;
}
/**
* A failure occurred.
*/
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
{
$this->writeProgressWithColor('bg-red, fg-white', 'F');
$this->lastTestFailed = true;
}
/**
* A warning occurred.
*/
public function addWarning(Test $test, Warning $e, float $time): void
{
$this->writeProgressWithColor('fg-yellow, bold', 'W');
$this->lastTestFailed = true;
}
/**
* Incomplete test.
*/
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
{
$this->writeProgressWithColor('fg-yellow, bold', 'I');
$this->lastTestFailed = true;
}
/**
* Risky test.
*/
public function addRiskyTest(Test $test, Throwable $t, float $time): void
{
$this->writeProgressWithColor('fg-yellow, bold', 'R');
$this->lastTestFailed = true;
}
/**
* Skipped test.
*/
public function addSkippedTest(Test $test, Throwable $t, float $time): void
{
$this->writeProgressWithColor('fg-cyan, bold', 'S');
$this->lastTestFailed = true;
}
/**
* A testsuite started.
*/
public function startTestSuite(TestSuite $suite): void
{
if ($this->numTests == -1) {
$this->numTests = count($suite);
$this->numTestsWidth = strlen((string) $this->numTests);
$this->maxColumn = $this->numberOfColumns - strlen(' / (XXX%)') - (2 * $this->numTestsWidth);
}
}
/**
* A testsuite ended.
*/
public function endTestSuite(TestSuite $suite): void
{
}
/**
* A test started.
*/
public function startTest(Test $test): void
{
if ($this->debug) {
$this->write(
sprintf(
"Test '%s' started\n",
\PHPUnit\Util\Test::describeAsString($test)
)
);
}
}
/**
* A test ended.
*/
public function endTest(Test $test, float $time): void
{
if ($this->debug) {
$this->write(
sprintf(
"Test '%s' ended\n",
\PHPUnit\Util\Test::describeAsString($test)
)
);
}
if (!$this->lastTestFailed) {
$this->writeProgress('.');
}
if ($test instanceof TestCase) {
$this->numAssertions += $test->getNumAssertions();
} elseif ($test instanceof PhptTestCase) {
$this->numAssertions++;
}
$this->lastTestFailed = false;
if ($test instanceof TestCase && !$test->hasExpectationOnOutput()) {
$this->write($test->getActualOutput());
}
}
protected function printDefects(array $defects, string $type): void
{
$count = count($defects);
if ($count == 0) {
return;
}
if ($this->defectListPrinted) {
$this->write("\n--\n\n");
}
$this->write(
sprintf(
"There %s %d %s%s:\n",
($count == 1) ? 'was' : 'were',
$count,
$type,
($count == 1) ? '' : 's'
)
);
$i = 1;
if ($this->reverse) {
$defects = array_reverse($defects);
}
foreach ($defects as $defect) {
$this->printDefect($defect, $i++);
}
$this->defectListPrinted = true;
}
protected function printDefect(TestFailure $defect, int $count): void
{
$this->printDefectHeader($defect, $count);
$this->printDefectTrace($defect);
}
protected function printDefectHeader(TestFailure $defect, int $count): void
{
$this->write(
sprintf(
"\n%d) %s\n",
$count,
$defect->getTestName()
)
);
}
protected function printDefectTrace(TestFailure $defect): void
{
$e = $defect->thrownException();
$this->write((string) $e);
while ($e = $e->getPrevious()) {
$this->write("\nCaused by\n" . trim((string) $e) . "\n");
}
}
protected function printErrors(TestResult $result): void
{
$this->printDefects($result->errors(), 'error');
}
protected function printFailures(TestResult $result): void
{
$this->printDefects($result->failures(), 'failure');
}
protected function printWarnings(TestResult $result): void
{
$this->printDefects($result->warnings(), 'warning');
}
protected function printIncompletes(TestResult $result): void
{
$this->printDefects($result->notImplemented(), 'incomplete test');
}
protected function printRisky(TestResult $result): void
{
$this->printDefects($result->risky(), 'risky test');
}
protected function printSkipped(TestResult $result): void
{
$this->printDefects($result->skipped(), 'skipped test');
}
protected function printHeader(TestResult $result): void
{
if (count($result) > 0) {
$this->write(PHP_EOL . PHP_EOL . (new ResourceUsageFormatter)->resourceUsage($this->timer->stop()) . PHP_EOL . PHP_EOL);
}
}
protected function printFooter(TestResult $result): void
{
if (count($result) === 0) {
$this->writeWithColor(
'fg-black, bg-yellow',
'No tests executed!'
);
return;
}
if ($result->wasSuccessfulAndNoTestIsRiskyOrSkippedOrIncomplete()) {
$this->writeWithColor(
'fg-black, bg-green',
sprintf(
'OK (%d test%s, %d assertion%s)',
count($result),
(count($result) === 1) ? '' : 's',
$this->numAssertions,
($this->numAssertions === 1) ? '' : 's'
)
);
return;
}
$color = 'fg-black, bg-yellow';
if ($result->wasSuccessful()) {
if ($this->verbose || !$result->allHarmless()) {
$this->write("\n");
}
$this->writeWithColor(
$color,
'OK, but incomplete, skipped, or risky tests!'
);
} else {
$this->write("\n");
if ($result->errorCount()) {
$color = 'fg-white, bg-red';
$this->writeWithColor(
$color,
'ERRORS!'
);
} elseif ($result->failureCount()) {
$color = 'fg-white, bg-red';
$this->writeWithColor(
$color,
'FAILURES!'
);
} elseif ($result->warningCount()) {
$color = 'fg-black, bg-yellow';
$this->writeWithColor(
$color,
'WARNINGS!'
);
}
}
$this->writeCountString(count($result), 'Tests', $color, true);
$this->writeCountString($this->numAssertions, 'Assertions', $color, true);
$this->writeCountString($result->errorCount(), 'Errors', $color);
$this->writeCountString($result->failureCount(), 'Failures', $color);
$this->writeCountString($result->warningCount(), 'Warnings', $color);
$this->writeCountString($result->skippedCount(), 'Skipped', $color);
$this->writeCountString($result->notImplementedCount(), 'Incomplete', $color);
$this->writeCountString($result->riskyCount(), 'Risky', $color);
$this->writeWithColor($color, '.');
}
protected function writeProgress(string $progress): void
{
if ($this->debug) {
return;
}
$this->write($progress);
$this->column++;
$this->numTestsRun++;
if ($this->column == $this->maxColumn || $this->numTestsRun == $this->numTests) {
if ($this->numTestsRun == $this->numTests) {
$this->write(str_repeat(' ', $this->maxColumn - $this->column));
}
$this->write(
sprintf(
' %' . $this->numTestsWidth . 'd / %' .
$this->numTestsWidth . 'd (%3s%%)',
$this->numTestsRun,
$this->numTests,
floor(($this->numTestsRun / $this->numTests) * 100)
)
);
if ($this->column == $this->maxColumn) {
$this->writeNewLine();
}
}
}
protected function writeNewLine(): void
{
$this->column = 0;
$this->write("\n");
}
/**
* Formats a buffer with a specified ANSI color sequence if colors are
* enabled.
*/
protected function colorizeTextBox(string $color, string $buffer): string
{
if (!$this->colors) {
return $buffer;
}
$lines = preg_split('/\r\n|\r|\n/', $buffer);
$padding = max(array_map('\strlen', $lines));
$styledLines = [];
foreach ($lines as $line) {
$styledLines[] = Color::colorize($color, str_pad($line, $padding));
}
return implode(PHP_EOL, $styledLines);
}
/**
* Writes a buffer out with a color sequence if colors are enabled.
*/
protected function writeWithColor(string $color, string $buffer, bool $lf = true): void
{
$this->write($this->colorizeTextBox($color, $buffer));
if ($lf) {
$this->write(PHP_EOL);
}
}
/**
* Writes progress with a color sequence if colors are enabled.
*/
protected function writeProgressWithColor(string $color, string $buffer): void
{
$buffer = $this->colorizeTextBox($color, $buffer);
$this->writeProgress($buffer);
}
private function writeCountString(int $count, string $name, string $color, bool $always = false): void
{
static $first = true;
if ($always || $count > 0) {
$this->writeWithColor(
$color,
sprintf(
'%s%s: %d',
!$first ? ', ' : '',
$name,
$count
),
false
);
$first = false;
}
}
}
phpunit/src/TextUI/Help.php 0000644 00000032241 15024772077 0011622 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TextUI;
use const PHP_EOL;
use function count;
use function explode;
use function max;
use function preg_replace_callback;
use function str_pad;
use function str_repeat;
use function strlen;
use function wordwrap;
use PHPUnit\Util\Color;
use SebastianBergmann\Environment\Console;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class Help
{
private const LEFT_MARGIN = ' ';
private const HELP_TEXT = [
'Usage' => [
['text' => 'phpunit [options] UnitTest.php'],
['text' => 'phpunit [options] addTest()
and addTestSuite
* as well as the separate import statements for the user's convenience.
*
* If the named file cannot be read or there are no new tests that can be
* added, a PHPUnit\Framework\WarningTestCase
will be created instead,
* leaving the current test run untouched.
*
* @throws Exception
*/
public function addTestFile(string $filename): void
{
if (is_file($filename) && substr($filename, -5) === '.phpt') {
$this->addTest(new PhptTestCase($filename));
$this->declaredClassesPointer = count(get_declared_classes());
return;
}
$numTests = count($this->tests);
// The given file may contain further stub classes in addition to the
// test class itself. Figure out the actual test class.
$filename = FileLoader::checkAndLoad($filename);
$newClasses = array_slice(get_declared_classes(), $this->declaredClassesPointer);
// The diff is empty in case a parent class (with test methods) is added
// AFTER a child class that inherited from it. To account for that case,
// accumulate all discovered classes, so the parent class may be found in
// a later invocation.
if (!empty($newClasses)) {
// On the assumption that test classes are defined first in files,
// process discovered classes in approximate LIFO order, so as to
// avoid unnecessary reflection.
$this->foundClasses = array_merge($newClasses, $this->foundClasses);
$this->declaredClassesPointer = count(get_declared_classes());
}
// The test class's name must match the filename, either in full, or as
// a PEAR/PSR-0 prefixed short name ('NameSpace_ShortName'), or as a
// PSR-1 local short name ('NameSpace\ShortName'). The comparison must be
// anchored to prevent false-positive matches (e.g., 'OtherShortName').
$shortName = basename($filename, '.php');
$shortNameRegEx = '/(?:^|_|\\\\)' . preg_quote($shortName, '/') . '$/';
foreach ($this->foundClasses as $i => $className) {
if (preg_match($shortNameRegEx, $className)) {
try {
$class = new ReflectionClass($className);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
throw new Exception(
$e->getMessage(),
$e->getCode(),
$e
);
}
// @codeCoverageIgnoreEnd
if ($class->getFileName() == $filename) {
$newClasses = [$className];
unset($this->foundClasses[$i]);
break;
}
}
}
foreach ($newClasses as $className) {
try {
$class = new ReflectionClass($className);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
throw new Exception(
$e->getMessage(),
$e->getCode(),
$e
);
}
// @codeCoverageIgnoreEnd
if (dirname($class->getFileName()) === __DIR__) {
continue;
}
if ($class->isAbstract() && $class->isSubclassOf(TestCase::class)) {
$this->addWarning(
sprintf(
'Abstract test case classes with "Test" suffix are deprecated (%s)',
$class->getName()
)
);
}
if (!$class->isAbstract()) {
if ($class->hasMethod(BaseTestRunner::SUITE_METHODNAME)) {
try {
$method = $class->getMethod(
BaseTestRunner::SUITE_METHODNAME
);
// @codeCoverageIgnoreStart
} catch (ReflectionException $e) {
throw new Exception(
$e->getMessage(),
$e->getCode(),
$e
);
}
// @codeCoverageIgnoreEnd
if ($method->isStatic()) {
$this->addTest($method->invoke(null, $className));
}
} elseif ($class->implementsInterface(Test::class)) {
// Do we have modern namespacing ('Foo\Bar\WhizBangTest') or old-school namespacing ('Foo_Bar_WhizBangTest')?
$isPsr0 = (!$class->inNamespace()) && (strpos($class->getName(), '_') !== false);
$expectedClassName = $isPsr0 ? $className : $shortName;
if (($pos = strpos($expectedClassName, '.')) !== false) {
$expectedClassName = substr(
$expectedClassName,
0,
$pos
);
}
if ($class->getShortName() !== $expectedClassName) {
$this->addWarning(
sprintf(
"Test case class not matching filename is deprecated\n in %s\n Class name was '%s', expected '%s'",
$filename,
$class->getShortName(),
$expectedClassName
)
);
}
$this->addTestSuite($class);
}
}
}
if (count($this->tests) > ++$numTests) {
$this->addWarning(
sprintf(
"Multiple test case classes per file is deprecated\n in %s",
$filename
)
);
}
$this->numTests = -1;
}
/**
* Wrapper for addTestFile() that adds multiple test files.
*
* @throws Exception
*/
public function addTestFiles(iterable $fileNames): void
{
foreach ($fileNames as $filename) {
$this->addTestFile((string) $filename);
}
}
/**
* Counts the number of test cases that will be run by this test.
*
* @todo refactor usage of numTests in DefaultResultPrinter
*/
public function count(): int
{
$this->numTests = 0;
foreach ($this as $test) {
$this->numTests += count($test);
}
return $this->numTests;
}
/**
* Returns the name of the suite.
*/
public function getName(): string
{
return $this->name;
}
/**
* Returns the test groups of the suite.
*
* @psalm-return list
* // match first parameter with value 2
* $b->with(2);
* // match first parameter with value 'smock' and second identical to 42
* $b->with('smock', new PHPUnit\Framework\Constraint\IsEqual(42));
*
*
* @return ParametersMatch
*/
public function with(...$arguments);
/**
* Sets a rule which allows any kind of parameters.
*
* Some examples:
*
* // match any number of parameters
* $b->withAnyParameters();
*
*
* @return ParametersMatch
*/
public function withAnyParameters();
}
phpunit/src/Framework/MockObject/MockMethodSet.php 0000644 00000002031 15024772100 0016210 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\MockObject;
use function array_key_exists;
use function array_values;
use function strtolower;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class MockMethodSet
{
/**
* @var MockMethod[]
*/
private $methods = [];
public function addMethods(MockMethod ...$methods): void
{
foreach ($methods as $method) {
$this->methods[strtolower($method->getName())] = $method;
}
}
/**
* @return MockMethod[]
*/
public function asArray(): array
{
return array_values($this->methods);
}
public function hasMethod(string $methodName): bool
{
return array_key_exists(strtolower($methodName), $this->methods);
}
}
phpunit/src/Framework/MockObject/Api/Method.php 0000644 00000001337 15024772100 0015443 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\MockObject;
use function call_user_func_array;
use function func_get_args;
use PHPUnit\Framework\MockObject\Rule\AnyInvokedCount;
/**
* @internal This trait is not covered by the backward compatibility promise for PHPUnit
*/
trait Method
{
public function method()
{
$expects = $this->expects(new AnyInvokedCount);
return call_user_func_array(
[$expects, 'method'],
func_get_args()
);
}
}
phpunit/src/Framework/MockObject/Api/Api.php 0000644 00000005607 15024772100 0014740 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\MockObject;
use PHPUnit\Framework\MockObject\Builder\InvocationMocker as InvocationMockerBuilder;
use PHPUnit\Framework\MockObject\Rule\InvocationOrder;
/**
* @internal This trait is not covered by the backward compatibility promise for PHPUnit
*/
trait Api
{
/**
* @var ConfigurableMethod[]
*/
private static $__phpunit_configurableMethods;
/**
* @var object
*/
private $__phpunit_originalObject;
/**
* @var bool
*/
private $__phpunit_returnValueGeneration = true;
/**
* @var InvocationHandler
*/
private $__phpunit_invocationMocker;
/** @noinspection MagicMethodsValidityInspection */
public static function __phpunit_initConfigurableMethods(ConfigurableMethod ...$configurableMethods): void
{
if (isset(static::$__phpunit_configurableMethods)) {
throw new ConfigurableMethodsAlreadyInitializedException(
'Configurable methods is already initialized and can not be reinitialized'
);
}
static::$__phpunit_configurableMethods = $configurableMethods;
}
/** @noinspection MagicMethodsValidityInspection */
public function __phpunit_setOriginalObject($originalObject): void
{
$this->__phpunit_originalObject = $originalObject;
}
/** @noinspection MagicMethodsValidityInspection */
public function __phpunit_setReturnValueGeneration(bool $returnValueGeneration): void
{
$this->__phpunit_returnValueGeneration = $returnValueGeneration;
}
/** @noinspection MagicMethodsValidityInspection */
public function __phpunit_getInvocationHandler(): InvocationHandler
{
if ($this->__phpunit_invocationMocker === null) {
$this->__phpunit_invocationMocker = new InvocationHandler(
static::$__phpunit_configurableMethods,
$this->__phpunit_returnValueGeneration
);
}
return $this->__phpunit_invocationMocker;
}
/** @noinspection MagicMethodsValidityInspection */
public function __phpunit_hasMatchers(): bool
{
return $this->__phpunit_getInvocationHandler()->hasMatchers();
}
/** @noinspection MagicMethodsValidityInspection */
public function __phpunit_verify(bool $unsetInvocationMocker = true): void
{
$this->__phpunit_getInvocationHandler()->verify();
if ($unsetInvocationMocker) {
$this->__phpunit_invocationMocker = null;
}
}
public function expects(InvocationOrder $matcher): InvocationMockerBuilder
{
return $this->__phpunit_getInvocationHandler()->expects($matcher);
}
}
phpunit/src/Framework/MockObject/Generator/mocked_class.tpl 0000644 00000000220 15024772100 0020065 0 ustar 00 declare(strict_types=1);
{prologue}{class_declaration}
{
use \PHPUnit\Framework\MockObject\Api;{method}{clone}
{mocked_methods}}{epilogue}
phpunit/src/Framework/MockObject/Generator/wsdl_method.tpl 0000644 00000000074 15024772100 0017756 0 ustar 00
public function {method_name}({arguments})
{
}
phpunit/src/Framework/MockObject/Generator/mocked_static_method.tpl 0000644 00000000356 15024772100 0021621 0 ustar 00
{modifier} function {reference}{method_name}({arguments_decl}){return_declaration}
{
throw new \PHPUnit\Framework\MockObject\BadMethodCallException('Static method "{method_name}" cannot be invoked on mock object');
}
phpunit/src/Framework/MockObject/Generator/proxied_method_never_or_void.tpl 0000644 00000001566 15024772100 0023406 0 ustar 00
{modifier} function {reference}{method_name}({arguments_decl}){return_declaration}
{
$__phpunit_arguments = [{arguments_call}];
$__phpunit_count = func_num_args();
if ($__phpunit_count > {arguments_count}) {
$__phpunit_arguments_tmp = func_get_args();
for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) {
$__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i];
}
}
$this->__phpunit_getInvocationHandler()->invoke(
new \PHPUnit\Framework\MockObject\Invocation(
'{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true
)
);
call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments);
}
phpunit/src/Framework/MockObject/Generator/intersection.tpl 0000644 00000000114 15024772100 0020146 0 ustar 00 declare(strict_types=1);
interface {intersection} extends {interfaces}
{
}
phpunit/src/Framework/MockObject/Generator/mocked_method_never_or_void.tpl 0000644 00000001417 15024772100 0023171 0 ustar 00
{modifier} function {reference}{method_name}({arguments_decl}){return_declaration}
{{deprecation}
$__phpunit_arguments = [{arguments_call}];
$__phpunit_count = func_num_args();
if ($__phpunit_count > {arguments_count}) {
$__phpunit_arguments_tmp = func_get_args();
for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) {
$__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i];
}
}
$this->__phpunit_getInvocationHandler()->invoke(
new \PHPUnit\Framework\MockObject\Invocation(
'{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}
)
);
}
phpunit/src/Framework/MockObject/Generator/wsdl_class.tpl 0000644 00000000315 15024772100 0017601 0 ustar 00 declare(strict_types=1);
{namespace}class {class_name} extends \SoapClient
{
public function __construct($wsdl, array $options)
{
parent::__construct('{wsdl}', $options);
}
{methods}}
phpunit/src/Framework/MockObject/Generator/mocked_method.tpl 0000644 00000001506 15024772100 0020250 0 ustar 00
{modifier} function {reference}{method_name}({arguments_decl}){return_declaration}
{{deprecation}
$__phpunit_arguments = [{arguments_call}];
$__phpunit_count = func_num_args();
if ($__phpunit_count > {arguments_count}) {
$__phpunit_arguments_tmp = func_get_args();
for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) {
$__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i];
}
}
$__phpunit_result = $this->__phpunit_getInvocationHandler()->invoke(
new \PHPUnit\Framework\MockObject\Invocation(
'{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}
)
);
return $__phpunit_result;
}
phpunit/src/Framework/MockObject/Generator/proxied_method.tpl 0000644 00000001575 15024772100 0020466 0 ustar 00
{modifier} function {reference}{method_name}({arguments_decl}){return_declaration}
{
$__phpunit_arguments = [{arguments_call}];
$__phpunit_count = func_num_args();
if ($__phpunit_count > {arguments_count}) {
$__phpunit_arguments_tmp = func_get_args();
for ($__phpunit_i = {arguments_count}; $__phpunit_i < $__phpunit_count; $__phpunit_i++) {
$__phpunit_arguments[] = $__phpunit_arguments_tmp[$__phpunit_i];
}
}
$this->__phpunit_getInvocationHandler()->invoke(
new \PHPUnit\Framework\MockObject\Invocation(
'{class_name}', '{method_name}', $__phpunit_arguments, '{return_type}', $this, {clone_arguments}, true
)
);
return call_user_func_array(array($this->__phpunit_originalObject, "{method_name}"), $__phpunit_arguments);
}
phpunit/src/Framework/MockObject/Generator/trait_class.tpl 0000644 00000000121 15024772100 0017746 0 ustar 00 declare(strict_types=1);
{prologue}class {class_name}
{
use {trait_name};
}
phpunit/src/Framework/MockObject/Generator/deprecation.tpl 0000644 00000000073 15024772100 0017741 0 ustar 00
@trigger_error({deprecation}, E_USER_DEPRECATED);
phpunit/src/Framework/MockObject/Invocation.php 0000644 00000017400 15024772100 0015621 0 ustar 00
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\Framework\MockObject;
use function array_map;
use function explode;
use function get_class;
use function implode;
use function in_array;
use function interface_exists;
use function is_object;
use function sprintf;
use function strpos;
use function strtolower;
use function substr;
use Doctrine\Instantiator\Instantiator;
use PHPUnit\Framework\SelfDescribing;
use PHPUnit\Util\Cloner;
use SebastianBergmann\Exporter\Exporter;
use stdClass;
use Throwable;
/**
* @internal This class is not covered by the backward compatibility promise for PHPUnit
*/
final class Invocation implements SelfDescribing
{
/**
* @var string
*/
private $className;
/**
* @var string
*/
private $methodName;
/**
* @var array
*/
private $parameters;
/**
* @var string
*/
private $returnType;
/**
* @var bool
*/
private $isReturnTypeNullable = false;
/**
* @var bool
*/
private $proxiedCall;
/**
* @var object
*/
private $object;
public function __construct(string $className, string $methodName, array $parameters, string $returnType, object $object, bool $cloneObjects = false, bool $proxiedCall = false)
{
$this->className = $className;
$this->methodName = $methodName;
$this->parameters = $parameters;
$this->object = $object;
$this->proxiedCall = $proxiedCall;
if (strtolower($methodName) === '__tostring') {
$returnType = 'string';
}
if (strpos($returnType, '?') === 0) {
$returnType = substr($returnType, 1);
$this->isReturnTypeNullable = true;
}
$this->returnType = $returnType;
if (!$cloneObjects) {
return;
}
foreach ($this->parameters as $key => $value) {
if (is_object($value)) {
$this->parameters[$key] = Cloner::clone($value);
}
}
}
public function getClassName(): string
{
return $this->className;
}
public function getMethodName(): string
{
return $this->methodName;
}
public function getParameters(): array
{
return $this->parameters;
}
/**
* @throws RuntimeException
*
* @return mixed Mocked return value
*/
public function generateReturnValue()
{
if ($this->isReturnTypeNullable || $this->proxiedCall) {
return null;
}
$intersection = false;
$union = false;
$unionContainsIntersections = false;
if (strpos($this->returnType, '|') !== false) {
$types = explode('|', $this->returnType);
$union = true;
if (strpos($this->returnType, '(') !== false) {
$unionContainsIntersections = true;
}
} elseif (strpos($this->returnType, '&') !== false) {
$types = explode('&', $this->returnType);
$intersection = true;
} else {
$types = [$this->returnType];
}
$types = array_map('strtolower', $types);
if (!$intersection && !$unionContainsIntersections) {
if (in_array('', $types, true) ||
in_array('null', $types, true) ||
in_array('mixed', $types, true) ||
in_array('void', $types, true)) {
return null;
}
if (in_array('true', $types, true)) {
return true;
}
if (in_array('false', $types, true) ||
in_array('bool', $types, true)) {
return false;
}
if (in_array('float', $types, true)) {
return 0.0;
}
if (in_array('int', $types, true)) {
return 0;
}
if (in_array('string', $types, true)) {
return '';
}
if (in_array('array', $types, true)) {
return [];
}
if (in_array('static', $types, true)) {
try {
return (new Instantiator)->instantiate(get_class($this->object));
} catch (Throwable $t) {
throw new RuntimeException(
$t->getMessage(),
(int) $t->getCode(),
$t
);
}
}
if (in_array('object', $types, true)) {
return new stdClass;
}
if (in_array('callable', $types, true) ||
in_array('closure', $types, true)) {
return static function (): void
{
};
}
if (in_array('traversable', $types, true) ||
in_array('generator', $types, true) ||
in_array('iterable', $types, true)) {
$generator = static function (): \Generator
{
yield from [];
};
return $generator();
}
if (!$union) {
try {
return (new Generator)->getMock($this->returnType, [], [], '', false);
} catch (Throwable $t) {
if ($t instanceof Exception) {
throw $t;
}
throw new RuntimeException(
$t->getMessage(),
(int) $t->getCode(),
$t
);
}
}
}
if ($intersection && $this->onlyInterfaces($types)) {
try {
return (new Generator)->getMockForInterfaces($types);
} catch (Throwable $t) {
throw new RuntimeException(
sprintf(
'Return value for %s::%s() cannot be generated: %s',
$this->className,
$this->methodName,
$t->getMessage(),
),
(int) $t->getCode(),
);
}
}
$reason = '';
if ($union) {
$reason = ' because the declared return type is a union';
} elseif ($intersection) {
$reason = ' because the declared return type is an intersection';
}
throw new RuntimeException(
sprintf(
'Return value for %s::%s() cannot be generated%s, please configure a return value for this method',
$this->className,
$this->methodName,
$reason
)
);
}
public function toString(): string
{
$exporter = new Exporter;
return sprintf(
'%s::%s(%s)%s',
$this->className,
$this->methodName,
implode(
', ',
array_map(
[$exporter, 'shortenedExport'],
$this->parameters
)
),
$this->returnType ? sprintf(': %s', $this->returnType) : ''
);
}
public function getObject(): object
{
return $this->object;
}
/**
* @psalm-param non-empty-listCovered by small (and larger) testsCovered by medium (and large) testsCovered by large tests (and tests of unknown size)Not coveredNot coverable
', 'structure' => '', ] ); $template->renderTo($file . '.html'); if ($this->hasBranchCoverage) { $template->setVar( [ 'items' => $this->renderItems($node), 'lines' => $this->renderSourceWithBranchCoverage($node), 'legend' => 'Fully coveredPartially coveredNot covered
', 'structure' => $this->renderBranchStructure($node), ] ); $template->renderTo($file . '_branch.html'); $template->setVar( [ 'items' => $this->renderItems($node), 'lines' => $this->renderSourceWithPathCoverage($node), 'legend' => 'Fully coveredPartially coveredNot covered
', 'structure' => $this->renderPathStructure($node), ] ); $template->renderTo($file . '_path.html'); } } private function renderItems(FileNode $node): string { $templateName = $this->templatePath . ($this->hasBranchCoverage ? 'file_item_branch.html' : 'file_item.html'); $template = new Template($templateName, '{{', '}}'); $methodTemplateName = $this->templatePath . ($this->hasBranchCoverage ? 'method_item_branch.html' : 'method_item.html'); $methodItemTemplate = new Template( $methodTemplateName, '{{', '}}' ); $items = $this->renderItemTemplate( $template, [ 'name' => 'Total', 'numClasses' => $node->numberOfClassesAndTraits(), 'numTestedClasses' => $node->numberOfTestedClassesAndTraits(), 'numMethods' => $node->numberOfFunctionsAndMethods(), 'numTestedMethods' => $node->numberOfTestedFunctionsAndMethods(), 'linesExecutedPercent' => $node->percentageOfExecutedLines()->asFloat(), 'linesExecutedPercentAsString' => $node->percentageOfExecutedLines()->asString(), 'numExecutedLines' => $node->numberOfExecutedLines(), 'numExecutableLines' => $node->numberOfExecutableLines(), 'branchesExecutedPercent' => $node->percentageOfExecutedBranches()->asFloat(), 'branchesExecutedPercentAsString' => $node->percentageOfExecutedBranches()->asString(), 'numExecutedBranches' => $node->numberOfExecutedBranches(), 'numExecutableBranches' => $node->numberOfExecutableBranches(), 'pathsExecutedPercent' => $node->percentageOfExecutedPaths()->asFloat(), 'pathsExecutedPercentAsString' => $node->percentageOfExecutedPaths()->asString(), 'numExecutedPaths' => $node->numberOfExecutedPaths(), 'numExecutablePaths' => $node->numberOfExecutablePaths(), 'testedMethodsPercent' => $node->percentageOfTestedFunctionsAndMethods()->asFloat(), 'testedMethodsPercentAsString' => $node->percentageOfTestedFunctionsAndMethods()->asString(), 'testedClassesPercent' => $node->percentageOfTestedClassesAndTraits()->asFloat(), 'testedClassesPercentAsString' => $node->percentageOfTestedClassesAndTraits()->asString(), 'crap' => 'CRAP', ] ); $items .= $this->renderFunctionItems( $node->functions(), $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->traits(), $template, $methodItemTemplate ); $items .= $this->renderTraitOrClassItems( $node->classes(), $template, $methodItemTemplate ); return $items; } private function renderTraitOrClassItems(array $items, Template $template, Template $methodItemTemplate): string { $buffer = ''; if (empty($items)) { return $buffer; } foreach ($items as $name => $item) { $numMethods = 0; $numTestedMethods = 0; foreach ($item['methods'] as $method) { if ($method['executableLines'] > 0) { $numMethods++; if ($method['executedLines'] === $method['executableLines']) { $numTestedMethods++; } } } if ($item['executableLines'] > 0) { $numClasses = 1; $numTestedClasses = $numTestedMethods === $numMethods ? 1 : 0; $linesExecutedPercentAsString = Percentage::fromFractionAndTotal( $item['executedLines'], $item['executableLines'] )->asString(); $branchesExecutedPercentAsString = Percentage::fromFractionAndTotal( $item['executedBranches'], $item['executableBranches'] )->asString(); $pathsExecutedPercentAsString = Percentage::fromFractionAndTotal( $item['executedPaths'], $item['executablePaths'] )->asString(); } else { $numClasses = 0; $numTestedClasses = 0; $linesExecutedPercentAsString = 'n/a'; $branchesExecutedPercentAsString = 'n/a'; $pathsExecutedPercentAsString = 'n/a'; } $testedMethodsPercentage = Percentage::fromFractionAndTotal( $numTestedMethods, $numMethods ); $testedClassesPercentage = Percentage::fromFractionAndTotal( $numTestedMethods === $numMethods ? 1 : 0, 1 ); $buffer .= $this->renderItemTemplate( $template, [ 'name' => $this->abbreviateClassName($name), 'numClasses' => $numClasses, 'numTestedClasses' => $numTestedClasses, 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => Percentage::fromFractionAndTotal( $item['executedLines'], $item['executableLines'], )->asFloat(), 'linesExecutedPercentAsString' => $linesExecutedPercentAsString, 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'branchesExecutedPercent' => Percentage::fromFractionAndTotal( $item['executedBranches'], $item['executableBranches'], )->asFloat(), 'branchesExecutedPercentAsString' => $branchesExecutedPercentAsString, 'numExecutedBranches' => $item['executedBranches'], 'numExecutableBranches' => $item['executableBranches'], 'pathsExecutedPercent' => Percentage::fromFractionAndTotal( $item['executedPaths'], $item['executablePaths'] )->asFloat(), 'pathsExecutedPercentAsString' => $pathsExecutedPercentAsString, 'numExecutedPaths' => $item['executedPaths'], 'numExecutablePaths' => $item['executablePaths'], 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), 'testedClassesPercent' => $testedClassesPercentage->asFloat(), 'testedClassesPercentAsString' => $testedClassesPercentage->asString(), 'crap' => $item['crap'], ] ); foreach ($item['methods'] as $method) { $buffer .= $this->renderFunctionOrMethodItem( $methodItemTemplate, $method, ' ' ); } } return $buffer; } private function renderFunctionItems(array $functions, Template $template): string { if (empty($functions)) { return ''; } $buffer = ''; foreach ($functions as $function) { $buffer .= $this->renderFunctionOrMethodItem( $template, $function ); } return $buffer; } private function renderFunctionOrMethodItem(Template $template, array $item, string $indent = ''): string { $numMethods = 0; $numTestedMethods = 0; if ($item['executableLines'] > 0) { $numMethods = 1; if ($item['executedLines'] === $item['executableLines']) { $numTestedMethods = 1; } } $executedLinesPercentage = Percentage::fromFractionAndTotal( $item['executedLines'], $item['executableLines'] ); $executedBranchesPercentage = Percentage::fromFractionAndTotal( $item['executedBranches'], $item['executableBranches'] ); $executedPathsPercentage = Percentage::fromFractionAndTotal( $item['executedPaths'], $item['executablePaths'] ); $testedMethodsPercentage = Percentage::fromFractionAndTotal( $numTestedMethods, 1 ); return $this->renderItemTemplate( $template, [ 'name' => sprintf( '%s%s', $indent, $item['startLine'], htmlspecialchars($item['signature'], $this->htmlSpecialCharsFlags), $item['functionName'] ?? $item['methodName'] ), 'numMethods' => $numMethods, 'numTestedMethods' => $numTestedMethods, 'linesExecutedPercent' => $executedLinesPercentage->asFloat(), 'linesExecutedPercentAsString' => $executedLinesPercentage->asString(), 'numExecutedLines' => $item['executedLines'], 'numExecutableLines' => $item['executableLines'], 'branchesExecutedPercent' => $executedBranchesPercentage->asFloat(), 'branchesExecutedPercentAsString' => $executedBranchesPercentage->asString(), 'numExecutedBranches' => $item['executedBranches'], 'numExecutableBranches' => $item['executableBranches'], 'pathsExecutedPercent' => $executedPathsPercentage->asFloat(), 'pathsExecutedPercentAsString' => $executedPathsPercentage->asString(), 'numExecutedPaths' => $item['executedPaths'], 'numExecutablePaths' => $item['executablePaths'], 'testedMethodsPercent' => $testedMethodsPercentage->asFloat(), 'testedMethodsPercentAsString' => $testedMethodsPercentage->asString(), 'crap' => $item['crap'], ] ); } private function renderSourceWithLineCoverage(FileNode $node): string { $linesTemplate = new Template($this->templatePath . 'lines.html.dist', '{{', '}}'); $singleLineTemplate = new Template($this->templatePath . 'line.html.dist', '{{', '}}'); $coverageData = $node->lineCoverageData(); $testData = $node->testData(); $codeLines = $this->loadFile($node->pathAsString()); $lines = ''; $i = 1; foreach ($codeLines as $line) { $trClass = ''; $popoverContent = ''; $popoverTitle = ''; if (array_key_exists($i, $coverageData)) { $numTests = ($coverageData[$i] ? count($coverageData[$i]) : 0); if ($coverageData[$i] === null) { $trClass = 'warning'; } elseif ($numTests === 0) { $trClass = 'danger'; } else { if ($numTests > 1) { $popoverTitle = $numTests . ' tests cover line ' . $i; } else { $popoverTitle = '1 test covers line ' . $i; } $lineCss = 'covered-by-large-tests'; $popoverContent = '' . count($methodData['paths']) . ' is too many paths to sensibly render, consider refactoring your code to bring this number down.
'; continue; } foreach ($methodData['paths'] as $path) { $pathStructure .= $this->renderPathLines($path, $methodData['branches'], $codeLines, $testData); } if ($pathStructure !== '') { $paths .= 'Code Coverage |
||||||||||||||||
Lines |
Branches |
Paths |
Functions and Methods |
Classes and Traits |
Below are the source code lines that represent each code branch as identified by Xdebug. Please note a branch is not
necessarily coterminous with a line, a line may contain multiple branches and therefore show up more than once.
Please also be aware that some branches may be implicit rather than explicit, e.g. an if
statement
always has an else
as part of its logical flow even if you didn't write one.