mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 15:57:21 +01:00
Compare commits
77 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| dd2921fd26 | |||
| 977dbb5bcb | |||
| 49de462250 | |||
| 95e8add29b | |||
| b682fe631d | |||
| d32a648af5 | |||
| 50e9978dc3 | |||
| 17d407a26a | |||
| cdc3bd3f45 | |||
| 95b4192c0d | |||
| 4c911cd0eb | |||
| 7408999b0e | |||
| bb13bdaa80 | |||
| 10e7cbe006 | |||
| 0ad232e9de | |||
| 574cd11a40 | |||
| c04d6d946d | |||
| 36c2a985a6 | |||
| 91eff755fd | |||
| c05d287fcc | |||
| ea0be9e7a4 | |||
| 838ac273ab | |||
| 296e1c37e8 | |||
| 3117f11fae | |||
| 294c41f0dc | |||
| 60afbb2c20 | |||
| 19a45c856e | |||
| 3b784060b8 | |||
| dd5a11a61f | |||
| 9133b88d65 | |||
| 93b9afbd27 | |||
| 6c6bba2a04 | |||
| dd24b7e347 | |||
| ce896b9c83 | |||
| a3c1a61b59 | |||
| 98c62f4b1d | |||
| dd78cc9a50 | |||
| 9027411004 | |||
| 9394aa4649 | |||
| 88dc74bbe4 | |||
| 7023cec432 | |||
| 99d6fb9f5f | |||
| c9f723530d | |||
| 3205b571b0 | |||
| 2c4aef5272 | |||
| 4a45a7cc6b | |||
| ea8ab88056 | |||
| 564a21badd | |||
| 41ce87450f | |||
| fb0eef4200 | |||
| 819da37b89 | |||
| 8eb9c408a9 | |||
| 1f10b46402 | |||
| 7bb12b73e8 | |||
| b8103697c9 | |||
| ca3f8b5702 | |||
| daa01ea44b | |||
| 26d577f9c5 | |||
| 43fb711251 | |||
| 567af55a19 | |||
| c6a2e3b4d0 | |||
| a10428efe6 | |||
| 1440637e41 | |||
| 14cee66dfd | |||
| d048d60d04 | |||
| 1b9162151c | |||
| 885c9f1f06 | |||
| 90efcc8a8a | |||
| 7d35ee9998 | |||
| 584a7ac8a5 | |||
| f21e45ae64 | |||
| c7d26a27b6 | |||
| 54f9397f47 | |||
| ff44589572 | |||
| 53333b56ab | |||
| 99500d0cae | |||
| 7eb5478c42 |
4
.gitattributes
vendored
4
.gitattributes
vendored
@ -3,13 +3,11 @@
|
||||
/tests export-ignore
|
||||
/scripts export-ignore
|
||||
/.github export-ignore
|
||||
/.php_cs export-ignore
|
||||
/.php-cs-fixer.dist.php export-ignore
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.travis.yml export-ignore
|
||||
phpstan.neon export-ignore
|
||||
rector.yaml export-ignore
|
||||
phpunit.xml export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -4,7 +4,8 @@ composer.lock
|
||||
/vendor/
|
||||
coverage.xml
|
||||
.phpunit.result.cache
|
||||
.php_cs.cache
|
||||
/.php-cs-fixer.php
|
||||
.php-cs-fixer.cache
|
||||
.temp/coverage.php
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
@ -6,7 +6,7 @@ $finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'scripts')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'stubs')
|
||||
->in(__DIR__ . DIRECTORY_SEPARATOR . 'src')
|
||||
->append(['.php_cs']);
|
||||
->append(['.php-cs-fixer.dist.php']);
|
||||
|
||||
$rules = [
|
||||
'@Symfony' => true,
|
||||
@ -25,7 +25,7 @@ $rules = [
|
||||
|
||||
$rules['increment_style'] = ['style' => 'post'];
|
||||
|
||||
return PhpCsFixer\Config::create()
|
||||
return (new PhpCsFixer\Config())
|
||||
->setUsingCache(true)
|
||||
->setRules($rules)
|
||||
->setFinder($finder);
|
||||
36
CHANGELOG.md
36
CHANGELOG.md
@ -4,6 +4,42 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [v1.4.0 (2021-06-10)](https://github.com/pestphp/pest/compare/v1.3.2...v1.4.0)
|
||||
### Added
|
||||
- Support for multiple datasets (Matrix) on the `with` method ([#303](https://github.com/pestphp/pest/pull/303))
|
||||
- Support for incompleted tests ([49de462](https://github.com/pestphp/pest/commit/49de462250cf9f65f09e13eaf6dcc0e06865b930))
|
||||
|
||||
## [v1.3.2 (2021-06-07)](https://github.com/pestphp/pest/compare/v1.3.1...v1.3.2)
|
||||
### Fixed
|
||||
- Test cases with the @ symbol in the directory fail ([#308](https://github.com/pestphp/pest/pull/308))
|
||||
|
||||
## [v1.3.1 (2021-06-06)](https://github.com/pestphp/pest/compare/v1.3.0...v1.3.1)
|
||||
### Added
|
||||
- Added for PHPUnit 9.5.5 ([#310](https://github.com/pestphp/pest/pull/310))
|
||||
|
||||
### Changed
|
||||
- Lock minimum Pest plugin versions ([#306](https://github.com/pestphp/pest/pull/306))
|
||||
|
||||
## [v1.3.0 (2021-05-23)](https://github.com/pestphp/pest/compare/v1.2.1...v1.3.0)
|
||||
### Added
|
||||
- Named datasets no longer show the arguments ([#302](https://github.com/pestphp/pest/pull/302))
|
||||
|
||||
### Fixed
|
||||
- Wraps global functions within `function_exists` ([#300](https://github.com/pestphp/pest/pull/300))
|
||||
|
||||
## [v1.2.1 (2021-05-14)](https://github.com/pestphp/pest/compare/v1.2.0...v1.2.1)
|
||||
### Fixed
|
||||
- Laravel commands failing with new `--test-directory` option ([#297](https://github.com/pestphp/pest/pull/297))
|
||||
|
||||
## [v1.2.0 (2021-05-13)](https://github.com/pestphp/pest/compare/v1.1.0...v1.2.0)
|
||||
### Added
|
||||
- Adds JUnit / Infection support ([#291](https://github.com/pestphp/pest/pull/291))
|
||||
- `--test-directory` command line option ([#283](https://github.com/pestphp/pest/pull/283))
|
||||
|
||||
## [v1.1.0 (2021-05-02)](https://github.com/pestphp/pest/compare/v1.0.5...v1.1.0)
|
||||
### Added
|
||||
- Possibility of "hooks" being added using the "uses" function ([#282](https://github.com/pestphp/pest/pull/282))
|
||||
|
||||
## [v1.0.5 (2021-03-31)](https://github.com/pestphp/pest/compare/v1.0.4...v1.0.5)
|
||||
### Added
|
||||
- Add `--browse` option to `pest:dusk` command ([#280](https://github.com/pestphp/pest/pull/280))
|
||||
|
||||
@ -23,5 +23,6 @@ We would like to extend our thanks to the following sponsors for funding Pest de
|
||||
|
||||
- **[Scout APM](https://scoutapm.com)**
|
||||
- **[Akaunting](https://akaunting.com)**
|
||||
- **[Meema](https://meema.io/)**
|
||||
|
||||
Pest was created by **[Nuno Maduro](https://twitter.com/enunomaduro)** under the **[Sponsorware license](https://github.com/sponsorware/docs)**. It got open-sourced and is now licensed under the **[MIT license](https://opensource.org/licenses/MIT)**.
|
||||
|
||||
14
bin/pest
14
bin/pest
@ -29,10 +29,11 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
// get $rootPath based on $autoloadPath
|
||||
$rootPath = dirname($autoloadPath, 2);
|
||||
$argv = new ArgvInput();
|
||||
|
||||
$testSuite = TestSuite::getInstance($rootPath);
|
||||
$testSuite = TestSuite::getInstance($rootPath, $argv->getParameterOption('--test-directory', 'tests'));
|
||||
|
||||
$isDecorated = (new ArgvInput())->getParameterOption('--colors', 'always') !== 'never';
|
||||
$isDecorated = $argv->getParameterOption('--colors', 'always') !== 'never';
|
||||
$output = new ConsoleOutput(ConsoleOutput::VERBOSITY_NORMAL, $isDecorated);
|
||||
|
||||
$container = Container::getInstance();
|
||||
@ -41,5 +42,14 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
ValidatesEnvironment::in($testSuite);
|
||||
|
||||
// lets remove any arguments that PHPUnit does not understand
|
||||
if ($argv->hasParameterOption('--test-directory')) {
|
||||
foreach ($_SERVER['argv'] as $key => $value) {
|
||||
if (strpos($value, '--test-directory') !== false) {
|
||||
unset($_SERVER['argv'][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exit($container->get(Command::class)->run($_SERVER['argv']));
|
||||
})();
|
||||
|
||||
@ -21,9 +21,9 @@
|
||||
"nunomaduro/collision": "^5.0",
|
||||
"pestphp/pest-plugin": "^1.0",
|
||||
"pestphp/pest-plugin-coverage": "^1.0",
|
||||
"pestphp/pest-plugin-expectations": "^1.0",
|
||||
"pestphp/pest-plugin-init": "^1.0",
|
||||
"phpunit/phpunit": ">= 9.3.7 <= 9.5.4"
|
||||
"pestphp/pest-plugin-expectations": "^1.6",
|
||||
"pestphp/pest-plugin-init": "^1.1",
|
||||
"phpunit/phpunit": ">= 9.3.7 <= 9.5.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@ -46,7 +46,6 @@
|
||||
"illuminate/console": "^8.32.1",
|
||||
"illuminate/support": "^8.32.1",
|
||||
"laravel/dusk": "^6.13.0",
|
||||
"mockery/mockery": "^1.4.3",
|
||||
"pestphp/pest-dev-tools": "dev-master"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
|
||||
@ -22,10 +22,13 @@ parameters:
|
||||
- "#Method Pest\\\\Support\\\\Reflection::getParameterClassName\\(\\) has a nullable return type declaration.#"
|
||||
-
|
||||
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getName\(\)#'
|
||||
path: src/TeamCity.php
|
||||
path: src/Logging
|
||||
-
|
||||
message: '#invalid typehint type Pest\\Concerns\\TestCase#'
|
||||
path: src/TeamCity.php
|
||||
path: src/Logging
|
||||
-
|
||||
message: '#is not subtype of native type PHPUnit\\Framework\\Test#'
|
||||
path: src/TeamCity.php
|
||||
path: src/Logging
|
||||
-
|
||||
message: '#Call to an undefined method PHPUnit\\Framework\\Test::getPrintableTestCaseName\(\)#'
|
||||
path: src/Logging
|
||||
|
||||
@ -5,7 +5,8 @@ declare(strict_types=1);
|
||||
namespace Pest\Actions;
|
||||
|
||||
use NunoMaduro\Collision\Adapters\Phpunit\Printer;
|
||||
use Pest\TeamCity;
|
||||
use Pest\Logging\JUnit;
|
||||
use Pest\Logging\TeamCity;
|
||||
use PHPUnit\TextUI\DefaultResultPrinter;
|
||||
|
||||
/**
|
||||
@ -32,6 +33,14 @@ final class AddsDefaults
|
||||
$arguments[self::PRINTER] = new TeamCity($arguments['verbose'] ?? false, $arguments['colors'] ?? DefaultResultPrinter::COLOR_ALWAYS);
|
||||
}
|
||||
|
||||
// Load our junit logger instead.
|
||||
if (array_key_exists('junitLogfile', $arguments)) {
|
||||
$arguments['listeners'][] = new JUnit(
|
||||
$arguments['junitLogfile']
|
||||
);
|
||||
unset($arguments['junitLogfile']);
|
||||
}
|
||||
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace Pest\Actions;
|
||||
|
||||
use Pest\Support\Str;
|
||||
use PHPUnit\TextUI\Configuration\Configuration;
|
||||
use function Pest\testDirectory;
|
||||
use PHPUnit\Util\FileLoader;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
@ -33,7 +33,7 @@ final class LoadStructure
|
||||
*/
|
||||
public static function in(string $rootPath): void
|
||||
{
|
||||
$testsPath = $rootPath . DIRECTORY_SEPARATOR . 'tests';
|
||||
$testsPath = $rootPath . DIRECTORY_SEPARATOR . testDirectory();
|
||||
|
||||
$load = function ($filename): bool {
|
||||
return file_exists($filename) && (bool) FileLoader::checkAndLoad($filename);
|
||||
|
||||
@ -5,10 +5,10 @@ declare(strict_types=1);
|
||||
namespace Pest\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Pest\Support\ChainableClosure;
|
||||
use Pest\Support\ExceptionTrace;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\ExecutionOrderDependency;
|
||||
use PHPUnit\Util\Test;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
@ -34,6 +34,38 @@ trait TestCase
|
||||
*/
|
||||
private $__test;
|
||||
|
||||
/**
|
||||
* Holds a global/shared beforeEach ("set up") closure if one has been
|
||||
* defined.
|
||||
*
|
||||
* @var Closure|null
|
||||
*/
|
||||
private $beforeEach = null;
|
||||
|
||||
/**
|
||||
* Holds a global/shared afterEach ("tear down") closure if one has been
|
||||
* defined.
|
||||
*
|
||||
* @var Closure|null
|
||||
*/
|
||||
private $afterEach = null;
|
||||
|
||||
/**
|
||||
* Holds a global/shared beforeAll ("set up before") closure if one has been
|
||||
* defined.
|
||||
*
|
||||
* @var Closure|null
|
||||
*/
|
||||
private static $beforeAll = null;
|
||||
|
||||
/**
|
||||
* Holds a global/shared afterAll ("tear down after") closure if one has
|
||||
* been defined.
|
||||
*
|
||||
* @var Closure|null
|
||||
*/
|
||||
private static $afterAll = null;
|
||||
|
||||
/**
|
||||
* Creates a new instance of the test case.
|
||||
*/
|
||||
@ -73,6 +105,68 @@ trait TestCase
|
||||
$this->setDependencies($tests);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shared/"global" before all test hook that will execute **before**
|
||||
* the test defined `beforeAll` hook(s).
|
||||
*/
|
||||
public function addBeforeAll(?Closure $hook): void
|
||||
{
|
||||
if (!$hook) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$beforeAll = (self::$beforeAll instanceof Closure)
|
||||
? ChainableClosure::fromStatic(self::$beforeAll, $hook)
|
||||
: $hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shared/"global" after all test hook that will execute **before**
|
||||
* the test defined `afterAll` hook(s).
|
||||
*/
|
||||
public function addAfterAll(?Closure $hook): void
|
||||
{
|
||||
if (!$hook) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::$afterAll = (self::$afterAll instanceof Closure)
|
||||
? ChainableClosure::fromStatic(self::$afterAll, $hook)
|
||||
: $hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shared/"global" before each test hook that will execute **before**
|
||||
* the test defined `beforeEach` hook.
|
||||
*/
|
||||
public function addBeforeEach(?Closure $hook): void
|
||||
{
|
||||
$this->addHook('beforeEach', $hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shared/"global" after each test hook that will execute **before**
|
||||
* the test defined `afterEach` hook.
|
||||
*/
|
||||
public function addAfterEach(?Closure $hook): void
|
||||
{
|
||||
$this->addHook('afterEach', $hook);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a shared/global hook and compose them if more than one is passed.
|
||||
*/
|
||||
private function addHook(string $property, ?Closure $hook): void
|
||||
{
|
||||
if (!$hook) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->{$property} = ($this->{$property} instanceof Closure)
|
||||
? ChainableClosure::from($this->{$property}, $hook)
|
||||
: $hook;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the test case name. Note that, in Pest
|
||||
* we ignore withDataset argument as the description
|
||||
@ -97,6 +191,10 @@ trait TestCase
|
||||
|
||||
$beforeAll = TestSuite::getInstance()->beforeAll->get(self::$__filename);
|
||||
|
||||
if (self::$beforeAll instanceof Closure) {
|
||||
$beforeAll = ChainableClosure::fromStatic(self::$beforeAll, $beforeAll);
|
||||
}
|
||||
|
||||
call_user_func(Closure::bind($beforeAll, null, self::class));
|
||||
}
|
||||
|
||||
@ -107,6 +205,10 @@ trait TestCase
|
||||
{
|
||||
$afterAll = TestSuite::getInstance()->afterAll->get(self::$__filename);
|
||||
|
||||
if (self::$afterAll instanceof Closure) {
|
||||
$afterAll = ChainableClosure::fromStatic(self::$afterAll, $afterAll);
|
||||
}
|
||||
|
||||
call_user_func(Closure::bind($afterAll, null, self::class));
|
||||
|
||||
parent::tearDownAfterClass();
|
||||
@ -123,6 +225,10 @@ trait TestCase
|
||||
|
||||
$beforeEach = TestSuite::getInstance()->beforeEach->get(self::$__filename);
|
||||
|
||||
if ($this->beforeEach instanceof Closure) {
|
||||
$beforeEach = ChainableClosure::from($this->beforeEach, $beforeEach);
|
||||
}
|
||||
|
||||
$this->__callClosure($beforeEach, func_get_args());
|
||||
}
|
||||
|
||||
@ -133,6 +239,10 @@ trait TestCase
|
||||
{
|
||||
$afterEach = TestSuite::getInstance()->afterEach->get(self::$__filename);
|
||||
|
||||
if ($this->afterEach instanceof Closure) {
|
||||
$afterEach = ChainableClosure::from($this->afterEach, $afterEach);
|
||||
}
|
||||
|
||||
$this->__callClosure($afterEach, func_get_args());
|
||||
|
||||
parent::tearDown();
|
||||
|
||||
@ -90,8 +90,6 @@ final class Command extends BaseCommand
|
||||
*/
|
||||
$this->arguments = AddsDefaults::to($this->arguments);
|
||||
|
||||
LoadStructure::in($this->testSuite->rootPath);
|
||||
|
||||
$testRunner = new TestRunner($this->arguments['loader']);
|
||||
$testSuite = $this->arguments['test'];
|
||||
|
||||
@ -127,6 +125,8 @@ final class Command extends BaseCommand
|
||||
*/
|
||||
public function run(array $argv, bool $exit = true): int
|
||||
{
|
||||
LoadStructure::in($this->testSuite->rootPath);
|
||||
|
||||
$result = parent::run($argv, false);
|
||||
|
||||
/*
|
||||
|
||||
@ -51,36 +51,34 @@ final class Datasets
|
||||
/**
|
||||
* Resolves the current dataset to an array value.
|
||||
*
|
||||
* @param Traversable<int|string, mixed>|Closure|iterable<int|string, mixed>|string|null $data
|
||||
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function resolve(string $description, $data): array
|
||||
public static function resolve(string $description, array $datasets): array
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
if (is_null($data) || empty($data)) {
|
||||
if (empty($datasets)) {
|
||||
return [$description => []];
|
||||
}
|
||||
|
||||
if (is_string($data)) {
|
||||
$data = self::get($data);
|
||||
}
|
||||
$datasets = self::processDatasets($datasets);
|
||||
|
||||
if (is_callable($data)) {
|
||||
$data = call_user_func($data);
|
||||
}
|
||||
|
||||
if ($data instanceof Traversable) {
|
||||
$data = iterator_to_array($data);
|
||||
}
|
||||
$datasetCombinations = self::getDataSetsCombinations($datasets);
|
||||
|
||||
$dataSetDescriptions = [];
|
||||
$dataSetValues = [];
|
||||
|
||||
foreach ($data as $key => $values) {
|
||||
$values = is_array($values) ? $values : [$values];
|
||||
foreach ($datasetCombinations as $datasetCombination) {
|
||||
$partialDescriptions = [];
|
||||
$values = [];
|
||||
|
||||
$dataSetDescriptions[] = $description . self::getDataSetDescription($key, $values);
|
||||
foreach ($datasetCombination as $dataset_data) {
|
||||
$partialDescriptions[] = $dataset_data['label'];
|
||||
$values = array_merge($values, $dataset_data['values']);
|
||||
}
|
||||
|
||||
$dataSetDescriptions[] = $description . ' with ' . implode(' / ', $partialDescriptions);
|
||||
$dataSetValues[] = $values;
|
||||
}
|
||||
|
||||
@ -103,6 +101,65 @@ final class Datasets
|
||||
return $namedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Closure|iterable<int|string, mixed>|string> $datasets
|
||||
*
|
||||
* @return array<array>
|
||||
*/
|
||||
private static function processDatasets(array $datasets): array
|
||||
{
|
||||
$processedDatasets = [];
|
||||
|
||||
foreach ($datasets as $index => $data) {
|
||||
$processedDataset = [];
|
||||
|
||||
if (is_string($data)) {
|
||||
$datasets[$index] = self::get($data);
|
||||
}
|
||||
|
||||
if (is_callable($datasets[$index])) {
|
||||
$datasets[$index] = call_user_func($datasets[$index]);
|
||||
}
|
||||
|
||||
if ($datasets[$index] instanceof Traversable) {
|
||||
$datasets[$index] = iterator_to_array($datasets[$index]);
|
||||
}
|
||||
|
||||
foreach ($datasets[$index] as $key => $values) {
|
||||
$values = is_array($values) ? $values : [$values];
|
||||
$processedDataset[] = [
|
||||
'label' => self::getDataSetDescription($key, $values),
|
||||
'values' => $values,
|
||||
];
|
||||
}
|
||||
|
||||
$processedDatasets[] = $processedDataset;
|
||||
}
|
||||
|
||||
return $processedDatasets;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<array> $combinations
|
||||
*
|
||||
* @return array<array>
|
||||
*/
|
||||
private static function getDataSetsCombinations(array $combinations): array
|
||||
{
|
||||
$result = [[]];
|
||||
foreach ($combinations as $index => $values) {
|
||||
$tmp = [];
|
||||
foreach ($result as $resultItem) {
|
||||
foreach ($values as $value) {
|
||||
$tmp[] = array_merge($resultItem, [$index => $value]);
|
||||
}
|
||||
}
|
||||
$result = $tmp;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int|string $key
|
||||
* @param array<int, mixed> $data
|
||||
@ -111,8 +168,10 @@ final class Datasets
|
||||
{
|
||||
$exporter = new Exporter();
|
||||
|
||||
$nameInsert = is_string($key) ? \sprintf('data set "%s" ', $key) : '';
|
||||
if (is_int($key)) {
|
||||
return \sprintf('(%s)', $exporter->shortenedRecursiveExport($data));
|
||||
}
|
||||
|
||||
return \sprintf(' with %s(%s)', $nameInsert, $exporter->shortenedRecursiveExport($data));
|
||||
return \sprintf('data set "%s"', $key);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class InvalidUsesPath extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of invalid uses path.
|
||||
*/
|
||||
public function __construct(string $target)
|
||||
{
|
||||
parent::__construct(sprintf('The path `%s` is not valid.', $target));
|
||||
}
|
||||
}
|
||||
24
src/Exceptions/MissingDependency.php
Normal file
24
src/Exceptions/MissingDependency.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest\Exceptions;
|
||||
|
||||
use InvalidArgumentException;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessEditor;
|
||||
use NunoMaduro\Collision\Contracts\RenderlessTrace;
|
||||
use Symfony\Component\Console\Exception\ExceptionInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class MissingDependency extends InvalidArgumentException implements ExceptionInterface, RenderlessEditor, RenderlessTrace
|
||||
{
|
||||
/**
|
||||
* Creates a new instance of missing dependency.
|
||||
*/
|
||||
public function __construct(string $feature, string $dependency)
|
||||
{
|
||||
parent::__construct(sprintf('The feature "%s" requires "%s".', $feature, $dependency));
|
||||
}
|
||||
}
|
||||
@ -11,9 +11,9 @@ use Pest\Contracts\HasPrintableTestCaseName;
|
||||
use Pest\Datasets;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use Pest\Support\HigherOrderMessageCollection;
|
||||
use Pest\Support\NullClosure;
|
||||
use Pest\Support\Str;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RuntimeException;
|
||||
|
||||
@ -62,9 +62,9 @@ final class TestCaseFactory
|
||||
/**
|
||||
* Holds the dataset, if any.
|
||||
*
|
||||
* @var Closure|iterable<int|string, mixed>|string|null
|
||||
* @var array<Closure|iterable<int|string, mixed>|string>
|
||||
*/
|
||||
public $dataset;
|
||||
public $datasets = [];
|
||||
|
||||
/**
|
||||
* The FQN of the test case class.
|
||||
@ -113,7 +113,11 @@ final class TestCaseFactory
|
||||
{
|
||||
$this->filename = $filename;
|
||||
$this->description = $description;
|
||||
$this->test = $closure ?? NullClosure::create();
|
||||
$this->test = $closure ?? function (): void {
|
||||
if (Assert::getCount() === 0) {
|
||||
self::markTestIncomplete(); // @phpstan-ignore-line
|
||||
}
|
||||
};
|
||||
|
||||
$this->factoryProxies = new HigherOrderMessageCollection();
|
||||
$this->proxies = new HigherOrderMessageCollection();
|
||||
@ -155,7 +159,7 @@ final class TestCaseFactory
|
||||
return $testCase;
|
||||
};
|
||||
|
||||
$datasets = Datasets::resolve($this->description, $this->dataset);
|
||||
$datasets = Datasets::resolve($this->description, $this->datasets);
|
||||
|
||||
return array_map($createTest, array_keys($datasets), $datasets);
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ use Pest\Support\HigherOrderTapProxy;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
if (!function_exists('beforeAll')) {
|
||||
/**
|
||||
* Runs the given closure before all tests in the current file.
|
||||
*/
|
||||
@ -19,7 +20,9 @@ function beforeAll(Closure $closure): void
|
||||
{
|
||||
TestSuite::getInstance()->beforeAll->set($closure);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('beforeEach')) {
|
||||
/**
|
||||
* Runs the given closure before each test in the current file.
|
||||
*
|
||||
@ -31,7 +34,9 @@ function beforeEach(Closure $closure = null): BeforeEachCall
|
||||
|
||||
return new BeforeEachCall(TestSuite::getInstance(), $filename, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('dataset')) {
|
||||
/**
|
||||
* Registers the given dataset.
|
||||
*
|
||||
@ -41,7 +46,9 @@ function dataset(string $name, $dataset): void
|
||||
{
|
||||
Datasets::set($name, $dataset);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('uses')) {
|
||||
/**
|
||||
* The uses function binds the given
|
||||
* arguments to test closures.
|
||||
@ -52,7 +59,9 @@ function uses(string ...$classAndTraits): UsesCall
|
||||
|
||||
return new UsesCall($filename, $classAndTraits);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('test')) {
|
||||
/**
|
||||
* Adds the given closure as a test. The first argument
|
||||
* is the test description; the second argument is
|
||||
@ -70,7 +79,9 @@ function test(string $description = null, Closure $closure = null)
|
||||
|
||||
return new TestCall(TestSuite::getInstance(), $filename, $description, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('it')) {
|
||||
/**
|
||||
* Adds the given closure as a test. The first argument
|
||||
* is the test description; the second argument is
|
||||
@ -84,7 +95,9 @@ function it(string $description, Closure $closure = null): TestCall
|
||||
|
||||
return test($description, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('afterEach')) {
|
||||
/**
|
||||
* Runs the given closure after each test in the current file.
|
||||
*
|
||||
@ -96,7 +109,9 @@ function afterEach(Closure $closure = null): AfterEachCall
|
||||
|
||||
return new AfterEachCall(TestSuite::getInstance(), $filename, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('afterAll')) {
|
||||
/**
|
||||
* Runs the given closure after all tests in the current file.
|
||||
*/
|
||||
@ -104,3 +119,4 @@ function afterAll(Closure $closure): void
|
||||
{
|
||||
TestSuite::getInstance()->afterAll->set($closure);
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,6 +8,8 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Illuminate\Support\Str;
|
||||
use Pest\Exceptions\InvalidConsoleArgument;
|
||||
use function Pest\testDirectory;
|
||||
use Pest\TestSuite;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -19,7 +21,8 @@ final class PestDatasetCommand extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'pest:dataset {name : The name of the dataset}';
|
||||
protected $signature = 'pest:dataset {name : The name of the dataset}
|
||||
{--test-directory=tests : The name of the tests directory}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -33,10 +36,13 @@ final class PestDatasetCommand extends Command
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
TestSuite::getInstance(base_path(), $this->option('test-directory'));
|
||||
|
||||
/** @var string $name */
|
||||
$name = $this->argument('name');
|
||||
|
||||
$relativePath = sprintf('tests/Datasets/%s.php', ucfirst($name));
|
||||
$relativePath = sprintf(testDirectory('Datasets/%s.php'), ucfirst($name));
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
$target = base_path($relativePath);
|
||||
|
||||
@ -8,6 +8,8 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Pest\Console\Thanks;
|
||||
use Pest\Exceptions\InvalidConsoleArgument;
|
||||
use function Pest\testDirectory;
|
||||
use Pest\TestSuite;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -19,7 +21,7 @@ final class PestInstallCommand extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'pest:install';
|
||||
protected $signature = 'pest:install {--test-directory=tests : The name of the tests directory}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -34,7 +36,10 @@ final class PestInstallCommand extends Command
|
||||
public function handle(): void
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
$pest = base_path('tests/Pest.php');
|
||||
TestSuite::getInstance(base_path(), $this->option('test-directory'));
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
$pest = base_path(testDirectory('Pest.php'));
|
||||
$stubs = 'stubs/Laravel';
|
||||
|
||||
if (File::exists($pest)) {
|
||||
|
||||
@ -8,6 +8,8 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use Pest\Exceptions\InvalidConsoleArgument;
|
||||
use Pest\Support\Str;
|
||||
use function Pest\testDirectory;
|
||||
use Pest\TestSuite;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -19,7 +21,7 @@ final class PestTestCommand extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test} {--dusk : Create a Dusk test}';
|
||||
protected $signature = 'pest:test {name : The name of the file} {--unit : Create a unit test} {--dusk : Create a Dusk test} {--test-directory=tests : The name of the tests directory}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
@ -33,12 +35,16 @@ final class PestTestCommand extends Command
|
||||
*/
|
||||
public function handle(): void
|
||||
{
|
||||
/* @phpstan-ignore-next-line */
|
||||
TestSuite::getInstance(base_path(), $this->option('test-directory'));
|
||||
|
||||
/** @var string $name */
|
||||
$name = $this->argument('name');
|
||||
|
||||
$type = ((bool) $this->option('unit')) ? 'Unit' : (((bool) $this->option('dusk')) ? 'Browser' : 'Feature');
|
||||
|
||||
$relativePath = sprintf('tests/%s/%s.php',
|
||||
$relativePath = sprintf(
|
||||
testDirectory('%s/%s.php'),
|
||||
$type,
|
||||
ucfirst($name)
|
||||
);
|
||||
|
||||
430
src/Logging/JUnit.php
Normal file
430
src/Logging/JUnit.php
Normal file
@ -0,0 +1,430 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
* This file is part of PHPUnit.
|
||||
*
|
||||
* (c) Sebastian Bergmann <sebastian@phpunit.de>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Pest\Logging;
|
||||
|
||||
use function class_exists;
|
||||
use DOMDocument;
|
||||
use DOMElement;
|
||||
use Exception;
|
||||
use function get_class;
|
||||
use function method_exists;
|
||||
use Pest\Concerns\TestCase;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
use PHPUnit\Framework\ExceptionWrapper;
|
||||
use PHPUnit\Framework\SelfDescribing;
|
||||
use PHPUnit\Framework\Test;
|
||||
use PHPUnit\Framework\TestFailure;
|
||||
use PHPUnit\Framework\TestListener;
|
||||
use PHPUnit\Framework\TestSuite;
|
||||
use PHPUnit\Framework\Warning;
|
||||
use PHPUnit\Util\Filter;
|
||||
use PHPUnit\Util\Printer;
|
||||
use PHPUnit\Util\Xml;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use function sprintf;
|
||||
use function str_replace;
|
||||
use Throwable;
|
||||
use function trim;
|
||||
|
||||
/**
|
||||
* @internal This class is not covered by the backward compatibility promise for PHPUnit
|
||||
*/
|
||||
final class JUnit extends Printer implements TestListener
|
||||
{
|
||||
/**
|
||||
* @var DOMDocument
|
||||
*/
|
||||
private $document;
|
||||
|
||||
/**
|
||||
* @var DOMElement
|
||||
*/
|
||||
private $root;
|
||||
|
||||
/**
|
||||
* @var DOMElement[]
|
||||
*/
|
||||
private $testSuites = [];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private $testSuiteTests = [0];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private $testSuiteAssertions = [0];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private $testSuiteErrors = [0];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private $testSuiteWarnings = [0];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private $testSuiteFailures = [0];
|
||||
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private $testSuiteSkipped = [0];
|
||||
|
||||
/**
|
||||
* @var int[]|float[]
|
||||
*/
|
||||
private $testSuiteTimes = [0];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $testSuiteLevel = 0;
|
||||
|
||||
/**
|
||||
* @var DOMElement|null
|
||||
*/
|
||||
private $currentTestCase;
|
||||
|
||||
public function __construct(string $out)
|
||||
{
|
||||
$this->document = new DOMDocument('1.0', 'UTF-8');
|
||||
$this->document->formatOutput = true;
|
||||
|
||||
$this->root = $this->document->createElement('testsuites');
|
||||
$this->document->appendChild($this->root);
|
||||
|
||||
parent::__construct($out);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush buffer and close output.
|
||||
*/
|
||||
public function flush(): void
|
||||
{
|
||||
$this->write($this->getXML());
|
||||
|
||||
parent::flush();
|
||||
}
|
||||
|
||||
/**
|
||||
* An error occurred.
|
||||
*/
|
||||
public function addError(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
$this->doAddFault($test, $t, 'error');
|
||||
$this->testSuiteErrors[$this->testSuiteLevel]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* A warning occurred.
|
||||
*/
|
||||
public function addWarning(Test $test, Warning $e, float $time): void
|
||||
{
|
||||
$this->doAddFault($test, $e, 'warning');
|
||||
$this->testSuiteWarnings[$this->testSuiteLevel]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* A failure occurred.
|
||||
*/
|
||||
public function addFailure(Test $test, AssertionFailedError $e, float $time): void
|
||||
{
|
||||
$this->doAddFault($test, $e, 'failure');
|
||||
$this->testSuiteFailures[$this->testSuiteLevel]++;
|
||||
}
|
||||
|
||||
/**
|
||||
* Incomplete test.
|
||||
*/
|
||||
public function addIncompleteTest(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
$this->doAddSkipped();
|
||||
}
|
||||
|
||||
/**
|
||||
* Risky test.
|
||||
*/
|
||||
public function addRiskyTest(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Skipped test.
|
||||
*/
|
||||
public function addSkippedTest(Test $test, Throwable $t, float $time): void
|
||||
{
|
||||
$this->doAddSkipped();
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
public function startTestSuite(TestSuite $suite): void
|
||||
{
|
||||
$testSuite = $this->document->createElement('testsuite');
|
||||
$testSuite->setAttribute('name', $suite->getName());
|
||||
|
||||
if (class_exists($suite->getName(), false)) {
|
||||
try {
|
||||
$class = new ReflectionClass($suite->getName());
|
||||
|
||||
if ($class->hasMethod('__getFileName')) {
|
||||
$fileName = $class->getMethod('__getFileName')->invoke(null);
|
||||
} else {
|
||||
$fileName = $class->getFileName();
|
||||
}
|
||||
|
||||
$testSuite->setAttribute('file', $fileName);
|
||||
} catch (ReflectionException $e) {
|
||||
// @ignoreException
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->testSuiteLevel > 0) {
|
||||
$this->testSuites[$this->testSuiteLevel]->appendChild($testSuite);
|
||||
} else {
|
||||
$this->root->appendChild($testSuite);
|
||||
}
|
||||
|
||||
$this->testSuiteLevel++;
|
||||
$this->testSuites[$this->testSuiteLevel] = $testSuite;
|
||||
$this->testSuiteTests[$this->testSuiteLevel] = 0;
|
||||
$this->testSuiteAssertions[$this->testSuiteLevel] = 0;
|
||||
$this->testSuiteErrors[$this->testSuiteLevel] = 0;
|
||||
$this->testSuiteWarnings[$this->testSuiteLevel] = 0;
|
||||
$this->testSuiteFailures[$this->testSuiteLevel] = 0;
|
||||
$this->testSuiteSkipped[$this->testSuiteLevel] = 0;
|
||||
$this->testSuiteTimes[$this->testSuiteLevel] = 0;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
public function endTestSuite(TestSuite $suite): void
|
||||
{
|
||||
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||
'tests',
|
||||
(string) $this->testSuiteTests[$this->testSuiteLevel]
|
||||
);
|
||||
|
||||
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||
'assertions',
|
||||
(string) $this->testSuiteAssertions[$this->testSuiteLevel]
|
||||
);
|
||||
|
||||
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||
'errors',
|
||||
(string) $this->testSuiteErrors[$this->testSuiteLevel]
|
||||
);
|
||||
|
||||
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||
'warnings',
|
||||
(string) $this->testSuiteWarnings[$this->testSuiteLevel]
|
||||
);
|
||||
|
||||
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||
'failures',
|
||||
(string) $this->testSuiteFailures[$this->testSuiteLevel]
|
||||
);
|
||||
|
||||
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||
'skipped',
|
||||
(string) $this->testSuiteSkipped[$this->testSuiteLevel]
|
||||
);
|
||||
|
||||
$this->testSuites[$this->testSuiteLevel]->setAttribute(
|
||||
'time',
|
||||
sprintf('%F', $this->testSuiteTimes[$this->testSuiteLevel])
|
||||
);
|
||||
|
||||
if ($this->testSuiteLevel > 1) {
|
||||
$this->testSuiteTests[$this->testSuiteLevel - 1] += $this->testSuiteTests[$this->testSuiteLevel];
|
||||
$this->testSuiteAssertions[$this->testSuiteLevel - 1] += $this->testSuiteAssertions[$this->testSuiteLevel];
|
||||
$this->testSuiteErrors[$this->testSuiteLevel - 1] += $this->testSuiteErrors[$this->testSuiteLevel];
|
||||
$this->testSuiteWarnings[$this->testSuiteLevel - 1] += $this->testSuiteWarnings[$this->testSuiteLevel];
|
||||
$this->testSuiteFailures[$this->testSuiteLevel - 1] += $this->testSuiteFailures[$this->testSuiteLevel];
|
||||
$this->testSuiteSkipped[$this->testSuiteLevel - 1] += $this->testSuiteSkipped[$this->testSuiteLevel];
|
||||
$this->testSuiteTimes[$this->testSuiteLevel - 1] += $this->testSuiteTimes[$this->testSuiteLevel];
|
||||
}
|
||||
|
||||
$this->testSuiteLevel--;
|
||||
}
|
||||
|
||||
/**
|
||||
* A test started.
|
||||
*
|
||||
* @param Test|TestCase $test
|
||||
*/
|
||||
public function startTest(Test $test): void
|
||||
{
|
||||
$usesDataprovider = false;
|
||||
|
||||
if (method_exists($test, 'usesDataProvider')) {
|
||||
$usesDataprovider = $test->usesDataProvider();
|
||||
}
|
||||
|
||||
$testCase = $this->document->createElement('testcase');
|
||||
$testCase->setAttribute('name', $test->getName());
|
||||
|
||||
try {
|
||||
$class = new ReflectionClass($test);
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (ReflectionException $e) {
|
||||
// @phpstan-ignore-next-line
|
||||
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$methodName = $test->getName(!$usesDataprovider);
|
||||
|
||||
if ($class->hasMethod($methodName)) {
|
||||
try {
|
||||
$method = $class->getMethod($methodName);
|
||||
// @codeCoverageIgnoreStart
|
||||
} catch (ReflectionException $e) {
|
||||
// @phpstan-ignore-next-line
|
||||
throw new Exception($e->getMessage(), (int) $e->getCode(), $e);
|
||||
}
|
||||
// @codeCoverageIgnoreEnd
|
||||
|
||||
$testCase->setAttribute('class', $class->getName());
|
||||
$testCase->setAttribute('classname', str_replace('\\', '.', $class->getName()));
|
||||
$fileName = $class->getFileName();
|
||||
if ($fileName !== false) {
|
||||
$testCase->setAttribute('file', $fileName);
|
||||
}
|
||||
$testCase->setAttribute('line', (string) $method->getStartLine());
|
||||
}
|
||||
|
||||
if (TeamCity::isPestTest($test)) {
|
||||
$testCase->setAttribute('class', $test->getPrintableTestCaseName());
|
||||
$testCase->setAttribute('classname', str_replace('\\', '.', $test->getPrintableTestCaseName()));
|
||||
// @phpstan-ignore-next-line
|
||||
$testCase->setAttribute('file', $test->__getFileName());
|
||||
}
|
||||
|
||||
$this->currentTestCase = $testCase;
|
||||
}
|
||||
|
||||
/**
|
||||
* A test ended.
|
||||
*/
|
||||
public function endTest(Test $test, float $time): void
|
||||
{
|
||||
$numAssertions = 0;
|
||||
|
||||
if (method_exists($test, 'getNumAssertions')) {
|
||||
$numAssertions = $test->getNumAssertions();
|
||||
}
|
||||
|
||||
$this->testSuiteAssertions[$this->testSuiteLevel] += $numAssertions;
|
||||
|
||||
if ($this->currentTestCase !== null) {
|
||||
$this->currentTestCase->setAttribute(
|
||||
'assertions',
|
||||
(string) $numAssertions
|
||||
);
|
||||
|
||||
$this->currentTestCase->setAttribute(
|
||||
'time',
|
||||
sprintf('%F', $time)
|
||||
);
|
||||
|
||||
$this->testSuites[$this->testSuiteLevel]->appendChild(
|
||||
$this->currentTestCase
|
||||
);
|
||||
}
|
||||
|
||||
$this->testSuiteTests[$this->testSuiteLevel]++;
|
||||
$this->testSuiteTimes[$this->testSuiteLevel] += $time;
|
||||
|
||||
$testOutput = '';
|
||||
|
||||
if (method_exists($test, 'hasOutput') && method_exists($test, 'getActualOutput')) {
|
||||
$testOutput = $test->hasOutput() ? $test->getActualOutput() : '';
|
||||
}
|
||||
|
||||
if ($testOutput !== '') {
|
||||
$systemOut = $this->document->createElement(
|
||||
'system-out',
|
||||
Xml::prepareString($testOutput)
|
||||
);
|
||||
|
||||
if ($this->currentTestCase !== null) {
|
||||
$this->currentTestCase->appendChild($systemOut);
|
||||
}
|
||||
}
|
||||
|
||||
$this->currentTestCase = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the XML as a string.
|
||||
*/
|
||||
public function getXML(): string
|
||||
{
|
||||
$xml = $this->document->saveXML();
|
||||
if ($xml === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $xml;
|
||||
}
|
||||
|
||||
private function doAddFault(Test $test, Throwable $t, string $type): void
|
||||
{
|
||||
if ($this->currentTestCase === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($test instanceof SelfDescribing) {
|
||||
$buffer = $test->toString() . "\n";
|
||||
} else {
|
||||
$buffer = '';
|
||||
}
|
||||
|
||||
$buffer .= trim(
|
||||
TestFailure::exceptionToString($t) . "\n" .
|
||||
Filter::getFilteredStacktrace($t)
|
||||
);
|
||||
|
||||
$fault = $this->document->createElement(
|
||||
$type,
|
||||
Xml::prepareString($buffer)
|
||||
);
|
||||
|
||||
if ($t instanceof ExceptionWrapper) {
|
||||
$fault->setAttribute('type', $t->getClassName());
|
||||
} else {
|
||||
$fault->setAttribute('type', get_class($t));
|
||||
}
|
||||
|
||||
$this->currentTestCase->appendChild($fault);
|
||||
}
|
||||
|
||||
private function doAddSkipped(): void
|
||||
{
|
||||
if ($this->currentTestCase === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
$skipped = $this->document->createElement('skipped');
|
||||
|
||||
$this->currentTestCase->appendChild($skipped);
|
||||
|
||||
$this->testSuiteSkipped[$this->testSuiteLevel]++;
|
||||
}
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Pest;
|
||||
namespace Pest\Logging;
|
||||
|
||||
use function getmypid;
|
||||
use Pest\Concerns\TestCase;
|
||||
@ -121,7 +121,7 @@ final class TeamCity extends DefaultResultPrinter
|
||||
|
||||
$this->printEvent('testStarted', [
|
||||
self::NAME => $test->getName(),
|
||||
/* @phpstan-ignore-next-line */
|
||||
// @phpstan-ignore-next-line
|
||||
self::LOCATION_HINT => self::PROTOCOL . $test->toString(),
|
||||
]);
|
||||
}
|
||||
@ -203,7 +203,7 @@ final class TeamCity extends DefaultResultPrinter
|
||||
return (int) round($time * 1000);
|
||||
}
|
||||
|
||||
private static function isPestTest(Test $test): bool
|
||||
public static function isPestTest(Test $test): bool
|
||||
{
|
||||
/** @var array<string, string> $uses */
|
||||
$uses = class_uses($test);
|
||||
@ -77,11 +77,13 @@ final class TestCall
|
||||
* Runs the current test multiple times with
|
||||
* each item of the given `iterable`.
|
||||
*
|
||||
* @param \Closure|iterable<int|string, mixed>|string $data
|
||||
* @param array<\Closure|iterable<int|string, mixed>|string> $data
|
||||
*/
|
||||
public function with($data): TestCall
|
||||
public function with(...$data): TestCall
|
||||
{
|
||||
$this->testCaseFactory->dataset = $data;
|
||||
foreach ($data as $dataset) {
|
||||
$this->testCaseFactory->datasets[] = $dataset;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\PendingObjects;
|
||||
|
||||
use Pest\Exceptions\InvalidUsesPath;
|
||||
use Closure;
|
||||
use Pest\TestSuite;
|
||||
|
||||
/**
|
||||
@ -12,6 +12,20 @@ use Pest\TestSuite;
|
||||
*/
|
||||
final class UsesCall
|
||||
{
|
||||
/**
|
||||
* Contains a global before each hook closure to be executed.
|
||||
*
|
||||
* Array indices here matter. They are mapped as follows:
|
||||
*
|
||||
* - `0` => `beforeAll`
|
||||
* - `1` => `beforeEach`
|
||||
* - `2` => `afterEach`
|
||||
* - `3` => `afterAll`
|
||||
*
|
||||
* @var array<int, Closure>
|
||||
*/
|
||||
private $hooks = [];
|
||||
|
||||
/**
|
||||
* Holds the class and traits.
|
||||
*
|
||||
@ -77,14 +91,13 @@ final class UsesCall
|
||||
]);
|
||||
}, $targets);
|
||||
|
||||
$this->targets = array_map(function ($target): string {
|
||||
$isValid = is_dir($target) || file_exists($target);
|
||||
if (!$isValid) {
|
||||
throw new InvalidUsesPath($target);
|
||||
$this->targets = array_reduce($targets, function (array $accumulator, string $target): array {
|
||||
if (is_dir($target) || file_exists($target)) {
|
||||
$accumulator[] = (string) realpath($target);
|
||||
}
|
||||
|
||||
return (string) realpath($target);
|
||||
}, $targets);
|
||||
return $accumulator;
|
||||
}, []);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,11 +110,56 @@ final class UsesCall
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the global beforeAll test hook.
|
||||
*/
|
||||
public function beforeAll(Closure $hook): UsesCall
|
||||
{
|
||||
$this->hooks[0] = $hook;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the global beforeEach test hook.
|
||||
*/
|
||||
public function beforeEach(Closure $hook): UsesCall
|
||||
{
|
||||
$this->hooks[1] = $hook;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the global afterEach test hook.
|
||||
*/
|
||||
public function afterEach(Closure $hook): UsesCall
|
||||
{
|
||||
$this->hooks[2] = $hook;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the global afterAll test hook.
|
||||
*/
|
||||
public function afterAll(Closure $hook): UsesCall
|
||||
{
|
||||
$this->hooks[3] = $hook;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the creation of uses.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
TestSuite::getInstance()->tests->use($this->classAndTraits, $this->groups, $this->targets);
|
||||
TestSuite::getInstance()->tests->use(
|
||||
$this->classAndTraits,
|
||||
$this->groups,
|
||||
$this->targets,
|
||||
$this->hooks,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,5 +6,10 @@ namespace Pest;
|
||||
|
||||
function version(): string
|
||||
{
|
||||
return '1.0.5';
|
||||
return '1.3.2';
|
||||
}
|
||||
|
||||
function testDirectory(string $file = ''): string
|
||||
{
|
||||
return TestSuite::getInstance()->testPath . '/' . $file;
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ final class Plugin
|
||||
public static function uses(string ...$traits): void
|
||||
{
|
||||
self::$callables[] = function () use ($traits): void {
|
||||
uses(...$traits)->in(TestSuite::getInstance()->rootPath . DIRECTORY_SEPARATOR . 'tests');
|
||||
uses(...$traits)->in(TestSuite::getInstance()->rootPath . DIRECTORY_SEPARATOR . testDirectory());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,6 @@ final class AfterEachRepository
|
||||
|
||||
return ChainableClosure::from(function (): void {
|
||||
if (class_exists(Mockery::class)) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
if ($container = Mockery::getContainer()) {
|
||||
/* @phpstan-ignore-next-line */
|
||||
$this->addToAssertionCount($container->mockery_getExpectationCount());
|
||||
|
||||
@ -4,6 +4,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace Pest\Repositories;
|
||||
|
||||
use Closure;
|
||||
use Pest\Exceptions\ShouldNotHappen;
|
||||
use Pest\Exceptions\TestAlreadyExist;
|
||||
use Pest\Exceptions\TestCaseAlreadyInUse;
|
||||
@ -18,13 +19,18 @@ use PHPUnit\Framework\TestCase;
|
||||
*/
|
||||
final class TestRepository
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const SEPARATOR = '>>>';
|
||||
|
||||
/**
|
||||
* @var array<string, TestCaseFactory>
|
||||
*/
|
||||
private $state = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<int, array<int, string>>>
|
||||
* @var array<string, array<int, array<int, string|Closure>>>
|
||||
*/
|
||||
private $uses = [];
|
||||
|
||||
@ -46,12 +52,13 @@ final class TestRepository
|
||||
};
|
||||
|
||||
foreach ($this->uses as $path => $uses) {
|
||||
[$classOrTraits, $groups] = $uses;
|
||||
$setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith): void {
|
||||
[$filename] = explode('@', $key);
|
||||
[$classOrTraits, $groups, $hooks] = $uses;
|
||||
|
||||
$setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith, $hooks): void {
|
||||
[$filename] = explode(self::SEPARATOR, $key);
|
||||
|
||||
if ((!is_dir($path) && $filename === $path) || (is_dir($path) && $startsWith($filename, $path))) {
|
||||
foreach ($classOrTraits as $class) {
|
||||
foreach ($classOrTraits as $class) { /** @var string $class */
|
||||
if (class_exists($class)) {
|
||||
if ($testCase->class !== TestCase::class) {
|
||||
throw new TestCaseAlreadyInUse($testCase->class, $class, $filename);
|
||||
@ -62,10 +69,12 @@ final class TestRepository
|
||||
}
|
||||
}
|
||||
|
||||
$testCase
|
||||
->factoryProxies
|
||||
// Consider set the real line here.
|
||||
->add($filename, 0, 'addGroups', [$groups]);
|
||||
// IDEA: Consider set the real lines on these.
|
||||
$testCase->factoryProxies->add($filename, 0, 'addGroups', [$groups]);
|
||||
$testCase->factoryProxies->add($filename, 0, 'addBeforeAll', [$hooks[0] ?? null]);
|
||||
$testCase->factoryProxies->add($filename, 0, 'addBeforeEach', [$hooks[1] ?? null]);
|
||||
$testCase->factoryProxies->add($filename, 0, 'addAfterEach', [$hooks[2] ?? null]);
|
||||
$testCase->factoryProxies->add($filename, 0, 'addAfterAll', [$hooks[3] ?? null]);
|
||||
}
|
||||
};
|
||||
|
||||
@ -81,7 +90,7 @@ final class TestRepository
|
||||
$state = count($onlyState) > 0 ? $onlyState : $this->state;
|
||||
|
||||
foreach ($state as $testFactory) {
|
||||
/* @var TestCaseFactory $testFactory */
|
||||
/** @var TestCaseFactory $testFactory */
|
||||
$tests = $testFactory->build($testSuite);
|
||||
foreach ($tests as $test) {
|
||||
$each($test);
|
||||
@ -95,8 +104,9 @@ final class TestRepository
|
||||
* @param array<int, string> $classOrTraits
|
||||
* @param array<int, string> $groups
|
||||
* @param array<int, string> $paths
|
||||
* @param array<int, Closure> $hooks
|
||||
*/
|
||||
public function use(array $classOrTraits, array $groups, array $paths): void
|
||||
public function use(array $classOrTraits, array $groups, array $paths, array $hooks): void
|
||||
{
|
||||
foreach ($classOrTraits as $classOrTrait) {
|
||||
if (!class_exists($classOrTrait) && !trait_exists($classOrTrait)) {
|
||||
@ -109,9 +119,10 @@ final class TestRepository
|
||||
$this->uses[$path] = [
|
||||
array_merge($this->uses[$path][0], $classOrTraits),
|
||||
array_merge($this->uses[$path][1], $groups),
|
||||
$this->uses[$path][2] + $hooks, // NOTE: array_merge will destroy numeric indices
|
||||
];
|
||||
} else {
|
||||
$this->uses[$path] = [$classOrTraits, $groups];
|
||||
$this->uses[$path] = [$classOrTraits, $groups, $hooks];
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -125,10 +136,10 @@ final class TestRepository
|
||||
throw ShouldNotHappen::fromMessage('Trying to create a test without description.');
|
||||
}
|
||||
|
||||
if (array_key_exists(sprintf('%s@%s', $test->filename, $test->description), $this->state)) {
|
||||
if (array_key_exists(sprintf('%s%s%s', $test->filename, self::SEPARATOR, $test->description), $this->state)) {
|
||||
throw new TestAlreadyExist($test->filename, $test->description);
|
||||
}
|
||||
|
||||
$this->state[sprintf('%s@%s', $test->filename, $test->description)] = $test;
|
||||
$this->state[sprintf('%s%s%s', $test->filename, self::SEPARATOR, $test->description)] = $test;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ use Closure;
|
||||
final class ChainableClosure
|
||||
{
|
||||
/**
|
||||
* Calls the given `$closure` and chains the the `$next` closure.
|
||||
* Calls the given `$closure` and chains the `$next` closure.
|
||||
*/
|
||||
public static function from(Closure $closure, Closure $next): Closure
|
||||
{
|
||||
@ -23,4 +23,17 @@ final class ChainableClosure
|
||||
call_user_func_array(Closure::bind($next, $this, get_class($this)), func_get_args());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the given static `$closure` and chains the `$next` closure.
|
||||
*/
|
||||
public static function fromStatic(Closure $closure, Closure $next): Closure
|
||||
{
|
||||
return static function () use ($closure, $next): void {
|
||||
/* @phpstan-ignore-next-line */
|
||||
call_user_func_array(Closure::bind($closure, null, self::class), func_get_args());
|
||||
/* @phpstan-ignore-next-line */
|
||||
call_user_func_array(Closure::bind($next, null, self::class), func_get_args());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ use ReflectionException;
|
||||
use ReflectionFunction;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ReflectionProperty;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
||||
@ -66,6 +66,13 @@ final class TestSuite
|
||||
*/
|
||||
public $rootPath;
|
||||
|
||||
/**
|
||||
* Holds the test path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $testPath;
|
||||
|
||||
/**
|
||||
* Holds an instance of the test suite.
|
||||
*
|
||||
@ -76,7 +83,7 @@ final class TestSuite
|
||||
/**
|
||||
* Creates a new instance of the test suite.
|
||||
*/
|
||||
public function __construct(string $rootPath)
|
||||
public function __construct(string $rootPath, string $testPath)
|
||||
{
|
||||
$this->beforeAll = new BeforeAllRepository();
|
||||
$this->beforeEach = new BeforeEachRepository();
|
||||
@ -85,15 +92,16 @@ final class TestSuite
|
||||
$this->afterAll = new AfterAllRepository();
|
||||
|
||||
$this->rootPath = (string) realpath($rootPath);
|
||||
$this->testPath = $testPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current instance of the test suite.
|
||||
*/
|
||||
public static function getInstance(string $rootPath = null): TestSuite
|
||||
public static function getInstance(string $rootPath = null, string $testPath = null): TestSuite
|
||||
{
|
||||
if (is_string($rootPath)) {
|
||||
self::$instance = new TestSuite($rootPath);
|
||||
if (is_string($rootPath) && is_string($testPath)) {
|
||||
self::$instance = new TestSuite($rootPath, $testPath);
|
||||
|
||||
foreach (Plugin::$callables as $callable) {
|
||||
$callable();
|
||||
|
||||
@ -40,8 +40,8 @@
|
||||
✓ eager wrapped registered datasets with (1)
|
||||
✓ eager wrapped registered datasets with (2)
|
||||
✓ eager registered wrapped datasets did the job right
|
||||
✓ named datasets with data set "one" (1)
|
||||
✓ named datasets with data set "two" (2)
|
||||
✓ named datasets with data set "one"
|
||||
✓ named datasets with data set "two"
|
||||
✓ named datasets did the job right
|
||||
✓ lazy named datasets with (Bar Object (...))
|
||||
✓ it creates unique test case names with ('Name 1', Pest\Plugin Object (), true) #1
|
||||
@ -51,6 +51,45 @@
|
||||
✓ it creates unique test case names with ('Name 2', Pest\Plugin Object (), true)
|
||||
✓ it creates unique test case names with ('Name 1', Pest\Plugin Object (), true) #3
|
||||
✓ it creates unique test case names - count
|
||||
✓ lazy multiple datasets with (1) / (3)
|
||||
✓ lazy multiple datasets with (1) / (4)
|
||||
✓ lazy multiple datasets with (2) / (3)
|
||||
✓ lazy multiple datasets with (2) / (4)
|
||||
✓ lazy multiple datasets did the job right
|
||||
✓ eager multiple datasets with (1) / (3)
|
||||
✓ eager multiple datasets with (1) / (4)
|
||||
✓ eager multiple datasets with (2) / (3)
|
||||
✓ eager multiple datasets with (2) / (4)
|
||||
✓ eager multiple datasets did the job right
|
||||
✓ lazy registered multiple datasets with (1) / (1)
|
||||
✓ lazy registered multiple datasets with (1) / (2)
|
||||
✓ lazy registered multiple datasets with (2) / (1)
|
||||
✓ lazy registered multiple datasets with (2) / (2)
|
||||
✓ lazy registered multiple datasets did the job right
|
||||
✓ eager registered multiple datasets with (1) / (1)
|
||||
✓ eager registered multiple datasets with (1) / (2)
|
||||
✓ eager registered multiple datasets with (2) / (1)
|
||||
✓ eager registered multiple datasets with (2) / (2)
|
||||
✓ eager registered multiple datasets did the job right
|
||||
✓ eager wrapped registered multiple datasets with (1) / (1)
|
||||
✓ eager wrapped registered multiple datasets with (1) / (2)
|
||||
✓ eager wrapped registered multiple datasets with (2) / (1)
|
||||
✓ eager wrapped registered multiple datasets with (2) / (2)
|
||||
✓ eager wrapped registered multiple datasets did the job right
|
||||
✓ named multiple datasets with data set "one" / data set "three"
|
||||
✓ named multiple datasets with data set "one" / data set "four"
|
||||
✓ named multiple datasets with data set "two" / data set "three"
|
||||
✓ named multiple datasets with data set "two" / data set "four"
|
||||
✓ named multiple datasets did the job right
|
||||
✓ more than two datasets with (1) / (3) / (5)
|
||||
✓ more than two datasets with (1) / (3) / (6)
|
||||
✓ more than two datasets with (1) / (4) / (5)
|
||||
✓ more than two datasets with (1) / (4) / (6)
|
||||
✓ more than two datasets with (2) / (3) / (5)
|
||||
✓ more than two datasets with (2) / (3) / (6)
|
||||
✓ more than two datasets with (2) / (4) / (5)
|
||||
✓ more than two datasets with (2) / (4) / (6)
|
||||
✓ more than two datasets did the job right
|
||||
|
||||
PASS Tests\Features\Exceptions
|
||||
✓ it gives access the the underlying expectException
|
||||
@ -63,11 +102,21 @@
|
||||
✓ it allows to call underlying protected/private methods
|
||||
✓ it throws error if method do not exist
|
||||
✓ it can forward unexpected calls to any global function
|
||||
✓ it can use helpers from helpers file
|
||||
|
||||
PASS Tests\Features\HigherOrderTests
|
||||
✓ it proxies calls to object
|
||||
✓ it is capable doing multiple assertions
|
||||
|
||||
WARN Tests\Features\Incompleted
|
||||
… incompleted
|
||||
… it is incompleted
|
||||
… it is incompleted even with method calls like skip
|
||||
… it is incompleted even with method calls like group
|
||||
✓ it is not incompleted because of expect
|
||||
✓ it is not incompleted because of assert
|
||||
✓ it is not incompleted because of test with assertions
|
||||
|
||||
PASS Tests\Features\It
|
||||
✓ it is a test
|
||||
✓ it is a higher order message test
|
||||
@ -76,10 +125,8 @@
|
||||
✓ it can call chained macro method
|
||||
✓ it will throw exception from call if no macro exists
|
||||
|
||||
PASS Tests\Features\Mocks
|
||||
✓ it has bar
|
||||
|
||||
PASS Tests\Features\PendingHigherOrderTests
|
||||
✓ get 'foo'
|
||||
✓ get 'foo' → get 'bar' → expect true → toBeTrue
|
||||
✓ get 'foo' → expect true → toBeTrue
|
||||
|
||||
@ -103,6 +150,18 @@
|
||||
PASS Tests\Fixtures\ExampleTest
|
||||
✓ it example 2
|
||||
|
||||
PASS Tests\Hooks\AfterAllTest
|
||||
✓ global afterAll execution order
|
||||
|
||||
PASS Tests\Hooks\AfterEachTest
|
||||
✓ global afterEach execution order
|
||||
|
||||
PASS Tests\Hooks\BeforeAllTest
|
||||
✓ global beforeAll execution order
|
||||
|
||||
PASS Tests\Hooks\BeforeEachTest
|
||||
✓ global beforeEach execution order
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\InvalidTestName
|
||||
✓ it runs file names like `@#$%^&()-_=+.php`
|
||||
|
||||
@ -112,6 +171,10 @@
|
||||
PASS Tests\PHPUnit\CustomAffixes\AdditionalFileExtensionspec
|
||||
✓ it runs file names like `AdditionalFileExtension.spec.php`
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\FolderWithAn\ExampleTest
|
||||
✓ custom traits can be used
|
||||
✓ trait applied in this file
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\ManyExtensionsclasstest
|
||||
✓ it runs file names like `ManyExtensions.class.test.php`
|
||||
|
||||
@ -158,7 +221,11 @@
|
||||
✓ it outputs the help information when --help is used
|
||||
|
||||
PASS Tests\Unit\Datasets
|
||||
✓ it show the names of named datasets in their description
|
||||
✓ it show only the names of named datasets in their description
|
||||
✓ it show the actual dataset of non-named datasets in their description
|
||||
✓ it show only the names of multiple named datasets in their description
|
||||
✓ it show the actual dataset of multiple non-named datasets in their description
|
||||
✓ it show the correct description for mixed named and not-named datasets
|
||||
|
||||
PASS Tests\Unit\Plugins\Version
|
||||
✓ it outputs the version when --version is used
|
||||
@ -209,5 +276,5 @@
|
||||
✓ it is a test
|
||||
✓ it uses correct parent class
|
||||
|
||||
Tests: 7 skipped, 115 passed
|
||||
Tests: 4 incompleted, 7 skipped, 168 passed
|
||||
|
||||
@ -8,11 +8,11 @@ beforeAll(function () use ($foo) {
|
||||
});
|
||||
|
||||
it('gets executed before tests', function () use ($foo) {
|
||||
expect($foo->bar)->toBe(1);
|
||||
expect($foo)->bar->toBe(1);
|
||||
|
||||
$foo->bar = 'changed';
|
||||
});
|
||||
|
||||
it('do not get executed before each test', function () use ($foo) {
|
||||
expect($foo->bar)->toBe('changed');
|
||||
expect($foo)->bar->toBe('changed');
|
||||
});
|
||||
|
||||
@ -137,3 +137,89 @@ it('creates unique test case names', function (string $name, Plugin $plugin, boo
|
||||
it('creates unique test case names - count', function () use (&$counter) {
|
||||
expect($counter)->toBe(6);
|
||||
});
|
||||
|
||||
$datasets_a = [[1], [2]];
|
||||
$datasets_b = [[3], [4]];
|
||||
|
||||
test('lazy multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b) {
|
||||
$state->text .= $text_a . $text_b;
|
||||
expect($datasets_a)->toContain([$text_a]);
|
||||
expect($datasets_b)->toContain([$text_b]);
|
||||
})->with($datasets_a, $datasets_b);
|
||||
|
||||
test('lazy multiple datasets did the job right', function () use ($state) {
|
||||
expect($state->text)->toBe('12121212121213142324');
|
||||
});
|
||||
|
||||
$state->text = '';
|
||||
|
||||
test('eager multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b) {
|
||||
$state->text .= $text_a . $text_b;
|
||||
expect($datasets_a)->toContain([$text_a]);
|
||||
expect($datasets_b)->toContain([$text_b]);
|
||||
})->with(function () use ($datasets_a) {
|
||||
return $datasets_a;
|
||||
})->with(function () use ($datasets_b) {
|
||||
return $datasets_b;
|
||||
});
|
||||
|
||||
test('eager multiple datasets did the job right', function () use ($state) {
|
||||
expect($state->text)->toBe('1212121212121314232413142324');
|
||||
});
|
||||
|
||||
test('lazy registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets) {
|
||||
$state->text .= $text_a . $text_b;
|
||||
expect($datasets)->toContain([$text_a]);
|
||||
expect($datasets)->toContain([$text_b]);
|
||||
})->with('numbers.array')->with('numbers.array');
|
||||
|
||||
test('lazy registered multiple datasets did the job right', function () use ($state) {
|
||||
expect($state->text)->toBe('121212121212131423241314232411122122');
|
||||
});
|
||||
|
||||
test('eager registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets) {
|
||||
$state->text .= $text_a . $text_b;
|
||||
expect($datasets)->toContain([$text_a]);
|
||||
expect($datasets)->toContain([$text_b]);
|
||||
})->with('numbers.array')->with('numbers.closure');
|
||||
|
||||
test('eager registered multiple datasets did the job right', function () use ($state) {
|
||||
expect($state->text)->toBe('12121212121213142324131423241112212211122122');
|
||||
});
|
||||
|
||||
test('eager wrapped registered multiple datasets', function ($text_a, $text_b) use ($state, $datasets) {
|
||||
$state->text .= $text_a . $text_b;
|
||||
expect($datasets)->toContain([$text_a]);
|
||||
expect($datasets)->toContain([$text_b]);
|
||||
})->with('numbers.closure.wrapped')->with('numbers.closure');
|
||||
|
||||
test('eager wrapped registered multiple datasets did the job right', function () use ($state) {
|
||||
expect($state->text)->toBe('1212121212121314232413142324111221221112212211122122');
|
||||
});
|
||||
|
||||
test('named multiple datasets', function ($text_a, $text_b) use ($state, $datasets_a, $datasets_b) {
|
||||
$state->text .= $text_a . $text_b;
|
||||
expect($datasets_a)->toContain([$text_a]);
|
||||
expect($datasets_b)->toContain([$text_b]);
|
||||
})->with([
|
||||
'one' => [1],
|
||||
'two' => [2],
|
||||
])->with([
|
||||
'three' => [3],
|
||||
'four' => [4],
|
||||
]);
|
||||
|
||||
test('named multiple datasets did the job right', function () use ($state) {
|
||||
expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324');
|
||||
});
|
||||
|
||||
test('more than two datasets', function ($text_a, $text_b, $text_c) use ($state, $datasets_a, $datasets_b) {
|
||||
$state->text .= $text_a . $text_b . $text_c;
|
||||
expect($datasets_a)->toContain([$text_a]);
|
||||
expect($datasets_b)->toContain([$text_b]);
|
||||
expect([5, 6])->toContain($text_c);
|
||||
})->with($datasets_a, $datasets_b)->with([5, 6]);
|
||||
|
||||
test('more than two datasets did the job right', function () use ($state) {
|
||||
expect($state->text)->toBe('121212121212131423241314232411122122111221221112212213142324135136145146235236245246');
|
||||
});
|
||||
|
||||
@ -42,3 +42,5 @@ it('throws error if method do not exist', function () {
|
||||
})->throws(\ReflectionException::class, 'Call to undefined method PHPUnit\Framework\TestCase::name()');
|
||||
|
||||
it('can forward unexpected calls to any global function')->_assertThat();
|
||||
|
||||
it('can use helpers from helpers file')->myAssertTrue(true);
|
||||
|
||||
17
tests/Features/Incompleted.php
Normal file
17
tests/Features/Incompleted.php
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
test('incompleted');
|
||||
|
||||
it('is incompleted');
|
||||
|
||||
it('is incompleted even with method calls like skip')->skip(false);
|
||||
|
||||
it('is incompleted even with method calls like group')->group('wtv');
|
||||
|
||||
it('is not incompleted because of expect')->expect(true)->toBeTrue();
|
||||
|
||||
it('is not incompleted because of assert')->assertTrue(true);
|
||||
|
||||
it('is not incompleted because of test with assertions', function () {
|
||||
expect(true)->toBeTrue();
|
||||
});
|
||||
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
it('is a test', function () {
|
||||
$this->assertArrayHasKey('key', ['key' => 'foo']);
|
||||
expect(['key' => 'foo'])->toHaveKey('key')->key->toBeString();
|
||||
});
|
||||
|
||||
it('is a higher order message test')->expect(true)->toBeTrue();
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
<?php
|
||||
|
||||
use function Tests\mock;
|
||||
|
||||
interface Foo
|
||||
{
|
||||
public function bar(): int;
|
||||
}
|
||||
|
||||
it('has bar', function () {
|
||||
$mock = mock(Foo::class);
|
||||
$mock->shouldReceive('bar')
|
||||
->times(1)
|
||||
->andReturn(2);
|
||||
|
||||
$mock->bar();
|
||||
});
|
||||
@ -26,5 +26,6 @@ trait Gettable
|
||||
}
|
||||
}
|
||||
|
||||
get('foo'); // not incomplete because closure is created...
|
||||
get('foo')->get('bar')->expect(true)->toBeTrue();
|
||||
get('foo')->expect(true)->toBeTrue();
|
||||
|
||||
@ -1,11 +1,6 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Mockery;
|
||||
use Mockery\MockInterface;
|
||||
|
||||
function mock(string $class): MockInterface
|
||||
function myAssertTrue($value)
|
||||
{
|
||||
return Mockery::mock($class);
|
||||
test()->assertTrue($value);
|
||||
}
|
||||
|
||||
27
tests/Hooks/AfterAllTest.php
Normal file
27
tests/Hooks/AfterAllTest.php
Normal file
@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
global $globalHook;
|
||||
|
||||
uses()->afterAll(function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('afterAll')
|
||||
->and($globalHook->afterAll)
|
||||
->toBe(0);
|
||||
|
||||
$globalHook->afterAll = 1;
|
||||
});
|
||||
|
||||
afterAll(function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('afterAll')
|
||||
->and($globalHook->afterAll)
|
||||
->toBe(1);
|
||||
|
||||
$globalHook->afterAll = 2;
|
||||
});
|
||||
|
||||
test('global afterAll execution order', function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->not()
|
||||
->toHaveProperty('afterAll');
|
||||
});
|
||||
23
tests/Hooks/AfterEachTest.php
Normal file
23
tests/Hooks/AfterEachTest.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
uses()->afterEach(function () {
|
||||
expect($this)
|
||||
->toHaveProperty('ith')
|
||||
->and($this->ith)
|
||||
->toBe(0);
|
||||
|
||||
$this->ith = 1;
|
||||
});
|
||||
|
||||
afterEach(function () {
|
||||
expect($this)
|
||||
->toHaveProperty('ith')
|
||||
->and($this->ith)
|
||||
->toBe(1);
|
||||
});
|
||||
|
||||
test('global afterEach execution order', function () {
|
||||
expect($this)
|
||||
->not()
|
||||
->toHaveProperty('ith');
|
||||
});
|
||||
28
tests/Hooks/BeforeAllTest.php
Normal file
28
tests/Hooks/BeforeAllTest.php
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
global $globalHook;
|
||||
|
||||
uses()->beforeAll(function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('beforeAll')
|
||||
->and($globalHook->beforeAll)
|
||||
->toBe(0);
|
||||
|
||||
$globalHook->beforeAll = 1;
|
||||
});
|
||||
|
||||
beforeAll(function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('beforeAll')
|
||||
->and($globalHook->beforeAll)
|
||||
->toBe(1);
|
||||
|
||||
$globalHook->beforeAll = 2;
|
||||
});
|
||||
|
||||
test('global beforeAll execution order', function () use ($globalHook) {
|
||||
expect($globalHook)
|
||||
->toHaveProperty('beforeAll')
|
||||
->and($globalHook->beforeAll)
|
||||
->toBe(2);
|
||||
});
|
||||
26
tests/Hooks/BeforeEachTest.php
Normal file
26
tests/Hooks/BeforeEachTest.php
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
uses()->beforeEach(function () {
|
||||
expect($this)
|
||||
->toHaveProperty('baz')
|
||||
->and($this->baz)
|
||||
->toBe(0);
|
||||
|
||||
$this->baz = 1;
|
||||
});
|
||||
|
||||
beforeEach(function () {
|
||||
expect($this)
|
||||
->toHaveProperty('baz')
|
||||
->and($this->baz)
|
||||
->toBe(1);
|
||||
|
||||
$this->baz = 2;
|
||||
});
|
||||
|
||||
test('global beforeEach execution order', function () {
|
||||
expect($this)
|
||||
->toHaveProperty('baz')
|
||||
->and($this->baz)
|
||||
->toBe(2);
|
||||
});
|
||||
19
tests/PHPUnit/CustomAffixes/FolderWithAn@/ExampleTest.php
Normal file
19
tests/PHPUnit/CustomAffixes/FolderWithAn@/ExampleTest.php
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
class MyCustomClassTest extends PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function assertTrueIsTrue()
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
|
||||
uses(MyCustomClassTest::class);
|
||||
|
||||
test('custom traits can be used', function () {
|
||||
$this->assertTrueIsTrue();
|
||||
});
|
||||
|
||||
test('trait applied in this file')->assertTrueIsTrue();
|
||||
@ -1,3 +1,20 @@
|
||||
<?php
|
||||
|
||||
uses()->group('integration')->in('Visual');
|
||||
|
||||
$globalHook = (object) []; // NOTE: global test value container to be mutated and checked across files, as needed
|
||||
|
||||
uses()
|
||||
->beforeEach(function () {
|
||||
$this->baz = 0;
|
||||
})
|
||||
->beforeAll(function () use ($globalHook) {
|
||||
$globalHook->beforeAll = 0;
|
||||
})
|
||||
->afterEach(function () {
|
||||
$this->ith = 0;
|
||||
})
|
||||
->afterAll(function () use ($globalHook) {
|
||||
$globalHook->afterAll = 0;
|
||||
})
|
||||
->in('Hooks');
|
||||
|
||||
@ -18,7 +18,7 @@ test('default php unit tests', function () {
|
||||
$testSuite->addTest($phpUnitTestCase);
|
||||
expect($testSuite->tests())->toHaveCount(1);
|
||||
|
||||
AddsTests::to($testSuite, new \Pest\TestSuite(getcwd()));
|
||||
AddsTests::to($testSuite, new \Pest\TestSuite(getcwd(), 'tests'));
|
||||
expect($testSuite->tests())->toHaveCount(1);
|
||||
});
|
||||
|
||||
@ -27,6 +27,6 @@ it('removes warnings', function () {
|
||||
$warningTestCase = new WarningTestCase('No tests found in class "Pest\TestCase".');
|
||||
$testSuite->addTest($warningTestCase);
|
||||
|
||||
AddsTests::to($testSuite, new \Pest\TestSuite(getcwd()));
|
||||
AddsTests::to($testSuite, new \Pest\TestSuite(getcwd(), 'tests'));
|
||||
expect($testSuite->tests())->toHaveCount(0);
|
||||
});
|
||||
|
||||
@ -2,12 +2,80 @@
|
||||
|
||||
use Pest\Datasets;
|
||||
|
||||
it('show the names of named datasets in their description', function () {
|
||||
it('show only the names of named datasets in their description', function () {
|
||||
$descriptions = array_keys(Datasets::resolve('test description', [
|
||||
[
|
||||
'one' => [1],
|
||||
'two' => [[2]],
|
||||
],
|
||||
]));
|
||||
|
||||
expect($descriptions[0])->toBe('test description with data set "one" (1)');
|
||||
expect($descriptions[1])->toBe('test description with data set "two" (array(2))');
|
||||
expect($descriptions[0])->toBe('test description with data set "one"');
|
||||
expect($descriptions[1])->toBe('test description with data set "two"');
|
||||
});
|
||||
|
||||
it('show the actual dataset of non-named datasets in their description', function () {
|
||||
$descriptions = array_keys(Datasets::resolve('test description', [
|
||||
[
|
||||
[1],
|
||||
[[2]],
|
||||
],
|
||||
]));
|
||||
|
||||
expect($descriptions[0])->toBe('test description with (1)');
|
||||
expect($descriptions[1])->toBe('test description with (array(2))');
|
||||
});
|
||||
|
||||
it('show only the names of multiple named datasets in their description', function () {
|
||||
$descriptions = array_keys(Datasets::resolve('test description', [
|
||||
[
|
||||
'one' => [1],
|
||||
'two' => [[2]],
|
||||
],
|
||||
[
|
||||
'three' => [3],
|
||||
'four' => [[4]],
|
||||
],
|
||||
]));
|
||||
|
||||
expect($descriptions[0])->toBe('test description with data set "one" / data set "three"');
|
||||
expect($descriptions[1])->toBe('test description with data set "one" / data set "four"');
|
||||
expect($descriptions[2])->toBe('test description with data set "two" / data set "three"');
|
||||
expect($descriptions[3])->toBe('test description with data set "two" / data set "four"');
|
||||
});
|
||||
|
||||
it('show the actual dataset of multiple non-named datasets in their description', function () {
|
||||
$descriptions = array_keys(Datasets::resolve('test description', [
|
||||
[
|
||||
[1],
|
||||
[[2]],
|
||||
],
|
||||
[
|
||||
[3],
|
||||
[[4]],
|
||||
],
|
||||
]));
|
||||
|
||||
expect($descriptions[0])->toBe('test description with (1) / (3)');
|
||||
expect($descriptions[1])->toBe('test description with (1) / (array(4))');
|
||||
expect($descriptions[2])->toBe('test description with (array(2)) / (3)');
|
||||
expect($descriptions[3])->toBe('test description with (array(2)) / (array(4))');
|
||||
});
|
||||
|
||||
it('show the correct description for mixed named and not-named datasets', function () {
|
||||
$descriptions = array_keys(Datasets::resolve('test description', [
|
||||
[
|
||||
'one' => [1],
|
||||
[[2]],
|
||||
],
|
||||
[
|
||||
[3],
|
||||
'four' => [[4]],
|
||||
],
|
||||
]));
|
||||
|
||||
expect($descriptions[0])->toBe('test description with data set "one" / (3)');
|
||||
expect($descriptions[1])->toBe('test description with data set "one" / data set "four"');
|
||||
expect($descriptions[2])->toBe('test description with (array(2)) / (3)');
|
||||
expect($descriptions[3])->toBe('test description with (array(2)) / data set "four"');
|
||||
});
|
||||
|
||||
@ -38,6 +38,7 @@ it('creates an instance and resolves also sub parameters', function () {
|
||||
|
||||
it('can resolve builtin value types', function () {
|
||||
$this->container->add('rootPath', getcwd());
|
||||
$this->container->add('testPath', 'tests');
|
||||
|
||||
$instance = $this->container->get(TestSuite::class);
|
||||
expect($instance)->toBeInstanceOf(TestSuite::class);
|
||||
|
||||
@ -4,7 +4,7 @@ use Pest\Exceptions\TestAlreadyExist;
|
||||
use Pest\TestSuite;
|
||||
|
||||
it('does not allow to add the same test description twice', function () {
|
||||
$testSuite = new TestSuite(getcwd());
|
||||
$testSuite = new TestSuite(getcwd(), 'tests');
|
||||
$test = function () {};
|
||||
$testSuite->tests->set(new \Pest\Factories\TestCaseFactory(__FILE__, 'foo', $test));
|
||||
$this->expectException(TestAlreadyExist::class);
|
||||
|
||||
Reference in New Issue
Block a user