mirror of
https://github.com/pestphp/pest.git
synced 2026-03-06 07:47:22 +01:00
Compare commits
88 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 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 | |||
| 6616b6299b | |||
| 0bab649156 | |||
| 2de7cb4726 | |||
| 99500d0cae | |||
| 7eb5478c42 | |||
| d2babb1331 | |||
| a6cced6b63 | |||
| 7917313422 | |||
| e02c22e136 | |||
| 2157644a39 | |||
| b6c2812a91 | |||
| 4b65d2c426 | |||
| 3589f3d5e7 | |||
| 1f39b8d239 | |||
| 7c3c390cbf | |||
| 19a1569fa8 | |||
| 9a0240bc7b | |||
| dd94a843b5 | |||
| 9426d08aa2 | |||
| fe2fac37f8 | |||
| cabd64df00 | |||
| bb57a54089 | |||
| 5e0bfba7bf | |||
| f6c19e469f | |||
| 13f09cc662 | |||
| a7e2856887 | |||
| 721e047485 | |||
| 301ff155a4 | |||
| 40f2065575 | |||
| be906eb823 | |||
| 2cee825f61 | |||
| ea6308bfdf | |||
| c6a6f7e2ab | |||
| 20077c285a | |||
| fa13016785 | |||
| b81bb9d621 | |||
| 2deb53c14f | |||
| 6b8feed08a | |||
| 2fe8e07cf3 | |||
| d2c907868e | |||
| a2900a5e09 | |||
| df934bacd9 |
30
.gitattributes
vendored
30
.gitattributes
vendored
@ -1,16 +1,14 @@
|
||||
/art export-ignore
|
||||
/docs export-ignore
|
||||
/tests export-ignore
|
||||
/scripts export-ignore
|
||||
/.github export-ignore
|
||||
/.php_cs 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
|
||||
README.md export-ignore
|
||||
/art export-ignore
|
||||
/docs export-ignore
|
||||
/tests export-ignore
|
||||
/scripts export-ignore
|
||||
/.github export-ignore
|
||||
/.php-cs-fixer.dist.php export-ignore
|
||||
.editorconfig export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
phpstan.neon export-ignore
|
||||
phpunit.xml export-ignore
|
||||
CHANGELOG.md export-ignore
|
||||
CONTRIBUTING.md export-ignore
|
||||
README.md export-ignore
|
||||
|
||||
4
.github/workflows/static.yml
vendored
4
.github/workflows/static.yml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.4
|
||||
php-version: 8.0
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.4
|
||||
php-version: 8.0
|
||||
tools: composer:v2
|
||||
coverage: none
|
||||
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -4,8 +4,9 @@ composer.lock
|
||||
/vendor/
|
||||
coverage.xml
|
||||
.phpunit.result.cache
|
||||
.php_cs.cache
|
||||
/.php-cs-fixer.php
|
||||
.php-cs-fixer.cache
|
||||
.temp/coverage.php
|
||||
*.swp
|
||||
*.swo
|
||||
.vscode/
|
||||
.vscode/
|
||||
|
||||
@ -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);
|
||||
44
CHANGELOG.md
44
CHANGELOG.md
@ -4,6 +4,50 @@ 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.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))
|
||||
- Support for PHPUnit 9.5.4 ([#284](https://github.com/pestphp/pest/pull/284))
|
||||
|
||||
## [v1.0.4 (2021-03-17)](https://github.com/pestphp/pest/compare/v1.0.3...v1.0.4)
|
||||
### Added
|
||||
- Support for PHPUnit 9.5.3 ([#278](https://github.com/pestphp/pest/pull/278))
|
||||
|
||||
## [v1.0.3 (2021-03-13)](https://github.com/pestphp/pest/compare/v1.0.2...v1.0.3)
|
||||
### Added
|
||||
- Support for test extensions ([#269](https://github.com/pestphp/pest/pull/269))
|
||||
|
||||
## [v1.0.2 (2021-02-04)](https://github.com/pestphp/pest/compare/v1.0.1...v1.0.2)
|
||||
### Added
|
||||
- Support for PHPUnit 9.5.2 ([#267](https://github.com/pestphp/pest/pull/267))
|
||||
|
||||
## [v1.0.1 (2021-01-18)](https://github.com/pestphp/pest/compare/v1.0.0...v1.0.1)
|
||||
### Added
|
||||
- Support for PHPUnit 9.5.1 ([#261](https://github.com/pestphp/pest/pull/261))
|
||||
|
||||
### Fixed
|
||||
- Fix `TestCase@expect` PHPDoc tag ([#251](https://github.com/pestphp/pest/pull/251))
|
||||
|
||||
## [v1.0.0 (2021-01-03)](https://github.com/pestphp/pest/compare/v0.3.19...v1.0.0)
|
||||
### Added
|
||||
- `pest:test --dusk` option ([#245](https://github.com/pestphp/pest/pull/245))
|
||||
|
||||
@ -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)**.
|
||||
|
||||
16
bin/pest
16
bin/pest
@ -28,11 +28,12 @@ use Symfony\Component\Console\Output\OutputInterface;
|
||||
(new Provider())->register();
|
||||
|
||||
// get $rootPath based on $autoloadPath
|
||||
$rootPath = dirname($autoloadPath, 2);
|
||||
$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']));
|
||||
})();
|
||||
|
||||
@ -23,7 +23,7 @@
|
||||
"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.0"
|
||||
"phpunit/phpunit": ">= 9.3.7 <= 9.5.4"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
@ -43,10 +43,9 @@
|
||||
]
|
||||
},
|
||||
"require-dev": {
|
||||
"illuminate/console": "^8.0",
|
||||
"illuminate/support": "^8.0",
|
||||
"laravel/dusk": "^6.9.1",
|
||||
"mockery/mockery": "^1.4.1",
|
||||
"illuminate/console": "^8.32.1",
|
||||
"illuminate/support": "^8.32.1",
|
||||
"laravel/dusk": "^6.13.0",
|
||||
"pestphp/pest-dev-tools": "dev-master"
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
|
||||
10
phpstan.neon
10
phpstan.neon
@ -14,7 +14,6 @@ parameters:
|
||||
|
||||
ignoreErrors:
|
||||
- "#type mixed is not subtype of native#"
|
||||
- "#Undefined variable: \\$this#"
|
||||
- "#is not allowed to extend#"
|
||||
- "#Language construct eval#"
|
||||
- "# with null as default value#"
|
||||
@ -23,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);
|
||||
|
||||
/*
|
||||
|
||||
@ -111,8 +111,10 @@ final class Datasets
|
||||
{
|
||||
$exporter = new Exporter();
|
||||
|
||||
$nameInsert = is_string($key) ? \sprintf('data set "%s" ', $key) : '';
|
||||
if (is_int($key)) {
|
||||
return \sprintf(' with (%s)', $exporter->shortenedRecursiveExport($data));
|
||||
}
|
||||
|
||||
return \sprintf(' with %s(%s)', $nameInsert, $exporter->shortenedRecursiveExport($data));
|
||||
return \sprintf(' with 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));
|
||||
}
|
||||
}
|
||||
@ -5,14 +5,17 @@ declare(strict_types=1);
|
||||
namespace Pest\Factories;
|
||||
|
||||
use Closure;
|
||||
use ParseError;
|
||||
use Pest\Concerns;
|
||||
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\TestCase;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -139,6 +142,7 @@ final class TestCaseFactory
|
||||
$proxies->proxy($this);
|
||||
$chains->chain($this);
|
||||
|
||||
/* @phpstan-ignore-next-line */
|
||||
return call_user_func(Closure::bind($factoryTest, $this, get_class($this)), ...func_get_args());
|
||||
};
|
||||
|
||||
@ -168,7 +172,7 @@ final class TestCaseFactory
|
||||
}, $filename);
|
||||
}
|
||||
|
||||
$filename = (string) realpath($filename);
|
||||
$filename = str_replace('\\\\', '\\', addslashes((string) realpath($filename)));
|
||||
$rootPath = TestSuite::getInstance()->rootPath;
|
||||
$relativePath = str_replace($rootPath . DIRECTORY_SEPARATOR, '', $filename);
|
||||
$relativePath = dirname(ucfirst($relativePath)) . DIRECTORY_SEPARATOR . basename($relativePath, '.php');
|
||||
@ -176,8 +180,12 @@ final class TestCaseFactory
|
||||
|
||||
// Strip out any %-encoded octets.
|
||||
$relativePath = (string) preg_replace('|%[a-fA-F0-9][a-fA-F0-9]|', '', $relativePath);
|
||||
// Remove escaped quote sequences (maintain namespace)
|
||||
$relativePath = str_replace(array_map(function (string $quote): string {
|
||||
return sprintf('\\%s', $quote);
|
||||
}, ['\'', '"']), '', $relativePath);
|
||||
// Limit to A-Z, a-z, 0-9, '_', '-'.
|
||||
$relativePath = (string) preg_replace('/[^A-Za-z0-9.\\\]/', '', $relativePath);
|
||||
$relativePath = (string) preg_replace('/[^A-Za-z0-9\\\\]/', '', $relativePath);
|
||||
|
||||
$classFQN = 'P\\' . $relativePath;
|
||||
if (class_exists($classFQN)) {
|
||||
@ -194,15 +202,24 @@ final class TestCaseFactory
|
||||
$namespace = implode('\\', $partsFQN);
|
||||
$baseClass = sprintf('\%s', $this->class);
|
||||
|
||||
eval("
|
||||
namespace $namespace;
|
||||
if ('' === trim($className)) {
|
||||
$className = 'InvalidTestName' . Str::random();
|
||||
$classFQN .= $className;
|
||||
}
|
||||
|
||||
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
|
||||
$traitsCode
|
||||
try {
|
||||
eval("
|
||||
namespace $namespace;
|
||||
|
||||
private static \$__filename = '$filename';
|
||||
}
|
||||
");
|
||||
final class $className extends $baseClass implements $hasPrintableTestCaseClassFQN {
|
||||
$traitsCode
|
||||
|
||||
private static \$__filename = '$filename';
|
||||
}
|
||||
");
|
||||
} catch (ParseError $caught) {
|
||||
throw new RuntimeException(sprintf('Unable to create test case for test file at %s', $filename), 1, $caught);
|
||||
}
|
||||
|
||||
return $classFQN;
|
||||
}
|
||||
|
||||
@ -12,95 +12,111 @@ use Pest\Support\HigherOrderTapProxy;
|
||||
use Pest\TestSuite;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* Runs the given closure before all tests in the current file.
|
||||
*/
|
||||
function beforeAll(Closure $closure): void
|
||||
{
|
||||
TestSuite::getInstance()->beforeAll->set($closure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given closure before each test in the current file.
|
||||
*
|
||||
* @return BeforeEachCall|TestCase|mixed
|
||||
*/
|
||||
function beforeEach(Closure $closure = null): BeforeEachCall
|
||||
{
|
||||
$filename = Backtrace::file();
|
||||
|
||||
return new BeforeEachCall(TestSuite::getInstance(), $filename, $closure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given dataset.
|
||||
*
|
||||
* @param Closure|iterable<int|string, mixed> $dataset
|
||||
*/
|
||||
function dataset(string $name, $dataset): void
|
||||
{
|
||||
Datasets::set($name, $dataset);
|
||||
}
|
||||
|
||||
/**
|
||||
* The uses function binds the given
|
||||
* arguments to test closures.
|
||||
*/
|
||||
function uses(string ...$classAndTraits): UsesCall
|
||||
{
|
||||
$filename = Backtrace::file();
|
||||
|
||||
return new UsesCall($filename, $classAndTraits);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given closure as a test. The first argument
|
||||
* is the test description; the second argument is
|
||||
* a closure that contains the test expectations.
|
||||
*
|
||||
* @return TestCall|TestCase|mixed
|
||||
*/
|
||||
function test(string $description = null, Closure $closure = null)
|
||||
{
|
||||
if ($description === null && TestSuite::getInstance()->test !== null) {
|
||||
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
|
||||
if (!function_exists('beforeAll')) {
|
||||
/**
|
||||
* Runs the given closure before all tests in the current file.
|
||||
*/
|
||||
function beforeAll(Closure $closure): void
|
||||
{
|
||||
TestSuite::getInstance()->beforeAll->set($closure);
|
||||
}
|
||||
|
||||
$filename = Backtrace::testFile();
|
||||
|
||||
return new TestCall(TestSuite::getInstance(), $filename, $description, $closure);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given closure as a test. The first argument
|
||||
* is the test description; the second argument is
|
||||
* a closure that contains the test expectations.
|
||||
*
|
||||
* @return TestCall|TestCase|mixed
|
||||
*/
|
||||
function it(string $description, Closure $closure = null): TestCall
|
||||
{
|
||||
$description = sprintf('it %s', $description);
|
||||
if (!function_exists('beforeEach')) {
|
||||
/**
|
||||
* Runs the given closure before each test in the current file.
|
||||
*
|
||||
* @return BeforeEachCall|TestCase|mixed
|
||||
*/
|
||||
function beforeEach(Closure $closure = null): BeforeEachCall
|
||||
{
|
||||
$filename = Backtrace::file();
|
||||
|
||||
return test($description, $closure);
|
||||
return new BeforeEachCall(TestSuite::getInstance(), $filename, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given closure after each test in the current file.
|
||||
*
|
||||
* @return AfterEachCall|TestCase|mixed
|
||||
*/
|
||||
function afterEach(Closure $closure = null): AfterEachCall
|
||||
{
|
||||
$filename = Backtrace::file();
|
||||
|
||||
return new AfterEachCall(TestSuite::getInstance(), $filename, $closure);
|
||||
if (!function_exists('dataset')) {
|
||||
/**
|
||||
* Registers the given dataset.
|
||||
*
|
||||
* @param Closure|iterable<int|string, mixed> $dataset
|
||||
*/
|
||||
function dataset(string $name, $dataset): void
|
||||
{
|
||||
Datasets::set($name, $dataset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the given closure after all tests in the current file.
|
||||
*/
|
||||
function afterAll(Closure $closure): void
|
||||
{
|
||||
TestSuite::getInstance()->afterAll->set($closure);
|
||||
if (!function_exists('uses')) {
|
||||
/**
|
||||
* The uses function binds the given
|
||||
* arguments to test closures.
|
||||
*/
|
||||
function uses(string ...$classAndTraits): UsesCall
|
||||
{
|
||||
$filename = Backtrace::file();
|
||||
|
||||
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
|
||||
* a closure that contains the test expectations.
|
||||
*
|
||||
* @return TestCall|TestCase|mixed
|
||||
*/
|
||||
function test(string $description = null, Closure $closure = null)
|
||||
{
|
||||
if ($description === null && TestSuite::getInstance()->test !== null) {
|
||||
return new HigherOrderTapProxy(TestSuite::getInstance()->test);
|
||||
}
|
||||
|
||||
$filename = Backtrace::testFile();
|
||||
|
||||
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
|
||||
* a closure that contains the test expectations.
|
||||
*
|
||||
* @return TestCall|TestCase|mixed
|
||||
*/
|
||||
function it(string $description, Closure $closure = null): TestCall
|
||||
{
|
||||
$description = sprintf('it %s', $description);
|
||||
|
||||
return test($description, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('afterEach')) {
|
||||
/**
|
||||
* Runs the given closure after each test in the current file.
|
||||
*
|
||||
* @return AfterEachCall|TestCase|mixed
|
||||
*/
|
||||
function afterEach(Closure $closure = null): AfterEachCall
|
||||
{
|
||||
$filename = Backtrace::file();
|
||||
|
||||
return new AfterEachCall(TestSuite::getInstance(), $filename, $closure);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('afterAll')) {
|
||||
/**
|
||||
* Runs the given closure after all tests in the current file.
|
||||
*/
|
||||
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);
|
||||
|
||||
@ -16,7 +16,9 @@ final class PestDuskCommand extends DuskCommand
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'pest:dusk {--without-tty : Disable output to TTY}';
|
||||
protected $signature = 'pest:dusk
|
||||
{--browse : Open a browser instead of using headless mode}
|
||||
{--without-tty : Disable output to TTY}';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
|
||||
@ -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);
|
||||
@ -12,7 +12,7 @@ use Pest\TestSuite;
|
||||
use SebastianBergmann\Exporter\Exporter;
|
||||
|
||||
/**
|
||||
* @method \Pest\Expectation expect(mixed $value)
|
||||
* @method \Pest\Expectations\Expectation expect(mixed $value)
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@ -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.0';
|
||||
return '1.3.0';
|
||||
}
|
||||
|
||||
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;
|
||||
@ -24,7 +25,7 @@ final class TestRepository
|
||||
private $state = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<int, array<int, string>>>
|
||||
* @var array<string, array<int, array<int, string|Closure>>>
|
||||
*/
|
||||
private $uses = [];
|
||||
|
||||
@ -46,12 +47,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 {
|
||||
[$classOrTraits, $groups, $hooks] = $uses;
|
||||
|
||||
$setClassName = function (TestCaseFactory $testCase, string $key) use ($path, $classOrTraits, $groups, $startsWith, $hooks): void {
|
||||
[$filename] = explode('@', $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 +64,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 +85,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);
|
||||
@ -92,11 +96,12 @@ final class TestRepository
|
||||
/**
|
||||
* Uses the given `$testCaseClass` on the given `$paths`.
|
||||
*
|
||||
* @param array<int, string> $classOrTraits
|
||||
* @param array<int, string> $groups
|
||||
* @param array<int, string> $paths
|
||||
* @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 +114,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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,13 +12,28 @@ 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
|
||||
{
|
||||
return function () use ($closure, $next): void {
|
||||
/* @phpstan-ignore-next-line */
|
||||
call_user_func_array(Closure::bind($closure, $this, get_class($this)), func_get_args());
|
||||
/* @phpstan-ignore-next-line */
|
||||
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());
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +79,7 @@ final class Container
|
||||
|
||||
if ($candidate === null) {
|
||||
$type = $param->getType();
|
||||
/* @phpstan-ignore-next-line */
|
||||
if ($type !== null && $type->isBuiltin()) {
|
||||
$candidate = $param->getName();
|
||||
} else {
|
||||
|
||||
@ -12,7 +12,6 @@ use ReflectionException;
|
||||
use ReflectionFunction;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionParameter;
|
||||
use ReflectionProperty;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
||||
@ -9,6 +9,25 @@ namespace Pest\Support;
|
||||
*/
|
||||
final class Str
|
||||
{
|
||||
/**
|
||||
* Pool of alpha-numeric characters for generating (unsafe) random strings
|
||||
* from.
|
||||
*/
|
||||
private const POOL = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
/**
|
||||
* Create a (unsecure & non-cryptographically safe) random alpha-numeric
|
||||
* string value.
|
||||
*
|
||||
* @param int $length the length of the resulting randomized string
|
||||
*
|
||||
* @see https://github.com/laravel/framework/blob/4.2/src/Illuminate/Support/Str.php#L240-L242
|
||||
*/
|
||||
public static function random(int $length = 16): string
|
||||
{
|
||||
return substr(str_shuffle(str_repeat(self::POOL, 5)), 0, $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given `$target` starts with the given `$search`.
|
||||
*/
|
||||
|
||||
@ -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
|
||||
@ -63,6 +63,7 @@
|
||||
✓ 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
|
||||
@ -76,9 +77,6 @@
|
||||
✓ 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 'bar' → expect true → toBeTrue
|
||||
✓ get 'foo' → expect true → toBeTrue
|
||||
@ -103,6 +101,39 @@
|
||||
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`
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\ATestWithSpaces
|
||||
✓ it runs file names like `A Test With Spaces.php`
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\AdditionalFileExtensionspec
|
||||
✓ it runs file names like `AdditionalFileExtension.spec.php`
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\ManyExtensionsclasstest
|
||||
✓ it runs file names like `ManyExtensions.class.test.php`
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\TestCaseWithQuotes
|
||||
✓ it runs file names like `Test 'Case' With Quotes.php`
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\kebabcasespec
|
||||
✓ it runs file names like `kebab-case-spec.php`
|
||||
|
||||
PASS Tests\PHPUnit\CustomAffixes\snakecasespec
|
||||
✓ it runs file names like `snake_case_spec.php`
|
||||
|
||||
PASS Tests\PHPUnit\CustomTestCase\UsesPerDirectory
|
||||
✓ closure was bound to CustomTestCase
|
||||
|
||||
@ -137,7 +168,8 @@
|
||||
✓ 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
|
||||
|
||||
PASS Tests\Unit\Plugins\Version
|
||||
✓ it outputs the version when --version is used
|
||||
@ -188,5 +220,5 @@
|
||||
✓ it is a test
|
||||
✓ it uses correct parent class
|
||||
|
||||
Tests: 7 skipped, 108 passed
|
||||
Tests: 7 skipped, 120 passed
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
});
|
||||
@ -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);
|
||||
});
|
||||
10
tests/PHPUnit/CustomAffixes/@#$%^&()-_=+.php
Normal file
10
tests/PHPUnit/CustomAffixes/@#$%^&()-_=+.php
Normal file
@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* NOTE: To preserve cross-platform testing compatibility we cannot use ! * and
|
||||
* other Windows reserved characters in this test's filename.
|
||||
*
|
||||
* See https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions
|
||||
*/
|
||||
|
||||
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);
|
||||
3
tests/PHPUnit/CustomAffixes/A Test With Spaces.php
Normal file
3
tests/PHPUnit/CustomAffixes/A Test With Spaces.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);
|
||||
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);
|
||||
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);
|
||||
3
tests/PHPUnit/CustomAffixes/Test 'Case' With Quotes.php
Normal file
3
tests/PHPUnit/CustomAffixes/Test 'Case' With Quotes.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);
|
||||
3
tests/PHPUnit/CustomAffixes/kebab-case-spec.php
Normal file
3
tests/PHPUnit/CustomAffixes/kebab-case-spec.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);
|
||||
3
tests/PHPUnit/CustomAffixes/snake_case_spec.php
Normal file
3
tests/PHPUnit/CustomAffixes/snake_case_spec.php
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
it(sprintf('runs file names like `%s`', basename(__FILE__)))->assertTrue(true);
|
||||
@ -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,22 @@
|
||||
|
||||
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))');
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -13,7 +13,13 @@ test('visual snapshot of test suite on success', function () {
|
||||
|
||||
$process->run();
|
||||
|
||||
return preg_replace('#\\x1b[[][^A-Za-z]*[A-Za-z]#', '', $process->getOutput());
|
||||
return preg_replace([
|
||||
'#\\x1b[[][^A-Za-z]*[A-Za-z]#',
|
||||
'/(Tests\\\PHPUnit\\\CustomAffixes\\\InvalidTestName)([A-Za-z0-9]*)/',
|
||||
], [
|
||||
'',
|
||||
'$1',
|
||||
], $process->getOutput());
|
||||
};
|
||||
|
||||
if (getenv('REBUILD_SNAPSHOTS')) {
|
||||
|
||||
Reference in New Issue
Block a user