first commit
This commit is contained in:
10
vendor/codeception/codeception/.dockerignore
vendored
Normal file
10
vendor/codeception/codeception/.dockerignore
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
/package
|
||||
/.github
|
||||
/tests
|
||||
/vendor
|
||||
/*.md
|
||||
/*.yml
|
||||
/codecept.bat
|
||||
/nitpick.json
|
||||
/ruleset.xml
|
||||
/.php-cs-fixer.dist.php
|
||||
17
vendor/codeception/codeception/.php-cs-fixer.dist.php
vendored
Normal file
17
vendor/codeception/codeception/.php-cs-fixer.dist.php
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
$finder = PhpCsFixer\Finder::create()
|
||||
->in(__DIR__ . '/src')
|
||||
->in(__DIR__ . '/tests')->notPath('data/Invalid.php')
|
||||
->in(__DIR__ . '/ext')
|
||||
->append([__FILE__]);
|
||||
|
||||
$config = new PhpCsFixer\Config();
|
||||
return $config->setRules([
|
||||
'@PSR12' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'braces' => ['allow_single_line_closure' => true,],
|
||||
'no_spaces_after_function_name' => true,
|
||||
'nullable_type_declaration_for_default_null_value' => true,
|
||||
'single_blank_line_at_eof' => true,
|
||||
])->setFinder($finder);
|
||||
21
vendor/codeception/codeception/LICENSE
vendored
Normal file
21
vendor/codeception/codeception/LICENSE
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011 Michael Bodnarchuk and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
46
vendor/codeception/codeception/app.php
vendored
Normal file
46
vendor/codeception/codeception/app.php
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__ . '/autoload.php';
|
||||
|
||||
use Codeception\Application;
|
||||
|
||||
call_user_func(static function () {
|
||||
$app = new Application('Codeception', Codeception\Codecept::VERSION);
|
||||
$app->add(new Codeception\Command\Build('build'));
|
||||
$app->add(new Codeception\Command\Run('run'));
|
||||
$app->add(new Codeception\Command\Init('init'));
|
||||
$app->add(new Codeception\Command\Console('console'));
|
||||
$app->add(new Codeception\Command\Bootstrap('bootstrap'));
|
||||
$app->add(new Codeception\Command\GenerateCest('generate:cest'));
|
||||
$app->add(new Codeception\Command\GenerateTest('generate:test'));
|
||||
$app->add(new Codeception\Command\GenerateSuite('generate:suite'));
|
||||
$app->add(new Codeception\Command\GenerateHelper('generate:helper'));
|
||||
$app->add(new Codeception\Command\GenerateScenarios('generate:scenarios'));
|
||||
$app->add(new Codeception\Command\Clean('clean'));
|
||||
$app->add(new Codeception\Command\GenerateGroup('generate:groupobject'));
|
||||
$app->add(new Codeception\Command\GeneratePageObject('generate:pageobject'));
|
||||
$app->add(new Codeception\Command\GenerateStepObject('generate:stepobject'));
|
||||
$app->add(new Codeception\Command\GenerateSnapshot('generate:snapshot'));
|
||||
$app->add(new Codeception\Command\GenerateEnvironment('generate:environment'));
|
||||
$app->add(new Codeception\Command\GenerateFeature('generate:feature'));
|
||||
$app->add(new Codeception\Command\GherkinSnippets('gherkin:snippets'));
|
||||
$app->add(new Codeception\Command\GherkinSteps('gherkin:steps'));
|
||||
$app->add(new Codeception\Command\DryRun('dry-run'));
|
||||
$app->add(new Codeception\Command\ConfigValidate('config:validate'));
|
||||
|
||||
// Suggests package
|
||||
if (class_exists('Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand')) {
|
||||
$app->add(new Codeception\Command\Completion());
|
||||
} else {
|
||||
$app->add(new Codeception\Command\CompletionFallback());
|
||||
}
|
||||
|
||||
$app->registerCustomCommands();
|
||||
|
||||
// add only if within a phar archive.
|
||||
if ('phar:' === substr(__FILE__, 0, 5)) {
|
||||
$app->add(new Codeception\Command\SelfUpdate('self-update'));
|
||||
}
|
||||
|
||||
$app->run();
|
||||
});
|
||||
35
vendor/codeception/codeception/autoload.php
vendored
Normal file
35
vendor/codeception/codeception/autoload.php
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
$autoloadFile = './vendor/codeception/codeception/autoload.php';
|
||||
if ((!isset($argv) || (isset($argv) && !in_array('--no-redirect', $argv))) && file_exists('./vendor/autoload.php') && file_exists($autoloadFile) && __FILE__ != realpath($autoloadFile)) {
|
||||
//for global installation or phar file
|
||||
fwrite(
|
||||
STDERR,
|
||||
"\n==== Redirecting to Composer-installed version in vendor/codeception. You can skip this using --no-redirect ====\n"
|
||||
);
|
||||
|
||||
if (file_exists('./vendor/codeception/codeception/app.php')) {
|
||||
//codeception v4+
|
||||
require './vendor/codeception/codeception/app.php';
|
||||
} else {
|
||||
//older version
|
||||
require $autoloadFile;
|
||||
//require package/bin instead of codecept to avoid printing hashbang line
|
||||
require './vendor/codeception/codeception/package/bin';
|
||||
}
|
||||
|
||||
die;
|
||||
} elseif (file_exists(__DIR__ . '/vendor/autoload.php')) {
|
||||
// for phar
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
} elseif (file_exists(__DIR__ . '/../../autoload.php')) {
|
||||
//for composer
|
||||
require_once __DIR__ . '/../../autoload.php';
|
||||
}
|
||||
unset($autoloadFile);
|
||||
if (isset($argv)) {
|
||||
$argv = array_values(array_diff($argv, ['--no-redirect']));
|
||||
}
|
||||
if (isset($_SERVER['argv'])) {
|
||||
$_SERVER['argv'] = array_values(array_diff($_SERVER['argv'], ['--no-redirect']));
|
||||
}
|
||||
7
vendor/codeception/codeception/codecept
vendored
Executable file
7
vendor/codeception/codeception/codecept
vendored
Executable file
@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env php
|
||||
<?php
|
||||
/**
|
||||
* Codeception CLI
|
||||
*/
|
||||
|
||||
require __DIR__ . '/app.php';
|
||||
7
vendor/codeception/codeception/codecept.bat
vendored
Normal file
7
vendor/codeception/codeception/codecept.bat
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
@echo off
|
||||
|
||||
if "%PHP_PEAR_PHP_BIN%" neq "" (
|
||||
set PHPBIN=%PHP_PEAR_PHP_BIN%
|
||||
) else set PHPBIN=php
|
||||
|
||||
"%PHPBIN%" "codecept" %*
|
||||
123
vendor/codeception/codeception/composer.json
vendored
Normal file
123
vendor/codeception/codeception/composer.json
vendored
Normal file
@ -0,0 +1,123 @@
|
||||
{
|
||||
"name":"codeception/codeception",
|
||||
"description":"BDD-style testing framework",
|
||||
"keywords":["BDD", "acceptance testing", "functional testing", "unit testing", "tdd"],
|
||||
"homepage":"https://codeception.com/",
|
||||
"type":"library",
|
||||
"license":"MIT",
|
||||
"authors":[
|
||||
{
|
||||
"name":"Michael Bodnarchuk",
|
||||
"email":"davert.ua@gmail.com",
|
||||
"homepage":"https://codeception.com"
|
||||
}
|
||||
],
|
||||
"minimum-stability": "RC",
|
||||
|
||||
"require": {
|
||||
"php": "^8.1",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-mbstring": "*",
|
||||
"behat/gherkin": "^4.12",
|
||||
"codeception/lib-asserts": "^2.0",
|
||||
"codeception/stub": "^4.1",
|
||||
"phpunit/phpunit": "^9.5.20 || ^10.0 || ^11.0 || ^12.0",
|
||||
"phpunit/php-code-coverage": "^9.2 || ^10.0 || ^11.0 || ^12.0",
|
||||
"phpunit/php-text-template": "^2.0 || ^3.0 || ^4.0 || ^5.0",
|
||||
"phpunit/php-timer": "^5.0.3 || ^6.0 || ^7.0 || ^8.0",
|
||||
"sebastian/comparator": "^4.0.5 || ^5.0 || ^6.0 || ^7.0",
|
||||
"sebastian/diff": "^4.0.3 || ^5.0 || ^6.0 || ^7.0",
|
||||
"symfony/console": ">=5.4.24 <8.0",
|
||||
"symfony/css-selector": ">=5.4.24 <8.0",
|
||||
"symfony/event-dispatcher": ">=5.4.24 <8.0",
|
||||
"symfony/finder": ">=5.4.24 <8.0",
|
||||
"symfony/yaml": ">=5.4.24 <8.0",
|
||||
"symfony/var-dumper": ">=5.4.24 <8.0",
|
||||
"psy/psysh": "^0.11.2 || ^0.12"
|
||||
},
|
||||
"require-dev": {
|
||||
"ext-simplexml": "*",
|
||||
"codeception/lib-innerbrowser": "*@dev",
|
||||
"codeception/lib-web": "*@dev",
|
||||
"codeception/module-asserts": "*@dev",
|
||||
"codeception/module-cli": "*@dev",
|
||||
"codeception/module-db": "*@dev",
|
||||
"codeception/module-filesystem": "*@dev",
|
||||
"codeception/module-phpbrowser": "*@dev",
|
||||
"codeception/util-universalframework": "*@dev",
|
||||
"symfony/process": ">=5.4.24 <8.0",
|
||||
"symfony/dotenv": ">=5.4.24 <8.0",
|
||||
"vlucas/phpdotenv": "^5.1",
|
||||
"jetbrains/phpstorm-attributes": "^1.0"
|
||||
},
|
||||
"conflict": {
|
||||
"codeception/lib-innerbrowser": "<3.1.3",
|
||||
"codeception/module-phpbrowser": "<2.5",
|
||||
"codeception/module-filesystem": "<3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-simplexml": "For loading params from XML files",
|
||||
"vlucas/phpdotenv": "For loading params from .env files",
|
||||
"symfony/dotenv": "For loading params from .env files",
|
||||
"codeception/specify": "BDD-style code blocks",
|
||||
"codeception/verify": "BDD-style assertions",
|
||||
"symfony/phpunit-bridge": "For phpunit-bridge support",
|
||||
"stecman/symfony-console-completion": "For BASH autocompletion"
|
||||
},
|
||||
"replace": {
|
||||
"codeception/phpunit-wrapper": "*"
|
||||
},
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-main": "5.2.x-dev"
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"src/PHPUnit/TestCase.php"
|
||||
],
|
||||
"files": [
|
||||
"functions.php"
|
||||
],
|
||||
"psr-4": {
|
||||
"Codeception\\": "src/Codeception",
|
||||
"Codeception\\Extension\\": "ext"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"classmap": [
|
||||
"tests/cli/_steps",
|
||||
"tests/data/app/data.php",
|
||||
"tests/data/claypit/tests/_data",
|
||||
"tests/data/fail_dependencies",
|
||||
"tests/data/register_command/examples",
|
||||
"tests/data/DummyClass.php",
|
||||
"tests/data/DummyOverloadableClass.php",
|
||||
"tests/data/services/UserModel.php",
|
||||
"tests/data/services/UserService.php",
|
||||
"tests/unit/Codeception/Command/BaseCommandRunner.php",
|
||||
"tests/unit/Codeception/Util/MockAutoload.php",
|
||||
"tests/unit/Codeception/Util/ReflectionTestClass.php"
|
||||
]
|
||||
},
|
||||
"bin":["codecept"],
|
||||
"scripts": {
|
||||
"cs-prod": "phpcs src/ ext/ *.php",
|
||||
"cs-tests": "phpcs tests/ --standard=tests/phpcs.xml"
|
||||
},
|
||||
"scripts-descriptions": {
|
||||
"cs-prod": "Check production code style",
|
||||
"cs-tests": "Check test code style"
|
||||
},
|
||||
"config": {
|
||||
"preferred-install": {
|
||||
"codeception/module-asserts": "source",
|
||||
"codeception/module-db": "source",
|
||||
"codeception/module-filesystem": "source",
|
||||
"codeception/module-phpbrowser": "source",
|
||||
"codeception/lib-innerbrowser": "source",
|
||||
"*": "dist"
|
||||
}
|
||||
}
|
||||
}
|
||||
125
vendor/codeception/codeception/ext/DotReporter.php
vendored
Normal file
125
vendor/codeception/codeception/ext/DotReporter.php
vendored
Normal file
@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Extension;
|
||||
|
||||
use Codeception\Event\FailEvent;
|
||||
use Codeception\Event\PrintResultEvent;
|
||||
use Codeception\Event\TestEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Extension;
|
||||
use Codeception\Subscriber\Console as CodeceptConsole;
|
||||
|
||||
/**
|
||||
* DotReporter provides less verbose output for test execution.
|
||||
* Like PHPUnit printer it prints dots "." for successful tests and "F" for failures.
|
||||
*
|
||||
* 
|
||||
*
|
||||
* ```bash
|
||||
* ..........
|
||||
* ..........
|
||||
* ..........
|
||||
* ..........
|
||||
* ..........
|
||||
* ..........
|
||||
* ..........
|
||||
* ..........
|
||||
*
|
||||
* Time: 2.07 seconds, Memory: 20.00MB
|
||||
*
|
||||
* OK (80 tests, 124 assertions)
|
||||
* ```
|
||||
*
|
||||
*
|
||||
* Enable this reporter with `--ext option`
|
||||
*
|
||||
* ```
|
||||
* codecept run --ext DotReporter
|
||||
* ```
|
||||
*
|
||||
* Failures and Errors are printed by a standard Codeception reporter.
|
||||
* Use this extension as an example for building custom reporters.
|
||||
*/
|
||||
class DotReporter extends Extension
|
||||
{
|
||||
protected ?CodeceptConsole $standardReporter = null;
|
||||
|
||||
protected array $errors = [];
|
||||
|
||||
protected array $failures = [];
|
||||
|
||||
protected int $width = 10;
|
||||
|
||||
protected int $currentPos = 0;
|
||||
|
||||
public function _initialize(): void
|
||||
{
|
||||
$this->_reconfigure(['settings' => ['silent' => true]]); // turn off printing for everything else
|
||||
$this->standardReporter = new CodeceptConsole($this->options);
|
||||
$this->width = $this->standardReporter->detectWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* We are listening for events
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $events = [
|
||||
Events::SUITE_BEFORE => 'beforeSuite',
|
||||
Events::TEST_SUCCESS => 'success',
|
||||
Events::TEST_FAIL => 'fail',
|
||||
Events::TEST_ERROR => 'error',
|
||||
Events::TEST_SKIPPED => 'skipped',
|
||||
Events::TEST_FAIL_PRINT => 'printFailed',
|
||||
Events::RESULT_PRINT_AFTER => 'afterResult',
|
||||
];
|
||||
|
||||
public function beforeSuite(): void
|
||||
{
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
public function success(): void
|
||||
{
|
||||
$this->printChar('.');
|
||||
}
|
||||
|
||||
public function fail(FailEvent $event): void
|
||||
{
|
||||
$this->printChar('<error>F</error>');
|
||||
}
|
||||
|
||||
public function error(FailEvent $event): void
|
||||
{
|
||||
$this->printChar('<error>E</error>');
|
||||
}
|
||||
|
||||
public function skipped(): void
|
||||
{
|
||||
$this->printChar('S');
|
||||
}
|
||||
|
||||
protected function printChar(string $char): void
|
||||
{
|
||||
if ($this->currentPos >= $this->width) {
|
||||
$this->output->writeln('');
|
||||
$this->currentPos = 0;
|
||||
}
|
||||
$this->write($char);
|
||||
++$this->currentPos;
|
||||
}
|
||||
|
||||
public function printFailed(FailEvent $event): void
|
||||
{
|
||||
$this->standardReporter->printFail($event);
|
||||
}
|
||||
|
||||
public function afterResult(PrintResultEvent $event): void
|
||||
{
|
||||
$this->output->writeln('');
|
||||
$this->output->writeln('');
|
||||
$this->standardReporter->afterResult($event);
|
||||
}
|
||||
}
|
||||
148
vendor/codeception/codeception/ext/Logger.php
vendored
Normal file
148
vendor/codeception/codeception/ext/Logger.php
vendored
Normal file
@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Extension;
|
||||
|
||||
use Codeception\Event\FailEvent;
|
||||
use Codeception\Event\StepEvent;
|
||||
use Codeception\Event\SuiteEvent;
|
||||
use Codeception\Event\TestEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Exception\ExtensionException;
|
||||
use Codeception\Extension;
|
||||
use Codeception\Test\Descriptor;
|
||||
use Monolog\Formatter\LineFormatter;
|
||||
use Monolog\Handler\RotatingFileHandler;
|
||||
|
||||
use function class_exists;
|
||||
use function function_exists;
|
||||
use function str_replace;
|
||||
use function ucfirst;
|
||||
|
||||
/**
|
||||
* Log suites/tests/steps using Monolog library.
|
||||
* Monolog should be installed additionally by Composer.
|
||||
*
|
||||
* ```
|
||||
* composer require monolog/monolog
|
||||
* ```
|
||||
*
|
||||
* Codeception's core/internal stuff is logged into `tests/_output/codeception.log`.
|
||||
* Test suites' steps are logged into `tests/_output/<test_full_name>-<rotation_date>.log`.
|
||||
*
|
||||
* To enable this module add to your `codeception.yml`:
|
||||
*
|
||||
* ``` yaml
|
||||
* extensions:
|
||||
* enabled: [Codeception\Extension\Logger]
|
||||
* ```
|
||||
*
|
||||
* #### Config
|
||||
*
|
||||
* * `max_files` (default: 3) - how many log files to keep
|
||||
*
|
||||
*/
|
||||
class Logger extends Extension
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $events = [
|
||||
Events::SUITE_BEFORE => 'beforeSuite',
|
||||
Events::TEST_BEFORE => 'beforeTest',
|
||||
Events::TEST_AFTER => 'afterTest',
|
||||
Events::TEST_END => 'endTest',
|
||||
Events::STEP_BEFORE => 'beforeStep',
|
||||
Events::TEST_FAIL => 'testFail',
|
||||
Events::TEST_ERROR => 'testError',
|
||||
Events::TEST_INCOMPLETE => 'testIncomplete',
|
||||
Events::TEST_SKIPPED => 'testSkipped',
|
||||
];
|
||||
|
||||
protected ?RotatingFileHandler $logHandler = null;
|
||||
|
||||
protected static ?\Monolog\Logger $logger = null;
|
||||
|
||||
protected ?string $path = null;
|
||||
|
||||
/**
|
||||
* @var array<string, int>
|
||||
*/
|
||||
protected array $config = ['max_files' => 3];
|
||||
|
||||
public function _initialize(): void
|
||||
{
|
||||
if (!class_exists('\Monolog\Logger')) {
|
||||
throw new ConfigurationException('Logger extension requires Monolog library to be installed');
|
||||
}
|
||||
$this->path = $this->getLogDir();
|
||||
|
||||
// internal log
|
||||
$logHandler = new RotatingFileHandler($this->path . 'codeception.log', $this->config['max_files']);
|
||||
|
||||
$formatter = $logHandler->getFormatter();
|
||||
if ($formatter instanceof LineFormatter) {
|
||||
$formatter->ignoreEmptyContextAndExtra(true);
|
||||
}
|
||||
|
||||
self::$logger = new \Monolog\Logger('Codeception');
|
||||
self::$logger->pushHandler($logHandler);
|
||||
}
|
||||
|
||||
public static function getLogger(): \Monolog\Logger
|
||||
{
|
||||
return self::$logger;
|
||||
}
|
||||
|
||||
public function beforeSuite(SuiteEvent $event): void
|
||||
{
|
||||
$suiteLogFile = str_replace('\\', '_', $event->getSuite()->getName()) . '.log';
|
||||
$this->logHandler = new RotatingFileHandler($this->path . $suiteLogFile, $this->config['max_files']);
|
||||
}
|
||||
|
||||
public function beforeTest(TestEvent $event): void
|
||||
{
|
||||
self::$logger = new \Monolog\Logger(Descriptor::getTestFullName($event->getTest()));
|
||||
self::$logger->pushHandler($this->logHandler);
|
||||
self::$logger->info('------------------------------------');
|
||||
self::$logger->info('STARTED: ' . ucfirst(Descriptor::getTestAsString($event->getTest())));
|
||||
}
|
||||
|
||||
public function afterTest(TestEvent $event): void
|
||||
{
|
||||
}
|
||||
|
||||
public function endTest(TestEvent $event): void
|
||||
{
|
||||
self::$logger->info('PASSED');
|
||||
}
|
||||
|
||||
public function testFail(FailEvent $event): void
|
||||
{
|
||||
self::$logger->alert($event->getFail()->getMessage());
|
||||
self::$logger->info('# FAILED #');
|
||||
}
|
||||
|
||||
public function testError(FailEvent $event): void
|
||||
{
|
||||
self::$logger->alert($event->getFail()->getMessage());
|
||||
self::$logger->info('# ERROR #');
|
||||
}
|
||||
|
||||
public function testSkipped(FailEvent $event): void
|
||||
{
|
||||
self::$logger->info('# Skipped #');
|
||||
}
|
||||
|
||||
public function testIncomplete(FailEvent $event): void
|
||||
{
|
||||
self::$logger->info('# Incomplete #');
|
||||
}
|
||||
|
||||
public function beforeStep(StepEvent $event): void
|
||||
{
|
||||
self::$logger->info((string) $event->getStep());
|
||||
}
|
||||
}
|
||||
240
vendor/codeception/codeception/ext/README.md
vendored
Normal file
240
vendor/codeception/codeception/ext/README.md
vendored
Normal file
@ -0,0 +1,240 @@
|
||||
# Official Extensions
|
||||
|
||||
## DotReporter
|
||||
|
||||
[See Source](https://github.com/Codeception/Codeception/blob/4.0/ext/DotReporter.php)
|
||||
|
||||
DotReporter provides less verbose output for test execution.
|
||||
Like PHPUnit printer it prints dots "." for successful testes and "F" for failures.
|
||||
|
||||

|
||||
|
||||
```bash
|
||||
..........
|
||||
..........
|
||||
..........
|
||||
..........
|
||||
..........
|
||||
..........
|
||||
..........
|
||||
..........
|
||||
|
||||
Time: 2.07 seconds, Memory: 20.00MB
|
||||
|
||||
OK (80 tests, 124 assertions)
|
||||
```
|
||||
|
||||
|
||||
Enable this reporter with `--ext option`
|
||||
|
||||
```
|
||||
codecept run --ext DotReporter
|
||||
```
|
||||
|
||||
Failures and Errors are printed by a standard Codeception reporter.
|
||||
Use this extension as an example for building custom reporters.
|
||||
|
||||
|
||||
|
||||
## Logger
|
||||
|
||||
[See Source](https://github.com/Codeception/Codeception/blob/4.0/ext/Logger.php)
|
||||
|
||||
Log suites/tests/steps using Monolog library.
|
||||
Monolog should be installed additionally by Composer.
|
||||
|
||||
```
|
||||
composer require monolog/monolog
|
||||
```
|
||||
|
||||
Steps are logged into `tests/_output/codeception.log`
|
||||
|
||||
To enable this module add to your `codeception.yml`:
|
||||
|
||||
``` yaml
|
||||
extensions:
|
||||
enabled: [Codeception\Extension\Logger]
|
||||
```
|
||||
|
||||
#### Config
|
||||
|
||||
* `max_files` (default: 3) - how many log files to keep
|
||||
|
||||
|
||||
|
||||
|
||||
## Recorder
|
||||
|
||||
[See Source](https://github.com/Codeception/Codeception/blob/4.0/ext/Recorder.php)
|
||||
|
||||
Saves a screenshot of each step in acceptance tests and shows them as a slideshow on one HTML page (here's an [example](https://codeception.com/images/recorder.gif))
|
||||
Activated only for suites with WebDriver module enabled.
|
||||
|
||||
The screenshots are saved to `tests/_output/record_*` directories, open `index.html` to see them as a slideshow.
|
||||
|
||||
#### Installation
|
||||
|
||||
Add this to the list of enabled extensions in `codeception.yml` or `acceptance.suite.yml`:
|
||||
|
||||
``` yaml
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\Recorder
|
||||
```
|
||||
|
||||
#### Configuration
|
||||
|
||||
* `delete_successful` (default: true) - delete screenshots for successfully passed tests (i.e. log only failed and errored tests).
|
||||
* `module` (default: WebDriver) - which module for screenshots to use. Set `AngularJS` if you want to use it with AngularJS module. Generally, the module should implement `Codeception\Lib\Interfaces\ScreenshotSaver` interface.
|
||||
* `ignore_steps` (default: []) - array of step names that should not be recorded (given the step passed), * wildcards supported. Meta steps can also be ignored.
|
||||
* `success_color` (default: success) - bootstrap values to be used for color representation for passed tests
|
||||
* `failure_color` (default: danger) - bootstrap values to be used for color representation for failed tests
|
||||
* `error_color` (default: dark) - bootstrap values to be used for color representation for scenarios where there's an issue occurred while generating a recording
|
||||
* `delete_orphaned` (default: false) - delete recording folders created via previous runs
|
||||
* `include_microseconds` (default: false) - enable microsecond precision for recorded step time details
|
||||
|
||||
#### Examples:
|
||||
|
||||
``` yaml
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\Recorder:
|
||||
module: AngularJS # enable for Angular
|
||||
delete_successful: false # keep screenshots of successful tests
|
||||
ignore_steps: [have, grab*]
|
||||
```
|
||||
#### Skipping recording of steps with annotations
|
||||
|
||||
It is also possible to skip recording of steps for specified tests by using the @skipRecording annotation.
|
||||
|
||||
```php
|
||||
/**
|
||||
* @skipRecording login
|
||||
* @skipRecording amOnUrl
|
||||
*\/
|
||||
public function testLogin(AcceptanceTester $I)
|
||||
{
|
||||
$I->login();
|
||||
$I->amOnUrl('https://codeception.com');
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
||||
## RunBefore
|
||||
|
||||
[See Source](https://github.com/Codeception/Codeception/blob/4.0/ext/RunBefore.php)
|
||||
|
||||
Extension for execution of some processes before running tests.
|
||||
|
||||
Processes can be independent and dependent.
|
||||
Independent processes run independently of each other.
|
||||
Dependent processes run sequentially one by one.
|
||||
|
||||
Can be configured in suite config:
|
||||
|
||||
```yaml
|
||||
# acceptance.suite.yml
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunBefore:
|
||||
- independent_process_1
|
||||
-
|
||||
- dependent_process_1_1
|
||||
- dependent_process_1_2
|
||||
- independent_process_2
|
||||
-
|
||||
- dependent_process_2_1
|
||||
- dependent_process_2_2
|
||||
```
|
||||
|
||||
HINT: you can use different configurations per environment.
|
||||
|
||||
|
||||
|
||||
## RunFailed
|
||||
|
||||
[See Source](https://github.com/Codeception/Codeception/blob/4.0/ext/RunFailed.php)
|
||||
|
||||
Saves failed tests into tests/log/failed in order to rerun failed tests.
|
||||
|
||||
To rerun failed tests just run the `failed` group:
|
||||
|
||||
```
|
||||
php codecept run -g failed
|
||||
```
|
||||
|
||||
To change failed group name add:
|
||||
```
|
||||
--override "extensions: config: Codeception\Extension\RunFailed: fail-group: another_group1"
|
||||
```
|
||||
Remember: if you run tests and they generated custom-named fail group, to run this group, you should add override too
|
||||
|
||||
Starting from Codeception 2.1 **this extension is enabled by default**.
|
||||
|
||||
``` yaml
|
||||
extensions:
|
||||
enabled: [Codeception\Extension\RunFailed]
|
||||
```
|
||||
|
||||
On each execution failed tests are logged and saved into `tests/_output/failed` file.
|
||||
|
||||
|
||||
|
||||
## RunProcess
|
||||
|
||||
[See Source](https://github.com/Codeception/Codeception/blob/4.0/ext/RunProcess.php)
|
||||
|
||||
Extension to start and stop processes per suite.
|
||||
Can be used to start/stop selenium server, chromedriver, phantomjs, mailcatcher, etc.
|
||||
|
||||
Can be configured in suite config:
|
||||
|
||||
```yaml
|
||||
# acceptance.suite.yml
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunProcess:
|
||||
- chromedriver
|
||||
```
|
||||
|
||||
Multiple parameters can be passed as array:
|
||||
|
||||
```yaml
|
||||
# acceptance.suite.yml
|
||||
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunProcess:
|
||||
- php -S 127.0.0.1:8000 -t tests/data/app
|
||||
- java -jar ~/selenium-server.jar
|
||||
```
|
||||
|
||||
In the end of a suite all launched processes will be stopped.
|
||||
|
||||
To wait for the process to be launched use `sleep` option.
|
||||
In this case you need configuration to be specified as object:
|
||||
|
||||
```yaml
|
||||
extensions:
|
||||
enabled:
|
||||
- Codeception\Extension\RunProcess:
|
||||
0: java -jar ~/selenium-server.jar
|
||||
1: mailcatcher
|
||||
sleep: 5 # wait 5 seconds for processes to boot
|
||||
```
|
||||
|
||||
HINT: you can use different configurations per environment.
|
||||
|
||||
|
||||
|
||||
## SimpleReporter
|
||||
|
||||
[See Source](https://github.com/Codeception/Codeception/blob/4.0/ext/SimpleReporter.php)
|
||||
|
||||
This extension demonstrates how you can implement console output of your own.
|
||||
Recommended to be used for development purposes only.
|
||||
|
||||
|
||||
|
||||
642
vendor/codeception/codeception/ext/Recorder.php
vendored
Normal file
642
vendor/codeception/codeception/ext/Recorder.php
vendored
Normal file
@ -0,0 +1,642 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Extension;
|
||||
|
||||
use Codeception\Event\StepEvent;
|
||||
use Codeception\Event\TestEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Exception\ExtensionException;
|
||||
use Codeception\Extension;
|
||||
use Codeception\Lib\Interfaces\ScreenshotSaver;
|
||||
use Codeception\Module;
|
||||
use Codeception\Module\WebDriver;
|
||||
use Codeception\Step;
|
||||
use Codeception\Step\Comment as CommentStep;
|
||||
use Codeception\Test\Descriptor;
|
||||
use Codeception\Util\FileSystem;
|
||||
use Codeception\Util\Template;
|
||||
use DateTime;
|
||||
use DirectoryIterator;
|
||||
use Exception;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
use function array_diff;
|
||||
use function array_key_exists;
|
||||
use function array_keys;
|
||||
use function array_merge;
|
||||
use function array_unique;
|
||||
use function basename;
|
||||
use function codecept_output_dir;
|
||||
use function codecept_relative_path;
|
||||
use function dirname;
|
||||
use function file_put_contents;
|
||||
use function in_array;
|
||||
use function is_array;
|
||||
use function is_dir;
|
||||
use function mkdir;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function sprintf;
|
||||
use function str_pad;
|
||||
use function str_replace;
|
||||
use function strcasecmp;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function trim;
|
||||
use function ucfirst;
|
||||
use function uniqid;
|
||||
|
||||
/**
|
||||
* Saves a screenshot of each step in acceptance tests and shows them as a slideshow on one HTML page (here's an [example](https://codeception.com/images/recorder.gif)).
|
||||
* Works only for suites with WebDriver module enabled.
|
||||
*
|
||||
* The screenshots are saved to `tests/_output/record_*` directories, open `index.html` to see them as a slideshow.
|
||||
*
|
||||
* #### Installation
|
||||
*
|
||||
* Add this to the list of enabled extensions in `codeception.yml` or `Acceptance.suite.yml`:
|
||||
*
|
||||
* ``` yaml
|
||||
* extensions:
|
||||
* enabled:
|
||||
* - Codeception\Extension\Recorder
|
||||
* ```
|
||||
*
|
||||
* #### Configuration
|
||||
*
|
||||
* * `delete_successful` (default: true) - delete screenshots for successfully passed tests (i.e. log only failed and errored tests).
|
||||
* * `module` (default: WebDriver) - which module for screenshots to use. Set `AngularJS` if you want to use it with AngularJS module. Generally, the module should implement `Codeception\Lib\Interfaces\ScreenshotSaver` interface.
|
||||
* * `ignore_steps` (default: []) - array of step names that should not be recorded (given the step passed), * wildcards supported. Meta steps can also be ignored.
|
||||
* * `success_color` (default: success) - bootstrap values to be used for color representation for passed tests
|
||||
* * `failure_color` (default: danger) - bootstrap values to be used for color representation for failed tests
|
||||
* * `error_color` (default: dark) - bootstrap values to be used for color representation for scenarios where there's an issue occurred while generating a recording
|
||||
* * `delete_orphaned` (default: false) - delete recording folders created via previous runs
|
||||
* * `include_microseconds` (default: false) - enable microsecond precision for recorded step time details
|
||||
*
|
||||
* #### Examples:
|
||||
*
|
||||
* ``` yaml
|
||||
* extensions:
|
||||
* enabled:
|
||||
* - Codeception\Extension\Recorder:
|
||||
* module: AngularJS # enable for Angular
|
||||
* delete_successful: false # keep screenshots of successful tests
|
||||
* ignore_steps: [have, grab*]
|
||||
* ```
|
||||
* #### Skipping recording of steps with annotations
|
||||
*
|
||||
* It is also possible to skip recording of steps for specified tests by using the `@skipRecording` annotation.
|
||||
*
|
||||
* ```php
|
||||
* /**
|
||||
* * @skipRecording login
|
||||
* * @skipRecording amOnUrl
|
||||
* *\/
|
||||
* public function testLogin(AcceptanceTester $I)
|
||||
* {
|
||||
* $I->login();
|
||||
* $I->amOnUrl('https://codeception.com');
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
class Recorder extends Extension
|
||||
{
|
||||
protected array $config = [
|
||||
'delete_successful' => true,
|
||||
'module' => 'WebDriver',
|
||||
'template' => null,
|
||||
'animate_slides' => true,
|
||||
'ignore_steps' => [],
|
||||
'success_color' => 'success',
|
||||
'failure_color' => 'danger',
|
||||
'error_color' => 'dark',
|
||||
'delete_orphaned' => false,
|
||||
'include_microseconds' => false,
|
||||
];
|
||||
|
||||
protected string $template = <<<EOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Recorder Result</title>
|
||||
|
||||
<!-- Bootstrap Core CSS -->
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
}
|
||||
.active {
|
||||
height: 100%;
|
||||
}
|
||||
.carousel-caption {
|
||||
background: rgba(0,0,0,0.8);
|
||||
}
|
||||
.carousel-caption.error {
|
||||
background: #c0392b !important;
|
||||
}
|
||||
.carousel-item {
|
||||
min-height: 100vh;
|
||||
}
|
||||
.fill {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
overflow-y: scroll;
|
||||
background-position: top;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
background-size: cover;
|
||||
-o-background-size: cover;
|
||||
}
|
||||
.gradient-right {
|
||||
background:
|
||||
linear-gradient(to left, rgba(0,0,0,.4), rgba(0,0,0,.0))
|
||||
}
|
||||
.gradient-left {
|
||||
background:
|
||||
linear-gradient(to right, rgba(0,0,0,.4), rgba(0,0,0,.0))
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="../records.html"></span>Recorded Tests</a>
|
||||
</div>
|
||||
<div class="collapse navbar-collapse" id="navbarText">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<span class="navbar-text">{{feature}}</span>
|
||||
</ul>
|
||||
<span class="navbar-text">{{test}}</span>
|
||||
</div>
|
||||
</nav>
|
||||
<header id="steps" class="carousel slide" data-ride="carousel">
|
||||
<!-- Indicators -->
|
||||
<ol class="carousel-indicators">
|
||||
{{indicators}}
|
||||
</ol>
|
||||
|
||||
<!-- Wrapper for Slides -->
|
||||
<div class="carousel-inner">
|
||||
{{slides}}
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<a class="carousel-control-prev gradient-left" href="#steps" role="button" data-slide="prev">
|
||||
<span class="carousel-control-prev-icon" aria-hidden="false"></span>
|
||||
<span class="sr-only">Previous</span>
|
||||
</a>
|
||||
<a class="carousel-control-next gradient-right" href="#steps" role="button" data-slide="next">
|
||||
<span class="carousel-control-next-icon" aria-hidden="false"></span>
|
||||
<span class="sr-only">Next</span>
|
||||
</a>
|
||||
</header>
|
||||
|
||||
<!-- jQuery -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js"></script>
|
||||
|
||||
<!-- Script to Activate the Carousel -->
|
||||
<script>
|
||||
$('.carousel').carousel({
|
||||
wrap: true,
|
||||
interval: false
|
||||
})
|
||||
|
||||
$(document).bind('keyup', function(e) {
|
||||
if(e.keyCode==39){
|
||||
jQuery('a.carousel-control.right').trigger('click');
|
||||
}
|
||||
|
||||
else if(e.keyCode==37){
|
||||
jQuery('a.carousel-control.left').trigger('click');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
EOF;
|
||||
|
||||
protected string $indicatorTemplate = <<<EOF
|
||||
<li data-target="#steps" data-slide-to="{{step}}" class="{{isActive}}"></li>
|
||||
EOF;
|
||||
|
||||
protected string $indexTemplate = <<<EOF
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Recorder Results Index</title>
|
||||
|
||||
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Navigation -->
|
||||
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
||||
<div class="navbar-header">
|
||||
<a class="navbar-brand" href="#">Recorded Tests
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container py-4">
|
||||
<h1>Record #{{seed}}</h1>
|
||||
<ul>
|
||||
{{records}}
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
EOF;
|
||||
|
||||
protected string $slidesTemplate = <<<EOF
|
||||
<div class="carousel-item {{isActive}}">
|
||||
<img class="mx-auto d-block mh-100" src="{{image}}">
|
||||
<div class="carousel-caption {{isError}}">
|
||||
<h5>{{caption}}</h5>
|
||||
<p>Step finished at <span style="color: #3498db">"{{timeStamp}}"</span></p>
|
||||
</div>
|
||||
</div>
|
||||
EOF;
|
||||
|
||||
public static array $events = [
|
||||
Events::SUITE_BEFORE => 'beforeSuite',
|
||||
Events::SUITE_AFTER => 'afterSuite',
|
||||
Events::TEST_BEFORE => 'before',
|
||||
Events::TEST_ERROR => 'persist',
|
||||
Events::TEST_FAIL => 'persist',
|
||||
Events::TEST_SUCCESS => 'cleanup',
|
||||
Events::STEP_AFTER => 'afterStep',
|
||||
];
|
||||
|
||||
protected ?Module $webDriverModule = null;
|
||||
|
||||
protected ?string $dir = null;
|
||||
|
||||
protected array $slides = [];
|
||||
|
||||
protected int $stepNum = 0;
|
||||
|
||||
protected ?string $seed = null;
|
||||
|
||||
protected array $seeds = [];
|
||||
|
||||
protected array $recordedTests = [];
|
||||
|
||||
protected array $skipRecording = [];
|
||||
|
||||
protected array $errorMessages = [];
|
||||
|
||||
protected bool $colors = false;
|
||||
|
||||
protected bool $ansi = false;
|
||||
|
||||
protected array $timeStamps = [];
|
||||
|
||||
private ?string $dateFormat = null;
|
||||
|
||||
public function beforeSuite(): void
|
||||
{
|
||||
$this->webDriverModule = null;
|
||||
if (!$this->hasModule($this->config['module'])) {
|
||||
$this->writeln('Recorder is disabled, no available modules');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->seed = uniqid();
|
||||
$this->seeds[] = $this->seed;
|
||||
$this->webDriverModule = $this->getModule($this->config['module']);
|
||||
$this->skipRecording = [];
|
||||
$this->errorMessages = [];
|
||||
$this->dateFormat = $this->config['include_microseconds'] ? 'Y-m-d\TH:i:s.uP' : DATE_ATOM;
|
||||
$this->ansi = !isset($this->options['no-ansi']);
|
||||
$this->colors = !isset($this->options['no-colors']);
|
||||
|
||||
if (!$this->webDriverModule instanceof ScreenshotSaver) {
|
||||
throw new ExtensionException(
|
||||
$this,
|
||||
'You should pass module which implements ' . ScreenshotSaver::class . ' interface'
|
||||
);
|
||||
}
|
||||
|
||||
$this->writeln(
|
||||
sprintf(
|
||||
'⏺ <bold>Recording</bold> ⏺ step-by-step screenshots will be saved to <info>%s</info>',
|
||||
codecept_output_dir()
|
||||
)
|
||||
);
|
||||
$this->writeln("Directory Format: <debug>record_{$this->seed}_{filename}_{testname}</debug> ----");
|
||||
}
|
||||
|
||||
public function afterSuite(): void
|
||||
{
|
||||
if (!$this->webDriverModule instanceof Module) {
|
||||
return;
|
||||
}
|
||||
$links = '';
|
||||
|
||||
if ($this->slides !== []) {
|
||||
foreach ($this->recordedTests as $suiteName => $suite) {
|
||||
$links .= "<ul><li><b>{$suiteName}</b></li><ul>";
|
||||
foreach ($suite as $fileName => $tests) {
|
||||
$links .= "<li>{$fileName}</li><ul>";
|
||||
|
||||
foreach ($tests as $test) {
|
||||
$links .= in_array($test['path'], $this->skipRecording, true)
|
||||
? "<li class=\"text{$this->config['error_color']}\">{$test['name']}</li>\n"
|
||||
: '<li class="text-' . $this->config[$test['status'] . '_color']
|
||||
. "\"><a href='{$test['index']}'>{$test['name']}</a></li>\n";
|
||||
}
|
||||
|
||||
$links .= '</ul>';
|
||||
}
|
||||
$links .= '</ul></ul>';
|
||||
}
|
||||
|
||||
$indexHTML = (new Template($this->indexTemplate))
|
||||
->place('seed', $this->seed)
|
||||
->place('records', $links)
|
||||
->produce();
|
||||
|
||||
try {
|
||||
file_put_contents(codecept_output_dir() . 'records.html', $indexHTML);
|
||||
} catch (Exception $exception) {
|
||||
$this->writeln(
|
||||
"⏺ An exception occurred while saving records.html: <info>{$exception->getMessage()}</info>"
|
||||
);
|
||||
}
|
||||
|
||||
$this->writeln('⏺ Records saved into: <info>file://' . codecept_output_dir() . 'records.html</info>');
|
||||
}
|
||||
|
||||
foreach ($this->errorMessages as $message) {
|
||||
$this->writeln($message);
|
||||
}
|
||||
}
|
||||
|
||||
public function before(TestEvent $event): void
|
||||
{
|
||||
if (!$this->webDriverModule instanceof Module) {
|
||||
return;
|
||||
}
|
||||
$this->dir = null;
|
||||
$this->stepNum = 0;
|
||||
$this->slides = [];
|
||||
$this->timeStamps = [];
|
||||
|
||||
$this->dir = codecept_output_dir() . "record_{$this->seed}_{$this->getTestName($event)}";
|
||||
$testPath = codecept_relative_path(Descriptor::getTestFullName($event->getTest()));
|
||||
|
||||
try {
|
||||
!is_dir($this->dir) && !mkdir($this->dir) && !is_dir($this->dir);
|
||||
} catch (Exception $exception) {
|
||||
$this->skipRecording[] = $testPath;
|
||||
$this->appendErrorMessage(
|
||||
$testPath,
|
||||
"⏺ An exception occurred while creating directory: <info>{$this->dir}</info>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function cleanup(TestEvent $event): void
|
||||
{
|
||||
if ($this->config['delete_orphaned']) {
|
||||
$recordingDirectories = [];
|
||||
$directories = new DirectoryIterator(codecept_output_dir());
|
||||
|
||||
// getting a list of currently present recording directories
|
||||
foreach ($directories as $directory) {
|
||||
preg_match('/^record_(.*?)_[^\n]+.php_[^\n]+$/', $directory->getFilename(), $match);
|
||||
if (isset($match[1])) {
|
||||
$recordingDirectories[$match[1]][] = codecept_output_dir() . $directory->getFilename();
|
||||
}
|
||||
}
|
||||
|
||||
// removing orphaned recording directories
|
||||
foreach (array_diff(array_keys($recordingDirectories), $this->seeds) as $orphanedSeed) {
|
||||
foreach ($recordingDirectories[$orphanedSeed] as $orphanedDirectory) {
|
||||
FileSystem::deleteDir($orphanedDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->webDriverModule instanceof Module || !$this->dir) {
|
||||
return;
|
||||
}
|
||||
if (!$this->config['delete_successful']) {
|
||||
$this->persist($event);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// deleting successfully executed tests
|
||||
FileSystem::deleteDir($this->dir);
|
||||
}
|
||||
|
||||
public function persist(TestEvent $event): void
|
||||
{
|
||||
if (!$this->webDriverModule instanceof Module) {
|
||||
return;
|
||||
}
|
||||
$indicatorHtml = '';
|
||||
$slideHtml = '';
|
||||
$testName = $this->getTestName($event);
|
||||
$testPath = codecept_relative_path(Descriptor::getTestFullName($event->getTest()));
|
||||
$dir = codecept_output_dir() . "record_{$this->seed}_$testName";
|
||||
$status = 'success';
|
||||
|
||||
if (strcasecmp($this->dir ?? '', $dir) !== 0) {
|
||||
$filename = str_pad('0', 3, '0', STR_PAD_LEFT) . '.png';
|
||||
|
||||
try {
|
||||
!is_dir($dir) && !mkdir($dir) && !is_dir($dir);
|
||||
$this->dir = $dir;
|
||||
} catch (Exception) {
|
||||
$this->skipRecording[] = $testPath;
|
||||
$this->appendErrorMessage(
|
||||
$testPath,
|
||||
"⏺ An exception occurred while creating directory: <info>{$dir}</info>"
|
||||
);
|
||||
}
|
||||
|
||||
$this->slides = [];
|
||||
$this->timeStamps = [];
|
||||
$this->slides[$filename] = new Step\Action('encountered an unexpected error prior to the test execution');
|
||||
$this->timeStamps[$filename] = (new DateTime())->format($this->dateFormat);
|
||||
$status = 'error';
|
||||
|
||||
try {
|
||||
if ($this->webDriverModule->webDriver === null) {
|
||||
throw new ExtensionException($this, 'Failed to save screenshot as webDriver is not set');
|
||||
}
|
||||
|
||||
$this->webDriverModule->webDriver->takeScreenshot($this->dir . DIRECTORY_SEPARATOR . $filename);
|
||||
} catch (Exception) {
|
||||
$this->appendErrorMessage(
|
||||
$testPath,
|
||||
"⏺ Unable to capture a screenshot for <info>{$testPath}/before</info>"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!in_array($testPath, $this->skipRecording, true)) {
|
||||
foreach ($this->slides as $i => $step) {
|
||||
/** @var Step $step */
|
||||
if ($step->hasFailed()) {
|
||||
$status = 'failure';
|
||||
}
|
||||
|
||||
$indicatorHtml .= (new Template($this->indicatorTemplate))
|
||||
->place('step', (int)$i)
|
||||
->place('isActive', (int)$i !== 0 ? '' : 'active')
|
||||
->produce();
|
||||
|
||||
$slideHtml .= (new Template($this->slidesTemplate))
|
||||
->place('image', $i)
|
||||
->place('caption', $step->getHtml('#3498db'))
|
||||
->place('isActive', (int)$i !== 0 ? '' : 'active')
|
||||
->place('isError', $status === 'success' ? '' : 'error')
|
||||
->place('timeStamp', $this->timeStamps[$i])
|
||||
->produce();
|
||||
}
|
||||
|
||||
$html = (new Template($this->template))
|
||||
->place('indicators', $indicatorHtml)
|
||||
->place('slides', $slideHtml)
|
||||
->place('feature', ucfirst((string) $event->getTest()->getFeature()))
|
||||
->place('test', Descriptor::getTestSignature($event->getTest()))
|
||||
->place('carousel_class', $this->config['animate_slides'] ? ' slide' : '')
|
||||
->produce();
|
||||
|
||||
$indexFile = $this->dir . DIRECTORY_SEPARATOR . 'index.html';
|
||||
$environment = $event->getTest()->getMetadata()->getCurrent('env') ?: '';
|
||||
$suite = ucfirst(basename(dirname($event->getTest()->getMetadata()->getFilename())));
|
||||
$testName = basename($event->getTest()->getMetadata()->getFilename());
|
||||
|
||||
try {
|
||||
file_put_contents($indexFile, $html);
|
||||
} catch (Exception $exception) {
|
||||
$this->skipRecording[] = $testPath;
|
||||
$this->appendErrorMessage(
|
||||
$testPath,
|
||||
"⏺ An exception occurred while saving index.html for <info>{$testPath}: "
|
||||
. "{$exception->getMessage()}</info>"
|
||||
);
|
||||
}
|
||||
|
||||
$this->recordedTests["{$suite} ({$environment})"][$testName][] = [
|
||||
'name' => $event->getTest()->getMetadata()->getName(),
|
||||
'path' => $testPath,
|
||||
'status' => $status,
|
||||
'index' => substr($indexFile, strlen(codecept_output_dir())),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
public function afterStep(StepEvent $event): void
|
||||
{
|
||||
if (!$this->webDriverModule instanceof Module || $this->dir === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->getStep() instanceof CommentStep) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only taking the ignore step into consideration if that step has passed
|
||||
if ($this->isStepIgnored($event) && !$event->getStep()->hasFailed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filename = str_pad((string)$this->stepNum, 3, '0', STR_PAD_LEFT) . '.png';
|
||||
|
||||
try {
|
||||
if ($this->webDriverModule->webDriver === null) {
|
||||
throw new ExtensionException($this, 'Failed to save screenshot as webDriver is not set');
|
||||
}
|
||||
|
||||
$this->webDriverModule->webDriver->takeScreenshot($this->dir . DIRECTORY_SEPARATOR . $filename);
|
||||
} catch (Exception) {
|
||||
$testPath = codecept_relative_path(Descriptor::getTestFullName($event->getTest()));
|
||||
$this->appendErrorMessage(
|
||||
$testPath,
|
||||
"⏺ Unable to capture a screenshot for <info>{$testPath}/{$event->getStep()->getAction()}</info>"
|
||||
);
|
||||
}
|
||||
|
||||
++$this->stepNum;
|
||||
$this->slides[$filename] = $event->getStep();
|
||||
$this->timeStamps[$filename] = (new DateTime())->format($this->dateFormat);
|
||||
}
|
||||
|
||||
protected function isStepIgnored(StepEvent $event): bool
|
||||
{
|
||||
$configIgnoredSteps = $this->config['ignore_steps'];
|
||||
$annotationIgnoredSteps = $event->getTest()->getMetadata()->getParam('skipRecording');
|
||||
|
||||
/** @var string[] $ignoredSteps */
|
||||
$ignoredSteps = array_unique(
|
||||
array_merge(
|
||||
$configIgnoredSteps,
|
||||
is_array($annotationIgnoredSteps) ? $annotationIgnoredSteps : []
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($ignoredSteps as $stepPattern) {
|
||||
$stepRegexp = '/^' . str_replace('*', '.*?', $stepPattern) . '$/i';
|
||||
|
||||
if (preg_match($stepRegexp, $event->getStep()->getAction())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
$event->getStep()->getMetaStep() !== null &&
|
||||
preg_match($stepRegexp, $event->getStep()->getMetaStep()->getAction())
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StepEvent|TestEvent $event
|
||||
*/
|
||||
private function getTestName(Event $event): string
|
||||
{
|
||||
return basename($event->getTest()->getMetadata()->getFilename()) . '_' . preg_replace('/[^A-Za-z0-9\-\_]/', '_', $event->getTest()->getMetadata()->getName());
|
||||
}
|
||||
|
||||
protected function writeln(iterable|string $messages): void
|
||||
{
|
||||
parent::writeln(
|
||||
$this->ansi
|
||||
? $messages
|
||||
: trim(preg_replace('/[ ]{2,}/', ' ', str_replace('⏺', '', $messages)))
|
||||
);
|
||||
}
|
||||
|
||||
private function appendErrorMessage(string $testPath, string $message): void
|
||||
{
|
||||
$this->errorMessages[$testPath] = array_merge(
|
||||
array_key_exists($testPath, $this->errorMessages) ? $this->errorMessages[$testPath] : [],
|
||||
[$message]
|
||||
);
|
||||
}
|
||||
}
|
||||
157
vendor/codeception/codeception/ext/RunBefore.php
vendored
Normal file
157
vendor/codeception/codeception/ext/RunBefore.php
vendored
Normal file
@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Extension;
|
||||
|
||||
use Codeception\Events;
|
||||
use Codeception\Exception\ExtensionException;
|
||||
use Codeception\Extension;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
use function array_shift;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
use function is_array;
|
||||
use function sleep;
|
||||
|
||||
/**
|
||||
* Extension for execution of some processes before running tests.
|
||||
*
|
||||
* Processes can be independent and dependent.
|
||||
* Independent processes run independently of each other.
|
||||
* Dependent processes run sequentially one by one.
|
||||
*
|
||||
* Can be configured in suite config:
|
||||
*
|
||||
* ```yaml
|
||||
* # acceptance.suite.yml
|
||||
* extensions:
|
||||
* enabled:
|
||||
* - Codeception\Extension\RunBefore:
|
||||
* - independent_process_1
|
||||
* -
|
||||
* - dependent_process_1_1
|
||||
* - dependent_process_1_2
|
||||
* - independent_process_2
|
||||
* -
|
||||
* - dependent_process_2_1
|
||||
* - dependent_process_2_2
|
||||
* ```
|
||||
*
|
||||
* HINT: you can use different configurations per environment.
|
||||
*/
|
||||
class RunBefore extends Extension
|
||||
{
|
||||
protected array $config = [];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected static array $events = [
|
||||
Events::SUITE_BEFORE => 'runBefore'
|
||||
];
|
||||
|
||||
private array $processes = [];
|
||||
|
||||
public function _initialize(): void
|
||||
{
|
||||
if (!class_exists(Process::class)) {
|
||||
throw new ExtensionException($this, 'symfony/process package is required');
|
||||
}
|
||||
}
|
||||
|
||||
public function runBefore(): void
|
||||
{
|
||||
$this->runProcesses();
|
||||
$this->processMonitoring();
|
||||
}
|
||||
|
||||
private function runProcesses(): void
|
||||
{
|
||||
foreach ($this->config as $item) {
|
||||
if (is_array($item)) {
|
||||
$currentCommand = array_shift($item);
|
||||
$followingCommands = $item;
|
||||
} else {
|
||||
$currentCommand = $item;
|
||||
$followingCommands = [];
|
||||
}
|
||||
|
||||
$process = $this->runProcess($currentCommand);
|
||||
$this->addProcessToMonitoring($process, $followingCommands);
|
||||
}
|
||||
}
|
||||
|
||||
private function runProcess(string $command): Process
|
||||
{
|
||||
$this->output->debug('[RunBefore] Starting ' . $command);
|
||||
|
||||
$process = Process::fromShellCommandline($command, $this->getRootDir());
|
||||
$process->start();
|
||||
|
||||
return $process;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $followingCommands
|
||||
*/
|
||||
private function addProcessToMonitoring(Process $process, array $followingCommands): void
|
||||
{
|
||||
$this->processes[] = [
|
||||
'instance' => $process,
|
||||
'following' => $followingCommands
|
||||
];
|
||||
}
|
||||
|
||||
private function removeProcessFromMonitoring(int $index): void
|
||||
{
|
||||
unset($this->processes[$index]);
|
||||
}
|
||||
|
||||
private function processMonitoring(): void
|
||||
{
|
||||
while ($this->processes !== []) {
|
||||
$this->checkProcesses();
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
private function checkProcesses(): void
|
||||
{
|
||||
foreach ($this->processes as $index => $process) {
|
||||
/**
|
||||
* @var Process $processInstance
|
||||
*/
|
||||
$processInstance = $process['instance'];
|
||||
|
||||
if (!$this->isRunning($processInstance)) {
|
||||
if (!$processInstance->isSuccessful()) {
|
||||
$this->output->debug('[RunBefore] Failed ' . $processInstance->getCommandLine());
|
||||
$this->output->writeln('<error>' . $processInstance->getErrorOutput() . '</error>');
|
||||
exit(1);
|
||||
}
|
||||
|
||||
$this->output->debug('[RunBefore] Completed ' . $processInstance->getCommandLine());
|
||||
$this->runFollowingCommand($process['following']);
|
||||
$this->removeProcessFromMonitoring($index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $followingCommands
|
||||
*/
|
||||
private function runFollowingCommand(array $followingCommands): void
|
||||
{
|
||||
if ($followingCommands !== []) {
|
||||
$process = $this->runProcess(array_shift($followingCommands));
|
||||
$this->addProcessToMonitoring($process, $followingCommands);
|
||||
}
|
||||
}
|
||||
|
||||
private function isRunning(Process $process): bool
|
||||
{
|
||||
return $process->isRunning();
|
||||
}
|
||||
}
|
||||
96
vendor/codeception/codeception/ext/RunFailed.php
vendored
Normal file
96
vendor/codeception/codeception/ext/RunFailed.php
vendored
Normal file
@ -0,0 +1,96 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Extension;
|
||||
|
||||
use Codeception\Event\PrintResultEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Extension;
|
||||
use Codeception\Test\Descriptor;
|
||||
|
||||
use function array_key_exists;
|
||||
use function file_put_contents;
|
||||
use function implode;
|
||||
use function is_file;
|
||||
use function realpath;
|
||||
use function str_replace;
|
||||
use function strlen;
|
||||
use function substr;
|
||||
use function unlink;
|
||||
|
||||
/**
|
||||
* Saves failed tests into `tests/_output/failed` in order to rerun failed tests.
|
||||
*
|
||||
* To rerun failed tests just run the `failed` group:
|
||||
*
|
||||
* ```
|
||||
* php codecept run -g failed
|
||||
* ```
|
||||
*
|
||||
* To change failed group name add:
|
||||
* ```
|
||||
* --override "extensions: config: Codeception\Extension\RunFailed: fail-group: another_group1"
|
||||
* ```
|
||||
* Remember: If you run tests and they generated custom-named fail group, to run this group, you should add override too
|
||||
*
|
||||
* **This extension is enabled by default.**
|
||||
*
|
||||
* ``` yaml
|
||||
* extensions:
|
||||
* enabled: [Codeception\Extension\RunFailed]
|
||||
* ```
|
||||
*
|
||||
* On each execution failed tests are logged and saved into `tests/_output/failed` file.
|
||||
*/
|
||||
class RunFailed extends Extension
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $events = [
|
||||
Events::RESULT_PRINT_AFTER => 'saveFailed'
|
||||
];
|
||||
|
||||
/** @var string filename/groupname for failed tests */
|
||||
protected string $group = 'failed';
|
||||
|
||||
public function _initialize(): void
|
||||
{
|
||||
if (array_key_exists('fail-group', $this->config) && $this->config['fail-group']) {
|
||||
$this->group = $this->config['fail-group'];
|
||||
}
|
||||
$logPath = str_replace($this->getRootDir(), '', $this->getLogDir()); // get local path to logs
|
||||
$this->_reconfigure(['groups' => [$this->group => $logPath . $this->group]]);
|
||||
}
|
||||
|
||||
public function saveFailed(PrintResultEvent $event): void
|
||||
{
|
||||
$file = $this->getLogDir() . $this->group;
|
||||
$result = $event->getResult();
|
||||
if ($result->wasSuccessful()) {
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
}
|
||||
return;
|
||||
}
|
||||
$output = [];
|
||||
foreach ($result->failures() as $fail) {
|
||||
$output[] = $this->localizePath(Descriptor::getTestFullName($fail->getTest()));
|
||||
}
|
||||
foreach ($result->errors() as $fail) {
|
||||
$output[] = $this->localizePath(Descriptor::getTestFullName($fail->getTest()));
|
||||
}
|
||||
|
||||
file_put_contents($file, implode("\n", $output));
|
||||
}
|
||||
|
||||
protected function localizePath(string $path): string
|
||||
{
|
||||
$root = realpath($this->getRootDir()) . DIRECTORY_SEPARATOR;
|
||||
if (str_starts_with($path, $root)) {
|
||||
return substr($path, strlen($root));
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
143
vendor/codeception/codeception/ext/RunProcess.php
vendored
Normal file
143
vendor/codeception/codeception/ext/RunProcess.php
vendored
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Extension;
|
||||
|
||||
use BadMethodCallException;
|
||||
use Codeception\Events;
|
||||
use Codeception\Exception\ExtensionException;
|
||||
use Codeception\Extension;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
use function array_reverse;
|
||||
use function class_exists;
|
||||
use function is_int;
|
||||
use function sleep;
|
||||
|
||||
/**
|
||||
* Extension to start and stop processes per suite.
|
||||
* Can be used to start/stop selenium server, chromedriver, [MailCatcher](https://mailcatcher.me/), etc.
|
||||
* Each command is executed only once, at the beginning of the test suite. To execute a command before each test, see [Before/After Attributes](https://codeception.com/docs/AdvancedUsage#BeforeAfter-Attributes).
|
||||
*
|
||||
* Can be enabled in suite config:
|
||||
*
|
||||
* ```yaml
|
||||
* # Acceptance.suite.yml
|
||||
* extensions:
|
||||
* enabled:
|
||||
* - Codeception\Extension\RunProcess:
|
||||
* - chromedriver
|
||||
* ```
|
||||
*
|
||||
* Multiple parameters can be passed as array:
|
||||
*
|
||||
* ```yaml
|
||||
* # Acceptance.suite.yml
|
||||
* extensions:
|
||||
* enabled:
|
||||
* - Codeception\Extension\RunProcess:
|
||||
* - php -S 127.0.0.1:8000 -t tests/data/app
|
||||
* - java -jar ~/selenium-server.jar
|
||||
* ```
|
||||
*
|
||||
* In the end of a suite all launched processes will be stopped.
|
||||
*
|
||||
* To wait for the process to be launched use `sleep` option. In this case you need configuration to be specified as object:
|
||||
*
|
||||
* ```yaml
|
||||
* extensions:
|
||||
* enabled:
|
||||
* - Codeception\Extension\RunProcess:
|
||||
* 0: java -jar ~/selenium-server.jar
|
||||
* 1: mailcatcher
|
||||
* sleep: 5 # wait 5 seconds for processes to boot
|
||||
* ```
|
||||
*
|
||||
* HINT: You can use different configurations per environment.
|
||||
*/
|
||||
class RunProcess extends Extension
|
||||
{
|
||||
/**
|
||||
* @var array<int|string, mixed>
|
||||
*/
|
||||
protected array $config = ['sleep' => 0];
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
protected static array $events = [
|
||||
Events::SUITE_BEFORE => 'runProcess',
|
||||
Events::SUITE_AFTER => 'stopProcess'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var Process[]
|
||||
*/
|
||||
private array $processes = [];
|
||||
|
||||
public function _initialize(): void
|
||||
{
|
||||
if (!class_exists(Process::class)) {
|
||||
throw new ExtensionException($this, 'symfony/process package is required');
|
||||
}
|
||||
}
|
||||
|
||||
public function runProcess(): void
|
||||
{
|
||||
$this->processes = [];
|
||||
foreach ($this->config as $key => $command) {
|
||||
if (!$command) {
|
||||
continue;
|
||||
}
|
||||
if (!is_int($key)) {
|
||||
continue; // configuration options
|
||||
}
|
||||
$process = Process::fromShellCommandline($command, $this->getRootDir(), null, null, null);
|
||||
$process->start();
|
||||
$this->processes[] = $process;
|
||||
$this->output->debug('[RunProcess] Starting ' . $command);
|
||||
}
|
||||
sleep($this->config['sleep']);
|
||||
}
|
||||
|
||||
public function __destruct()
|
||||
{
|
||||
$this->stopProcess();
|
||||
}
|
||||
|
||||
public function stopProcess(): void
|
||||
{
|
||||
foreach (array_reverse($this->processes) as $process) {
|
||||
/** @var Process $process */
|
||||
if (!$process->isRunning()) {
|
||||
continue;
|
||||
}
|
||||
$this->output->debug('[RunProcess] Stopping ' . $process->getCommandLine());
|
||||
$process->stop();
|
||||
}
|
||||
$this->processes = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the deserialization of the class to prevent attacker executing
|
||||
* code by leveraging the __destruct method.
|
||||
*
|
||||
* @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection
|
||||
*/
|
||||
public function __sleep()
|
||||
{
|
||||
throw new BadMethodCallException('Cannot serialize ' . self::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the deserialization of the class to prevent attacker executing
|
||||
* code by leveraging the __destruct method.
|
||||
*
|
||||
* @see https://owasp.org/www-community/vulnerabilities/PHP_Object_Injection
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new BadMethodCallException('Cannot unserialize ' . self::class);
|
||||
}
|
||||
}
|
||||
68
vendor/codeception/codeception/ext/SimpleReporter.php
vendored
Normal file
68
vendor/codeception/codeception/ext/SimpleReporter.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Extension;
|
||||
|
||||
use Codeception\Event\TestEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Extension;
|
||||
use Codeception\Test\Descriptor;
|
||||
|
||||
/**
|
||||
* This extension demonstrates how you can implement console output of your own.
|
||||
* Recommended to be used for development purposes only.
|
||||
*/
|
||||
class SimpleReporter extends Extension
|
||||
{
|
||||
public function _initialize(): void
|
||||
{
|
||||
$this->_reconfigure(['settings' => ['silent' => true]]); // turn off printing for everything else
|
||||
}
|
||||
|
||||
/**
|
||||
* We are listening for events
|
||||
*
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $events = [
|
||||
Events::SUITE_BEFORE => 'beforeSuite',
|
||||
Events::TEST_END => 'after',
|
||||
Events::TEST_SUCCESS => 'success',
|
||||
Events::TEST_FAIL => 'fail',
|
||||
Events::TEST_ERROR => 'error',
|
||||
];
|
||||
|
||||
public function beforeSuite(): void
|
||||
{
|
||||
$this->output->writeln('');
|
||||
}
|
||||
|
||||
public function success(): void
|
||||
{
|
||||
$this->output->write('[+] ');
|
||||
}
|
||||
|
||||
public function fail(): void
|
||||
{
|
||||
$this->output->write('[-] ');
|
||||
}
|
||||
|
||||
public function error(): void
|
||||
{
|
||||
$this->output->write('[E] ');
|
||||
}
|
||||
|
||||
// we are printing test status and time taken
|
||||
public function after(TestEvent $event): void
|
||||
{
|
||||
$secondsInput = $event->getTime();
|
||||
// See https://stackoverflow.com/q/16825240
|
||||
$milliseconds = (int)($secondsInput * 1000);
|
||||
$seconds = (int)($milliseconds / 1000);
|
||||
$time = ($seconds % 60) . (($milliseconds === 0) ? '' : '.' . $milliseconds);
|
||||
|
||||
$this->output->write(Descriptor::getTestSignature($event->getTest()));
|
||||
$this->output->writeln(' (' . $time . 's)');
|
||||
}
|
||||
}
|
||||
80
vendor/codeception/codeception/functions.php
vendored
Normal file
80
vendor/codeception/codeception/functions.php
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
// function not autoloaded in PHP, thus its a good place for them
|
||||
use Codeception\Extension\Logger;
|
||||
|
||||
function codecept_debug($data)
|
||||
{
|
||||
\Codeception\Util\Debug::debug($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes interactive pause in ths place
|
||||
*
|
||||
* @param array $vars
|
||||
* @return void
|
||||
*/
|
||||
function codecept_pause(array $vars = []): void
|
||||
{
|
||||
\Codeception\Util\Debug::pause($vars);
|
||||
}
|
||||
|
||||
function codecept_root_dir($appendPath = '')
|
||||
{
|
||||
return \Codeception\Configuration::projectDir() . $appendPath;
|
||||
}
|
||||
|
||||
function codecept_output_dir($appendPath = '')
|
||||
{
|
||||
return \Codeception\Configuration::outputDir() . $appendPath;
|
||||
}
|
||||
|
||||
function codecept_log_dir($appendPath = '')
|
||||
{
|
||||
return \Codeception\Configuration::outputDir() . $appendPath;
|
||||
}
|
||||
|
||||
|
||||
function codecept_data_dir($appendPath = '')
|
||||
{
|
||||
return \Codeception\Configuration::dataDir() . $appendPath;
|
||||
}
|
||||
|
||||
function codecept_relative_path($path)
|
||||
{
|
||||
return \Codeception\Util\PathResolver::getRelativeDir(
|
||||
$path,
|
||||
\Codeception\Configuration::projectDir(),
|
||||
DIRECTORY_SEPARATOR
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If $path is absolute, it will be returned without changes.
|
||||
* If $path is relative, it will be passed to `codecept_root_dir()` function
|
||||
* to make it absolute.
|
||||
*
|
||||
* @param string $path
|
||||
* @return string the absolute path
|
||||
*/
|
||||
function codecept_absolute_path($path)
|
||||
{
|
||||
return codecept_is_path_absolute($path) ? $path : codecept_root_dir($path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given $path is absolute.
|
||||
*
|
||||
* @param string $path
|
||||
* @return bool
|
||||
* @since 2.4.4
|
||||
*/
|
||||
function codecept_is_path_absolute($path)
|
||||
{
|
||||
return \Codeception\Util\PathResolver::isPathAbsolute($path);
|
||||
}
|
||||
|
||||
function codecept_log(): \Monolog\Logger
|
||||
{
|
||||
return Logger::getLogger();
|
||||
}
|
||||
3
vendor/codeception/codeception/package/bin
vendored
Normal file
3
vendor/codeception/codeception/package/bin
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
<?php
|
||||
|
||||
require __DIR__ . '/../app.php';
|
||||
9
vendor/codeception/codeception/phpcs.xml
vendored
Normal file
9
vendor/codeception/codeception/phpcs.xml
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?xml version="1.0"?>
|
||||
<ruleset name="Codeception">
|
||||
<description>Codeception code standard</description>
|
||||
<rule ref="PSR12">
|
||||
<exclude name="Generic.Files.LineLength.TooLong"/>
|
||||
<exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
|
||||
<exclude name="PSR2.Classes.PropertyDeclaration.Underscore"/>
|
||||
</rule>
|
||||
</ruleset>
|
||||
58
vendor/codeception/codeception/src/Codeception/Actor.php
vendored
Normal file
58
vendor/codeception/codeception/src/Codeception/Actor.php
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception;
|
||||
|
||||
use Closure;
|
||||
use Codeception\Lib\Actor\Shared\Comment;
|
||||
use Codeception\Lib\Actor\Shared\Pause;
|
||||
use Codeception\Step\Executor;
|
||||
use RuntimeException;
|
||||
|
||||
abstract class Actor
|
||||
{
|
||||
use Comment;
|
||||
use Pause;
|
||||
|
||||
public function __construct(protected Scenario $scenario)
|
||||
{
|
||||
}
|
||||
|
||||
protected function getScenario(): Scenario
|
||||
{
|
||||
return $this->scenario;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is used by Cept format to add description to test output
|
||||
*
|
||||
* It can be used by Cest format too.
|
||||
* It doesn't do anything when called, but it is parsed by Parser before execution
|
||||
*
|
||||
* @see \Codeception\Lib\Parser::parseFeature
|
||||
*/
|
||||
public function wantTo(string $text): void
|
||||
{
|
||||
}
|
||||
|
||||
public function wantToTest(string $text): void
|
||||
{
|
||||
}
|
||||
|
||||
public function __call(string $method, array $arguments)
|
||||
{
|
||||
$class = static::class;
|
||||
throw new RuntimeException("Call to undefined method {$class}::{$method}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lazy-execution given anonymous function
|
||||
*/
|
||||
public function execute(Closure $callable): self
|
||||
{
|
||||
$this->scenario->addStep(new Executor($callable, []));
|
||||
$callable();
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
182
vendor/codeception/codeception/src/Codeception/Application.php
vendored
Normal file
182
vendor/codeception/codeception/src/Codeception/Application.php
vendored
Normal file
@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception;
|
||||
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Exception;
|
||||
use Symfony\Component\Console\Application as BaseApplication;
|
||||
use Symfony\Component\Console\Input\ArgvInput as SymfonyArgvInput;
|
||||
use Symfony\Component\Console\Input\InputDefinition;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\ConsoleOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class Application extends BaseApplication
|
||||
{
|
||||
protected ?SymfonyArgvInput $coreArguments = null;
|
||||
|
||||
/**
|
||||
* Register commands from config file
|
||||
*
|
||||
* extensions:
|
||||
* commands:
|
||||
* - Project\Command\MyCustomCommand
|
||||
*/
|
||||
public function registerCustomCommands(): void
|
||||
{
|
||||
try {
|
||||
$this->readCustomCommandsFromConfig();
|
||||
} catch (ConfigurationException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
return;
|
||||
}
|
||||
$this->renderExceptionWrapper($e, new ConsoleOutput());
|
||||
exit(1);
|
||||
} catch (Exception $e) {
|
||||
$this->renderExceptionWrapper($e, new ConsoleOutput());
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
public function renderExceptionWrapper(Exception $exception, OutputInterface $output): void
|
||||
{
|
||||
if (method_exists(BaseApplication::class, 'renderException')) {
|
||||
//Symfony 5
|
||||
parent::renderException($exception, $output);
|
||||
} else {
|
||||
parent::renderThrowable($exception, $output);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Search custom commands and register them.
|
||||
*
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected function readCustomCommandsFromConfig(): void
|
||||
{
|
||||
$this->getCoreArguments(); // Maybe load outside config file
|
||||
|
||||
$config = Configuration::config();
|
||||
|
||||
if (empty($config['extensions']['commands'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($config['extensions']['commands'] as $commandClass) {
|
||||
$commandName = $this->getCustomCommandName($commandClass);
|
||||
$this->add(new $commandClass($commandName));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate and get the name of the command
|
||||
*
|
||||
* @param class-string $commandClass A class that implement the `\Codeception\CustomCommandInterface`.
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected function getCustomCommandName(string $commandClass): string
|
||||
{
|
||||
if (!class_exists($commandClass)) {
|
||||
throw new ConfigurationException("Extension: Command class {$commandClass} not found");
|
||||
}
|
||||
|
||||
$interfaces = class_implements($commandClass);
|
||||
|
||||
if (!in_array(CustomCommandInterface::class, $interfaces)) {
|
||||
throw new ConfigurationException("Extension: Command {$commandClass} must implement " .
|
||||
"the interface `Codeception\\CustomCommandInterface`");
|
||||
}
|
||||
|
||||
return $commandClass::getCommandName();
|
||||
}
|
||||
|
||||
/**
|
||||
* To cache Class ArgvInput
|
||||
*
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function run(?InputInterface $input = null, ?OutputInterface $output = null): int
|
||||
{
|
||||
if (!$input instanceof InputInterface) {
|
||||
$input = $this->getCoreArguments();
|
||||
}
|
||||
|
||||
if (!ini_get('register_argc_argv')) {
|
||||
throw new ConfigurationException('register_argc_argv must be set to On for running Codeception');
|
||||
}
|
||||
|
||||
return parent::run($input, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add global a --config option.
|
||||
*/
|
||||
protected function getDefaultInputDefinition(): InputDefinition
|
||||
{
|
||||
$inputDefinition = parent::getDefaultInputDefinition();
|
||||
$inputDefinition->addOption(
|
||||
new InputOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config')
|
||||
);
|
||||
return $inputDefinition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for --config Option and if found will be loaded
|
||||
*
|
||||
* example:
|
||||
* -c file.yml|dir
|
||||
* -cfile.yml|dir
|
||||
* --config file.yml|dir
|
||||
* --config=file.yml|dir
|
||||
*/
|
||||
protected function getCoreArguments(): SymfonyArgvInput
|
||||
{
|
||||
if ($this->coreArguments instanceof SymfonyArgvInput) {
|
||||
return $this->coreArguments;
|
||||
}
|
||||
|
||||
$argvWithoutConfig = [];
|
||||
if (isset($_SERVER['argv'])) {
|
||||
$argv = $_SERVER['argv'];
|
||||
|
||||
for ($i = 0; $i < count($argv); ++$i) {
|
||||
if (preg_match('#^(?:-([^c-]*)?c|--config(?:=|$))(.*)$#', $argv[$i], $match)) {
|
||||
if (!empty($match[2])) { //same index
|
||||
$this->preloadConfiguration($match[2]);
|
||||
} elseif (isset($argv[$i + 1])) { //next index
|
||||
$this->preloadConfiguration($argv[++$i]);
|
||||
}
|
||||
if (!empty($match[1])) {
|
||||
$argvWithoutConfig[] = "-" . $match[1]; //rest commands
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$argvWithoutConfig[] = $argv[$i];
|
||||
}
|
||||
}
|
||||
|
||||
return $this->coreArguments = new SymfonyArgvInput($argvWithoutConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre load Configuration, the config option is use.
|
||||
*
|
||||
* @param string $configFile Path to Configuration
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected function preloadConfiguration(string $configFile): void
|
||||
{
|
||||
try {
|
||||
Configuration::config($configFile);
|
||||
} catch (ConfigurationException $e) {
|
||||
if ($e->getCode() == 404) {
|
||||
throw new ConfigurationException("Your configuration file `{$configFile}` could not be found.", 405);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/After.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/After.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class After
|
||||
{
|
||||
public function __construct(string ...$methodNames)
|
||||
{
|
||||
}
|
||||
}
|
||||
12
vendor/codeception/codeception/src/Codeception/Attribute/AfterClass.php
vendored
Normal file
12
vendor/codeception/codeception/src/Codeception/Attribute/AfterClass.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class AfterClass
|
||||
{
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Before.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Before.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class Before
|
||||
{
|
||||
public function __construct(string ...$methodNames)
|
||||
{
|
||||
}
|
||||
}
|
||||
12
vendor/codeception/codeception/src/Codeception/Attribute/BeforeClass.php
vendored
Normal file
12
vendor/codeception/codeception/src/Codeception/Attribute/BeforeClass.php
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD)]
|
||||
final class BeforeClass
|
||||
{
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/DataProvider.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/DataProvider.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class DataProvider
|
||||
{
|
||||
public function __construct(string $methodName)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Depends.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Depends.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class Depends
|
||||
{
|
||||
public function __construct(string ...$testNames)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Env.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Env.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class Env
|
||||
{
|
||||
public function __construct(string ...$envValues)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Examples.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Examples.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class Examples
|
||||
{
|
||||
public function __construct(mixed ...$values)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Given.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Given.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class Given
|
||||
{
|
||||
public function __construct(string $description)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Group.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Group.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS | Attribute::IS_REPEATABLE)]
|
||||
final class Group
|
||||
{
|
||||
public function __construct(string ...$groups)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Incomplete.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Incomplete.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
|
||||
class Incomplete
|
||||
{
|
||||
public function __construct(string $reason = '')
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Prepare.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Prepare.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class Prepare
|
||||
{
|
||||
public function __construct(string ...$methodNames)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Skip.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Skip.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
|
||||
final class Skip
|
||||
{
|
||||
public function __construct(string $reason = '')
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/Then.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/Then.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class Then
|
||||
{
|
||||
public function __construct(string $description)
|
||||
{
|
||||
}
|
||||
}
|
||||
15
vendor/codeception/codeception/src/Codeception/Attribute/When.php
vendored
Normal file
15
vendor/codeception/codeception/src/Codeception/Attribute/When.php
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Attribute;
|
||||
|
||||
use Attribute;
|
||||
|
||||
#[Attribute(Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
|
||||
final class When
|
||||
{
|
||||
public function __construct(string $description)
|
||||
{
|
||||
}
|
||||
}
|
||||
280
vendor/codeception/codeception/src/Codeception/Codecept.php
vendored
Normal file
280
vendor/codeception/codeception/src/Codeception/Codecept.php
vendored
Normal file
@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception;
|
||||
|
||||
use Codeception\Coverage\Subscriber\Local;
|
||||
use Codeception\Coverage\Subscriber\LocalServer;
|
||||
use Codeception\Coverage\Subscriber\Printer as CoveragePrinter;
|
||||
use Codeception\Coverage\Subscriber\RemoteServer;
|
||||
use Codeception\Event\PrintResultEvent;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Lib\Console\Output;
|
||||
use Codeception\Lib\Interfaces\ConsolePrinter;
|
||||
use Codeception\Lib\Notification;
|
||||
use Codeception\Reporter\HtmlReporter;
|
||||
use Codeception\Reporter\JUnitReporter;
|
||||
use Codeception\Reporter\PhpUnitReporter;
|
||||
use Codeception\Reporter\ReportPrinter;
|
||||
use Codeception\Subscriber\AutoRebuild;
|
||||
use Codeception\Subscriber\BeforeAfterTest;
|
||||
use Codeception\Subscriber\Bootstrap;
|
||||
use Codeception\Subscriber\Console;
|
||||
use Codeception\Subscriber\Dependencies;
|
||||
use Codeception\Subscriber\Deprecation;
|
||||
use Codeception\Subscriber\ErrorHandler;
|
||||
use Codeception\Subscriber\ExtensionLoader;
|
||||
use Codeception\Subscriber\FailFast;
|
||||
use Codeception\Subscriber\GracefulTermination;
|
||||
use Codeception\Subscriber\Module;
|
||||
use Codeception\Subscriber\PrepareTest;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
class Codecept
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const VERSION = '5.2.2';
|
||||
|
||||
protected ResultAggregator $resultAggregator;
|
||||
|
||||
protected EventDispatcher $dispatcher;
|
||||
|
||||
protected ExtensionLoader $extensionLoader;
|
||||
|
||||
protected array $options = [
|
||||
'silent' => false,
|
||||
'debug' => false,
|
||||
'steps' => false,
|
||||
'html' => false,
|
||||
'xml' => false,
|
||||
'phpunit-xml' => false,
|
||||
'no-redirect' => true,
|
||||
'report' => false,
|
||||
'colors' => false,
|
||||
'coverage' => false,
|
||||
'coverage-xml' => false,
|
||||
'coverage-html' => false,
|
||||
'coverage-text' => false,
|
||||
'coverage-crap4j' => false,
|
||||
'coverage-cobertura' => false,
|
||||
'coverage-phpunit' => false,
|
||||
'disable-coverage-php' => false,
|
||||
'groups' => null,
|
||||
'excludeGroups' => null,
|
||||
'filter' => null,
|
||||
'shard' => null,
|
||||
'env' => null,
|
||||
'fail-fast' => 0,
|
||||
'ansi' => true,
|
||||
'verbosity' => 1,
|
||||
'interactive' => true,
|
||||
'no-rebuild' => false,
|
||||
'quiet' => false,
|
||||
];
|
||||
|
||||
protected array $config = [];
|
||||
|
||||
protected array $extensions = [];
|
||||
|
||||
private readonly Output $output;
|
||||
|
||||
public function __construct(array $options = [])
|
||||
{
|
||||
$this->resultAggregator = new ResultAggregator();
|
||||
$this->dispatcher = new EventDispatcher();
|
||||
$this->extensionLoader = new ExtensionLoader($this->dispatcher);
|
||||
|
||||
$baseOptions = $this->mergeOptions($options);
|
||||
$this->extensionLoader->bootGlobalExtensions($baseOptions); // extensions may override config
|
||||
|
||||
$this->config = Configuration::config();
|
||||
$this->options = $this->mergeOptions($options); // options updated from config
|
||||
|
||||
$this->output = new Output($this->options);
|
||||
|
||||
$this->registerSubscribers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges given options with default values and current configuration
|
||||
*
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected function mergeOptions(array $options): array
|
||||
{
|
||||
$config = Configuration::config();
|
||||
$baseOptions = array_merge($this->options, $config['settings']);
|
||||
return array_merge($baseOptions, $options);
|
||||
}
|
||||
|
||||
public function registerSubscribers(): void
|
||||
{
|
||||
// required
|
||||
$this->dispatcher->addSubscriber(new GracefulTermination($this->resultAggregator));
|
||||
$this->dispatcher->addSubscriber(new ErrorHandler());
|
||||
$this->dispatcher->addSubscriber(new Dependencies());
|
||||
$this->dispatcher->addSubscriber(new Bootstrap());
|
||||
$this->dispatcher->addSubscriber(new PrepareTest());
|
||||
$this->dispatcher->addSubscriber(new Module());
|
||||
$this->dispatcher->addSubscriber(new BeforeAfterTest());
|
||||
|
||||
// optional
|
||||
if (!$this->options['no-rebuild']) {
|
||||
$this->dispatcher->addSubscriber(new AutoRebuild());
|
||||
}
|
||||
|
||||
if ($this->options['fail-fast'] > 0) {
|
||||
$this->dispatcher->addSubscriber(new FailFast($this->options['fail-fast'], $this->resultAggregator));
|
||||
}
|
||||
|
||||
if ($this->options['coverage']) {
|
||||
$this->dispatcher->addSubscriber(new Local($this->options));
|
||||
$this->dispatcher->addSubscriber(new LocalServer($this->options));
|
||||
$this->dispatcher->addSubscriber(new RemoteServer($this->options));
|
||||
$this->dispatcher->addSubscriber(new CoveragePrinter($this->options, $this->output));
|
||||
}
|
||||
|
||||
if ($this->options['report']) {
|
||||
$this->dispatcher->addSubscriber(new ReportPrinter($this->options));
|
||||
}
|
||||
|
||||
$this->dispatcher->addSubscriber($this->extensionLoader);
|
||||
$this->extensionLoader->registerGlobalExtensions();
|
||||
|
||||
if (!$this->options['silent'] && !$this->isConsolePrinterSubscribed()) {
|
||||
$this->dispatcher->addSubscriber(new Console($this->options));
|
||||
}
|
||||
|
||||
$this->dispatcher->addSubscriber(new Deprecation($this->options));
|
||||
|
||||
$this->registerReporters();
|
||||
}
|
||||
|
||||
private function isConsolePrinterSubscribed(): bool
|
||||
{
|
||||
foreach ($this->dispatcher->getListeners() as $listeners) {
|
||||
foreach ($listeners as $listener) {
|
||||
if ($listener instanceof ConsolePrinter) {
|
||||
return true;
|
||||
}
|
||||
if (is_array($listener) && $listener[0] instanceof ConsolePrinter) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function registerReporters(): void
|
||||
{
|
||||
if (isset($this->config['reporters'])) {
|
||||
Notification::warning(
|
||||
"'reporters' option is not supported! Custom reporters must be reimplemented as extensions.",
|
||||
''
|
||||
);
|
||||
}
|
||||
if ($this->options['html']) {
|
||||
$this->dispatcher->addSubscriber(
|
||||
new HtmlReporter($this->options, $this->output)
|
||||
);
|
||||
}
|
||||
if ($this->options['xml']) {
|
||||
$this->dispatcher->addSubscriber(
|
||||
new JUnitReporter($this->options, $this->output)
|
||||
);
|
||||
}
|
||||
if ($this->options['phpunit-xml']) {
|
||||
$this->dispatcher->addSubscriber(
|
||||
new PhpUnitReporter($this->options, $this->output)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function run(string $suite, ?string $test = null, ?array $config = null): void
|
||||
{
|
||||
ini_set(
|
||||
'memory_limit',
|
||||
$this->config['settings']['memory_limit'] ?? '1024M'
|
||||
);
|
||||
|
||||
$config = $config ?: Configuration::config();
|
||||
$config = Configuration::suiteSettings($suite, $config);
|
||||
|
||||
$selectedEnvironments = $this->options['env'];
|
||||
|
||||
if (!$selectedEnvironments || empty($config['env'])) {
|
||||
$this->runSuite($config, $suite, $test);
|
||||
return;
|
||||
}
|
||||
|
||||
// Iterate over all unique environment sets and runs the given suite with each of the merged configurations.
|
||||
foreach (array_unique($selectedEnvironments) as $envList) {
|
||||
$envSet = explode(',', (string) $envList);
|
||||
$suiteEnvConfig = $config;
|
||||
|
||||
// contains a list of the environments used in this suite configuration env set.
|
||||
$envConfigs = [];
|
||||
foreach ($envSet as $currentEnv) {
|
||||
// The $settings['env'] actually contains all parsed configuration files as a
|
||||
// filename => filecontents key-value array. If there is no configuration file for the
|
||||
// $currentEnv the merge will be skipped.
|
||||
if (!array_key_exists($currentEnv, $config['env'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Merge configuration consecutively with already build configuration
|
||||
if (is_array($config['env'][$currentEnv])) {
|
||||
$suiteEnvConfig = Configuration::mergeConfigs($suiteEnvConfig, $config['env'][$currentEnv]);
|
||||
}
|
||||
$envConfigs[] = $currentEnv;
|
||||
}
|
||||
|
||||
$suiteEnvConfig['current_environment'] = implode(',', $envConfigs);
|
||||
|
||||
$suiteToRun = $suite;
|
||||
if (!empty($envList)) {
|
||||
$suiteToRun .= ' (' . implode(', ', $envSet) . ')';
|
||||
}
|
||||
$this->runSuite($suiteEnvConfig, $suiteToRun, $test);
|
||||
}
|
||||
}
|
||||
|
||||
public function runSuite(array $settings, string $suite, ?string $test = null): void
|
||||
{
|
||||
$settings['shard'] = $this->options['shard'];
|
||||
$suiteManager = new SuiteManager($this->dispatcher, $suite, $settings, $this->options);
|
||||
$suiteManager->initialize();
|
||||
mt_srand($this->options['seed']);
|
||||
$suiteManager->loadTests($test);
|
||||
mt_srand();
|
||||
$suiteManager->run($this->resultAggregator);
|
||||
}
|
||||
|
||||
public static function versionString(): string
|
||||
{
|
||||
return 'Codeception PHP Testing Framework v' . self::VERSION;
|
||||
}
|
||||
|
||||
public function printResult(): void
|
||||
{
|
||||
$this->dispatcher->dispatch(new PrintResultEvent($this->resultAggregator), Events::RESULT_PRINT_AFTER);
|
||||
}
|
||||
|
||||
public function getResultAggregator(): ResultAggregator
|
||||
{
|
||||
return $this->resultAggregator;
|
||||
}
|
||||
|
||||
public function getOptions(): array
|
||||
{
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
public function getDispatcher(): EventDispatcher
|
||||
{
|
||||
return $this->dispatcher;
|
||||
}
|
||||
}
|
||||
48
vendor/codeception/codeception/src/Codeception/Command/Bootstrap.php
vendored
Normal file
48
vendor/codeception/codeception/src/Codeception/Command/Bootstrap.php
vendored
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Template\Bootstrap as BootstrapTemplate;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Creates default config, tests directory and sample suites for current project.
|
||||
* Use this command to start building a test suite.
|
||||
*
|
||||
* By default, it will create 3 suites **Acceptance**, **Functional**, and **Unit**.
|
||||
*
|
||||
* * `codecept bootstrap` - creates `tests` dir and `codeception.yml` in current dir.
|
||||
* * `codecept bootstrap --empty` - creates `tests` dir without suites
|
||||
* * `codecept bootstrap --namespace Frontend` - creates tests, and use `Frontend` namespace for actor classes and helpers.
|
||||
* * `codecept bootstrap --actor Wizard` - sets actor as Wizard, to have `TestWizard` actor in tests.
|
||||
* * `codecept bootstrap path/to/the/project` - provide different path to a project, where tests should be placed
|
||||
*
|
||||
*/
|
||||
class Bootstrap extends Command
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Creates default test suites and generates all required files')
|
||||
->addArgument('path', InputArgument::OPTIONAL, 'custom installation dir')
|
||||
->addOption('namespace', 's', InputOption::VALUE_OPTIONAL, 'Namespace to add for actor classes and helpers')
|
||||
->addOption('actor', 'a', InputOption::VALUE_OPTIONAL, 'Custom actor instead of Tester')
|
||||
->addOption('empty', 'e', InputOption::VALUE_NONE, "Don't create standard suites");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$bootstrap = new BootstrapTemplate($input, $output);
|
||||
if ($path = $input->getArgument('path')) {
|
||||
$bootstrap->initDir($path);
|
||||
}
|
||||
|
||||
$bootstrap->setup();
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
109
vendor/codeception/codeception/src/Codeception/Command/Build.php
vendored
Normal file
109
vendor/codeception/codeception/src/Codeception/Command/Build.php
vendored
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Lib\Generator\Actions as ActionsGenerator;
|
||||
use Codeception\Lib\Generator\Actor as ActorGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface as SymfonyOutputInterface;
|
||||
|
||||
use function implode;
|
||||
|
||||
/**
|
||||
* Generates Actor classes (initially Guy classes) from suite configs.
|
||||
* Starting from Codeception 2.0 actor classes are auto-generated. Use this command to generate them manually.
|
||||
*
|
||||
* * `codecept build`
|
||||
* * `codecept build path/to/project`
|
||||
*
|
||||
*/
|
||||
class Build extends Command
|
||||
{
|
||||
use Shared\ConfigTrait;
|
||||
use Shared\FileSystemTrait;
|
||||
|
||||
protected string $inheritedMethodTemplate = ' * @method void %s(%s)';
|
||||
|
||||
protected ?SymfonyOutputInterface $output = null;
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Generates base classes for all suites';
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, SymfonyOutputInterface $output): int
|
||||
{
|
||||
$this->output = $output;
|
||||
$this->buildActorsForConfig();
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function buildActor(array $settings): bool
|
||||
{
|
||||
$actorGenerator = new ActorGenerator($settings);
|
||||
$this->output->writeln(
|
||||
'<info>' . Configuration::config()['namespace'] . '\\' . $actorGenerator->getActorName()
|
||||
. "</info> includes modules: " . implode(', ', $actorGenerator->getModules())
|
||||
);
|
||||
|
||||
$content = $actorGenerator->produce();
|
||||
|
||||
$file = $this->createDirectoryFor(
|
||||
Configuration::supportDir(),
|
||||
$settings['actor']
|
||||
) . $this->getShortClassName($settings['actor']);
|
||||
$file .= '.php';
|
||||
return $this->createFile($file, $content);
|
||||
}
|
||||
|
||||
private function buildActions(array $settings): bool
|
||||
{
|
||||
$actionsGenerator = new ActionsGenerator($settings);
|
||||
$content = $actionsGenerator->produce();
|
||||
$this->output->writeln(
|
||||
sprintf(' -> %sActions.php generated successfully. ', $settings['actor'])
|
||||
. $actionsGenerator->getNumMethods() . " methods added"
|
||||
);
|
||||
|
||||
$file = $this->createDirectoryFor(Configuration::supportDir() . '_generated', $settings['actor']);
|
||||
$file .= $this->getShortClassName($settings['actor']) . 'Actions.php';
|
||||
return $this->createFile($file, $content, true);
|
||||
}
|
||||
|
||||
private function buildSuiteActors(): void
|
||||
{
|
||||
$suites = $this->getSuites();
|
||||
if ($suites !== []) {
|
||||
$this->output->writeln("<info>Building Actor classes for suites: " . implode(', ', $suites) . '</info>');
|
||||
}
|
||||
foreach ($suites as $suite) {
|
||||
$settings = $this->getSuiteConfig($suite);
|
||||
if (!$settings['actor']) {
|
||||
continue; // no actor
|
||||
}
|
||||
$this->buildActions($settings);
|
||||
$actorBuilt = $this->buildActor($settings);
|
||||
|
||||
if ($actorBuilt) {
|
||||
$this->output->writeln($settings['actor'] . '.php created.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function buildActorsForConfig(?string $configFile = null): void
|
||||
{
|
||||
$config = $this->getGlobalConfig($configFile);
|
||||
|
||||
$dir = Configuration::projectDir();
|
||||
$this->buildSuiteActors();
|
||||
|
||||
foreach ($config['include'] as $subConfig) {
|
||||
$this->output->writeln("\n<comment>Included Configuration: {$subConfig}</comment>");
|
||||
$this->buildActorsForConfig($dir . DIRECTORY_SEPARATOR . $subConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
47
vendor/codeception/codeception/src/Codeception/Command/Clean.php
vendored
Normal file
47
vendor/codeception/codeception/src/Codeception/Command/Clean.php
vendored
Normal file
@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Util\FileSystem;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Recursively cleans `output` directory and generated code.
|
||||
*
|
||||
* * `codecept clean`
|
||||
*
|
||||
*/
|
||||
class Clean extends Command
|
||||
{
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Recursively cleans log and generated code');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->cleanProjectsRecursively($output, Configuration::projectDir());
|
||||
$output->writeln("Done");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function cleanProjectsRecursively(OutputInterface $output, string $projectDir): void
|
||||
{
|
||||
$config = Configuration::config($projectDir);
|
||||
$logDir = Configuration::outputDir();
|
||||
$output->writeln(sprintf('<info>Cleaning up output %s...</info>', $logDir));
|
||||
FileSystem::doEmptyDir($logDir);
|
||||
|
||||
$subProjects = $config['include'];
|
||||
foreach ($subProjects as $subProject) {
|
||||
$this->cleanProjectsRecursively($output, $projectDir . $subProject);
|
||||
}
|
||||
}
|
||||
}
|
||||
91
vendor/codeception/codeception/src/Codeception/Command/Completion.php
vendored
Normal file
91
vendor/codeception/codeception/src/Codeception/Command/Completion.php
vendored
Normal file
@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\Completion as ConsoleCompletion;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\Completion\CompletionInterface as ConsoleCompletionInterface;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\Completion\ShellPathCompletion;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\CompletionCommand;
|
||||
use Stecman\Component\Symfony\Console\BashCompletion\CompletionHandler;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputDefinition as SymfonyInputDefinition;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
// phpcs:ignoreFile PSR1.Files.SideEffects.FoundWithSymbols
|
||||
if (!class_exists(ConsoleCompletion::class)) {
|
||||
echo "Please install `stecman/symfony-console-completion\n` to enable auto completion";
|
||||
return;
|
||||
}
|
||||
|
||||
class Completion extends CompletionCommand
|
||||
{
|
||||
protected function configureCompletion(CompletionHandler $handler): void
|
||||
{
|
||||
// Can't set for all commands, because it wouldn't work well with generate:suite
|
||||
$suiteCommands = [
|
||||
'run',
|
||||
'config:validate',
|
||||
'console',
|
||||
'dry-run',
|
||||
'generate:cest',
|
||||
'generate:feature',
|
||||
'generate:phpunit',
|
||||
'generate:scenarios',
|
||||
'generate:stepobject',
|
||||
'generate:test',
|
||||
'gherkin:snippets',
|
||||
'gherkin:steps'
|
||||
];
|
||||
|
||||
foreach ($suiteCommands as $suiteCommand) {
|
||||
$handler->addHandler(new ConsoleCompletion(
|
||||
$suiteCommand,
|
||||
'suite',
|
||||
ConsoleCompletionInterface::TYPE_ARGUMENT,
|
||||
Configuration::suites()
|
||||
));
|
||||
}
|
||||
|
||||
$handler->addHandlers([
|
||||
new ShellPathCompletion(
|
||||
ConsoleCompletionInterface::ALL_COMMANDS,
|
||||
'path',
|
||||
ConsoleCompletionInterface::TYPE_ARGUMENT
|
||||
),
|
||||
new ShellPathCompletion(
|
||||
ConsoleCompletionInterface::ALL_COMMANDS,
|
||||
'test',
|
||||
ConsoleCompletionInterface::TYPE_ARGUMENT
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
if ($input->getOption('generate-hook') && $input->getOption('use-vendor-bin')) {
|
||||
global $argv;
|
||||
$argv[0] = 'vendor/bin/' . basename($argv[0]);
|
||||
}
|
||||
|
||||
parent::execute($input, $output);
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function createDefinition(): SymfonyInputDefinition
|
||||
{
|
||||
$definition = parent::createDefinition();
|
||||
$definition->addOption(new InputOption(
|
||||
'use-vendor-bin',
|
||||
null,
|
||||
InputOption::VALUE_NONE,
|
||||
'Use the vendor bin for autocompletion.'
|
||||
));
|
||||
|
||||
return $definition;
|
||||
}
|
||||
}
|
||||
36
vendor/codeception/codeception/src/Codeception/Command/CompletionFallback.php
vendored
Normal file
36
vendor/codeception/codeception/src/Codeception/Command/CompletionFallback.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CompletionFallback extends Command
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('_completion');
|
||||
}
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this
|
||||
->setDescription('BASH completion hook.')
|
||||
->setHidden(true) // Hide from listing
|
||||
->setHelp(<<<END
|
||||
To enable BASH completion, install optional stecman/symfony-console-completion first:
|
||||
|
||||
<comment>composer require stecman/symfony-console-completion</comment>
|
||||
|
||||
END);
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln("Install optional <comment>stecman/symfony-console-completion</comment>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
107
vendor/codeception/codeception/src/Codeception/Command/ConfigValidate.php
vendored
Normal file
107
vendor/codeception/codeception/src/Codeception/Command/ConfigValidate.php
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function codecept_data_dir;
|
||||
use function codecept_output_dir;
|
||||
use function codecept_root_dir;
|
||||
use function implode;
|
||||
use function preg_replace;
|
||||
use function print_r;
|
||||
|
||||
/**
|
||||
* Validates and prints Codeception config.
|
||||
* Use it do debug Yaml configs
|
||||
*
|
||||
* Check config:
|
||||
*
|
||||
* * `codecept config`: check global config
|
||||
* * `codecept config unit`: check suite config
|
||||
*
|
||||
* Load config:
|
||||
*
|
||||
* * `codecept config:validate -c path/to/another/config`: from another dir
|
||||
* * `codecept config:validate -c another_config.yml`: from another config file
|
||||
*
|
||||
* Check overriding config values (like in `run` command)
|
||||
*
|
||||
* * `codecept config:validate -o "settings: shuffle: true"`: enable shuffle
|
||||
* * `codecept config:validate -o "settings: lint: false"`: disable linting
|
||||
* * `codecept config:validate -o "reporters: report: \Custom\Reporter" --report`: use custom reporter
|
||||
*
|
||||
*/
|
||||
class ConfigValidate extends Command
|
||||
{
|
||||
use Shared\ConfigTrait;
|
||||
use Shared\StyleTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Validates and prints config to screen')
|
||||
->addArgument('suite', InputArgument::OPTIONAL, 'To show suite configuration')
|
||||
->addOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config')
|
||||
->addOption('override', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Override config values');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->addStyles($output);
|
||||
|
||||
if ($suite = $input->getArgument('suite')) {
|
||||
$output->write("Validating <bold>{$suite}</bold> config... ");
|
||||
$config = $this->getSuiteConfig($suite);
|
||||
$output->writeln("Ok");
|
||||
$output->writeln("------------------------------\n");
|
||||
$output->writeln("<info>{$suite} Suite Config</info>:\n");
|
||||
$output->writeln($this->formatOutput($config));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$output->write("Validating global config... ");
|
||||
$config = $this->getGlobalConfig();
|
||||
$output->writeln($input->getOption('override'));
|
||||
if (!empty($input->getOption('override'))) {
|
||||
$config = $this->overrideConfig($input->getOption('override'));
|
||||
}
|
||||
|
||||
$output->writeln("Ok");
|
||||
$suites = Configuration::suites();
|
||||
|
||||
$output->writeln("------------------------------\n");
|
||||
$output->writeln("<info>Codeception Config</info>:\n");
|
||||
$output->writeln($this->formatOutput($config));
|
||||
|
||||
$output->writeln('<info>Directories</info>:');
|
||||
$output->writeln("<comment>codecept_root_dir()</comment> " . codecept_root_dir());
|
||||
$output->writeln("<comment>codecept_output_dir()</comment> " . codecept_output_dir());
|
||||
$output->writeln("<comment>codecept_data_dir()</comment> " . codecept_data_dir());
|
||||
$output->writeln('');
|
||||
|
||||
$output->writeln("<info>Available suites</info>: " . implode(', ', $suites));
|
||||
|
||||
foreach ($suites as $suite) {
|
||||
$output->write("Validating suite <bold>{$suite}</bold>... ");
|
||||
$this->getSuiteConfig($suite);
|
||||
$output->writeln('Ok');
|
||||
}
|
||||
|
||||
$output->writeln("Execute <info>codecept config:validate [<suite>]</info> to see config for a suite");
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function formatOutput($config): ?string
|
||||
{
|
||||
$output = print_r($config, true);
|
||||
return preg_replace('#\[(.*?)] =>#', "<fg=yellow>$1</fg=yellow> =>", $output);
|
||||
}
|
||||
}
|
||||
133
vendor/codeception/codeception/src/Codeception/Command/Console.php
vendored
Normal file
133
vendor/codeception/codeception/src/Codeception/Command/Console.php
vendored
Normal file
@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Codecept;
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Event\SuiteEvent;
|
||||
use Codeception\Event\TestEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Lib\Console\Output;
|
||||
use Codeception\Scenario;
|
||||
use Codeception\Suite;
|
||||
use Codeception\SuiteManager;
|
||||
use Codeception\Test\Cept;
|
||||
use Codeception\Util\Debug;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function array_keys;
|
||||
use function file_exists;
|
||||
use function function_exists;
|
||||
use function pcntl_signal;
|
||||
|
||||
/**
|
||||
* Try to execute test commands in run-time. You may try commands before writing the test.
|
||||
*
|
||||
* * `codecept console acceptance` - starts acceptance suite environment. If you use WebDriver you can manipulate browser with Codeception commands.
|
||||
*/
|
||||
class Console extends Command
|
||||
{
|
||||
protected ?Cept $test = null;
|
||||
|
||||
protected ?Codecept $codecept = null;
|
||||
|
||||
protected ?Suite $suite = null;
|
||||
|
||||
protected ?OutputInterface $output = null;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $actions = [];
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Launches interactive test console')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'suite to be executed')
|
||||
->addOption('colors', '', InputOption::VALUE_NONE, 'Use colors in output');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$suiteName = $input->getArgument('suite');
|
||||
$this->output = $output;
|
||||
|
||||
$config = Configuration::config();
|
||||
$settings = Configuration::suiteSettings($suiteName, $config);
|
||||
|
||||
$options = $input->getOptions();
|
||||
$options['debug'] = true;
|
||||
$options['silent'] = true;
|
||||
$options['interactive'] = false;
|
||||
$options['colors'] = true;
|
||||
|
||||
Debug::setOutput(new Output($options));
|
||||
|
||||
$this->codecept = new Codecept($options);
|
||||
$eventDispatcher = $this->codecept->getDispatcher();
|
||||
|
||||
$suiteManager = new SuiteManager($eventDispatcher, $suiteName, $settings, []);
|
||||
$suiteManager->initialize();
|
||||
|
||||
$this->suite = $suiteManager->getSuite();
|
||||
$moduleContainer = $suiteManager->getModuleContainer();
|
||||
|
||||
$this->actions = array_keys($moduleContainer->getActions());
|
||||
|
||||
$this->test = new Cept('', '');
|
||||
$this->test->getMetadata()->setServices([
|
||||
'dispatcher' => $eventDispatcher,
|
||||
'modules' => $moduleContainer
|
||||
]);
|
||||
|
||||
$scenario = new Scenario($this->test);
|
||||
if (!$settings['actor']) {
|
||||
throw new ConfigurationException("Interactive shell can't be started without an actor");
|
||||
}
|
||||
|
||||
if (isset($config['namespace']) && $config['namespace'] !== '') {
|
||||
$settings['actor'] = $config['namespace'] . '\\Support\\' . $settings['actor'];
|
||||
}
|
||||
|
||||
$actor = $settings['actor'];
|
||||
$I = new $actor($scenario);
|
||||
|
||||
$this->listenToSignals();
|
||||
|
||||
$output->writeln("<info>Interactive console started for suite {$suiteName}</info>");
|
||||
$output->writeln("<info>Try Codeception commands without writing a test</info>");
|
||||
|
||||
$suiteEvent = new SuiteEvent($this->suite, $settings);
|
||||
$eventDispatcher->dispatch($suiteEvent, Events::SUITE_INIT);
|
||||
$eventDispatcher->dispatch(new TestEvent($this->test), Events::TEST_PARSED);
|
||||
$eventDispatcher->dispatch(new TestEvent($this->test), Events::TEST_BEFORE);
|
||||
|
||||
if (is_string($settings['bootstrap']) && file_exists($settings['bootstrap'])) {
|
||||
require $settings['bootstrap'];
|
||||
}
|
||||
|
||||
$I->pause();
|
||||
|
||||
$eventDispatcher->dispatch(new TestEvent($this->test), Events::TEST_AFTER);
|
||||
$eventDispatcher->dispatch(new SuiteEvent($this->suite), Events::SUITE_AFTER);
|
||||
|
||||
$output->writeln("<info>Bye-bye!</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function listenToSignals(): void
|
||||
{
|
||||
if (function_exists('pcntl_signal')) {
|
||||
declare(ticks=1);
|
||||
pcntl_signal(SIGINT, SIG_IGN);
|
||||
pcntl_signal(SIGTERM, SIG_IGN);
|
||||
}
|
||||
}
|
||||
}
|
||||
231
vendor/codeception/codeception/src/Codeception/Command/DryRun.php
vendored
Normal file
231
vendor/codeception/codeception/src/Codeception/Command/DryRun.php
vendored
Normal file
@ -0,0 +1,231 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Event\SuiteEvent;
|
||||
use Codeception\Event\TestEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Lib\Generator\Actions;
|
||||
use Codeception\Lib\ModuleContainer;
|
||||
use Codeception\Stub;
|
||||
use Codeception\Subscriber\Bootstrap as BootstrapLoader;
|
||||
use Codeception\Subscriber\Console as ConsolePrinter;
|
||||
use Codeception\SuiteManager;
|
||||
use Codeception\Test\Interfaces\ScenarioDriven;
|
||||
use Codeception\Test\Test;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use ReflectionClass;
|
||||
use ReflectionIntersectionType;
|
||||
use ReflectionMethod;
|
||||
use ReflectionNamedType;
|
||||
use ReflectionUnionType;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
use function ini_set;
|
||||
use function preg_match;
|
||||
use function str_replace;
|
||||
|
||||
/**
|
||||
* Shows step-by-step execution process for scenario driven tests without actually running them.
|
||||
*
|
||||
* * `codecept dry-run acceptance`
|
||||
* * `codecept dry-run acceptance MyCest`
|
||||
* * `codecept dry-run acceptance checkout.feature`
|
||||
* * `codecept dry-run tests/acceptance/MyCest.php`
|
||||
*
|
||||
*/
|
||||
class DryRun extends Command
|
||||
{
|
||||
use Shared\ConfigTrait;
|
||||
use Shared\StyleTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDefinition(
|
||||
[
|
||||
new InputArgument('suite', InputArgument::REQUIRED, 'suite to scan for feature files'),
|
||||
new InputArgument('test', InputArgument::OPTIONAL, 'tests to be loaded'),
|
||||
]
|
||||
);
|
||||
parent::configure();
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Prints step-by-step scenario-driven test or a feature';
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->addStyles($output);
|
||||
$suite = (string)$input->getArgument('suite');
|
||||
$test = $input->getArgument('test');
|
||||
|
||||
$config = $this->getGlobalConfig();
|
||||
ini_set(
|
||||
'memory_limit',
|
||||
$config['settings']['memory_limit'] ?? '1024M'
|
||||
);
|
||||
if (!Configuration::isEmpty() && !$test && str_starts_with($suite, (string)$config['paths']['tests'])) {
|
||||
[, $suite, $test] = $this->matchTestFromFilename($suite, $config['paths']['tests']);
|
||||
}
|
||||
$settings = $this->getSuiteConfig($suite);
|
||||
|
||||
$eventDispatcher = new EventDispatcher();
|
||||
$eventDispatcher->addSubscriber(new ConsolePrinter([
|
||||
'colors' => (!$input->hasParameterOption('--no-ansi') xor $input->hasParameterOption('ansi')),
|
||||
'steps' => true,
|
||||
'verbosity' => OutputInterface::VERBOSITY_VERBOSE,
|
||||
]));
|
||||
$eventDispatcher->addSubscriber(new BootstrapLoader());
|
||||
|
||||
$suiteManager = new SuiteManager($eventDispatcher, $suite, $settings, []);
|
||||
$moduleContainer = $suiteManager->getModuleContainer();
|
||||
foreach (Configuration::modules($settings) as $module) {
|
||||
$this->mockModule($module, $moduleContainer);
|
||||
}
|
||||
$suiteManager->loadTests($test);
|
||||
$tests = $suiteManager->getSuite()->getTests();
|
||||
|
||||
$eventDispatcher->dispatch(new SuiteEvent($suiteManager->getSuite(), $settings), Events::SUITE_INIT);
|
||||
$eventDispatcher->dispatch(new SuiteEvent($suiteManager->getSuite(), $settings), Events::SUITE_BEFORE);
|
||||
|
||||
foreach ($tests as $test) {
|
||||
if ($test instanceof Test && $test instanceof ScenarioDriven) {
|
||||
$this->dryRunTest($output, $eventDispatcher, $test);
|
||||
}
|
||||
}
|
||||
$eventDispatcher->dispatch(new SuiteEvent($suiteManager->getSuite()), Events::SUITE_AFTER);
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function matchTestFromFilename($filename, $testsPath): array
|
||||
{
|
||||
$filename = str_replace(['//', '\/', '\\'], '/', $filename);
|
||||
$res = preg_match("#^{$testsPath}/(.*?)/(.*)$#", $filename, $matches);
|
||||
if (!$res) {
|
||||
throw new InvalidArgumentException("Test file can't be matched");
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
protected function dryRunTest(OutputInterface $output, EventDispatcher $eventDispatcher, Test $test): void
|
||||
{
|
||||
$eventDispatcher->dispatch(new TestEvent($test), Events::TEST_START);
|
||||
$eventDispatcher->dispatch(new TestEvent($test), Events::TEST_BEFORE);
|
||||
try {
|
||||
$test->test();
|
||||
} catch (Exception) {
|
||||
}
|
||||
$eventDispatcher->dispatch(new TestEvent($test), Events::TEST_AFTER);
|
||||
$eventDispatcher->dispatch(new TestEvent($test), Events::TEST_END);
|
||||
|
||||
if ($test->getMetadata()->isBlocked()) {
|
||||
$output->writeln('');
|
||||
if ($skip = $test->getMetadata()->getSkip()) {
|
||||
$output->writeln("<warning> SKIPPED </warning>" . $skip);
|
||||
}
|
||||
if ($incomplete = $test->getMetadata()->getIncomplete()) {
|
||||
$output->writeln("<warning> INCOMPLETE </warning>" . $incomplete);
|
||||
}
|
||||
}
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
private function mockModule(string $moduleName, ModuleContainer $moduleContainer): void
|
||||
{
|
||||
$module = $moduleContainer->getModule($moduleName);
|
||||
$class = new ReflectionClass($module);
|
||||
$methodResults = [];
|
||||
foreach ($class->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
|
||||
if ($method->isConstructor()) {
|
||||
continue;
|
||||
}
|
||||
$methodResults[$method->getName()] = $this->getDefaultResultForMethod($class, $method);
|
||||
}
|
||||
|
||||
$moduleContainer->mock($moduleName, Stub::makeEmpty($module, $methodResults));
|
||||
}
|
||||
|
||||
private function getDefaultResultForMethod(ReflectionClass $class, ReflectionMethod $method): mixed
|
||||
{
|
||||
$returnType = $method->getReturnType();
|
||||
|
||||
if ($returnType === null || $returnType->allowsNull()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($returnType instanceof ReflectionUnionType) {
|
||||
return $this->getDefaultValueOfUnionType($returnType);
|
||||
}
|
||||
if ($returnType instanceof ReflectionIntersectionType) {
|
||||
return $this->returnDefaultValueForIntersectionType($returnType);
|
||||
}
|
||||
if ($returnType->isBuiltin()) {
|
||||
return $this->getDefaultValueForBuiltinType($returnType);
|
||||
}
|
||||
|
||||
$typeName = Actions::stringifyNamedType($returnType, $class);
|
||||
return Stub::makeEmpty($typeName);
|
||||
}
|
||||
|
||||
private function getDefaultValueForBuiltinType(ReflectionNamedType $returnType): mixed
|
||||
{
|
||||
return match ($returnType->getName()) {
|
||||
'mixed', 'void' => null,
|
||||
'string' => '',
|
||||
'int' => 0,
|
||||
'float' => 0.0,
|
||||
'bool' => false,
|
||||
'array' => [],
|
||||
'resource' => fopen('data://text/plain;base64,', 'r'),
|
||||
default => throw new Exception('Unsupported return type ' . $returnType->getName()),
|
||||
};
|
||||
}
|
||||
|
||||
private function getDefaultValueOfUnionType(ReflectionUnionType $returnType): mixed
|
||||
{
|
||||
$unionTypes = $returnType->getTypes();
|
||||
foreach ($unionTypes as $type) {
|
||||
if ($type->isBuiltin()) {
|
||||
return $this->getDefaultValueForBuiltinType($type);
|
||||
}
|
||||
}
|
||||
|
||||
return Stub::makeEmpty($unionTypes[0]);
|
||||
}
|
||||
|
||||
private function returnDefaultValueForIntersectionType(ReflectionIntersectionType $returnType): mixed
|
||||
{
|
||||
$extends = null;
|
||||
$implements = [];
|
||||
foreach ($returnType->getTypes() as $type) {
|
||||
if (class_exists($type->getName())) {
|
||||
$extends = $type;
|
||||
} else {
|
||||
$implements [] = $type;
|
||||
}
|
||||
}
|
||||
$className = uniqid('anonymous_class_');
|
||||
$code = "abstract class $className";
|
||||
if ($extends !== null) {
|
||||
$code .= " extends \\$extends";
|
||||
}
|
||||
if ($implements !== []) {
|
||||
$code .= ' implements ' . implode(', ', $implements);
|
||||
}
|
||||
$code .= ' {}';
|
||||
eval($code);
|
||||
|
||||
return Stub::makeEmpty($className);
|
||||
}
|
||||
}
|
||||
62
vendor/codeception/codeception/src/Codeception/Command/GenerateCest.php
vendored
Normal file
62
vendor/codeception/codeception/src/Codeception/Command/GenerateCest.php
vendored
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Lib\Generator\Cest as CestGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function file_exists;
|
||||
|
||||
/**
|
||||
* Generates Cest (scenario-driven object-oriented test) file:
|
||||
*
|
||||
* * `codecept generate:cest suite Login`
|
||||
* * `codecept g:cest suite subdir/subdir/testnameCest.php`
|
||||
* * `codecept g:cest suite LoginCest -c path/to/project`
|
||||
* * `codecept g:cest "App\Login"`
|
||||
*
|
||||
*/
|
||||
class GenerateCest extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates empty Cest file in suite')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'suite where tests will be put')
|
||||
->addArgument('class', InputArgument::REQUIRED, 'test name');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$suite = $input->getArgument('suite');
|
||||
$class = $input->getArgument('class');
|
||||
|
||||
$config = $this->getSuiteConfig($suite);
|
||||
$className = $this->getShortClassName($class);
|
||||
$path = $this->createDirectoryFor($config['path'], $class);
|
||||
|
||||
$filename = $this->completeSuffix($className, 'Cest');
|
||||
$filename = $path . $filename;
|
||||
|
||||
if (file_exists($filename)) {
|
||||
$output->writeln("<error>Test {$filename} already exists</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$cest = new CestGenerator($class, $config);
|
||||
$res = $this->createFile($filename, $cest->produce());
|
||||
if (!$res) {
|
||||
$output->writeln("<error>Test {$filename} already exists</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$output->writeln("<info>Test was created in {$filename}</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
58
vendor/codeception/codeception/src/Codeception/Command/GenerateEnvironment.php
vendored
Normal file
58
vendor/codeception/codeception/src/Codeception/Command/GenerateEnvironment.php
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Generates empty environment configuration file into envs dir:
|
||||
*
|
||||
* * `codecept g:env firefox`
|
||||
*
|
||||
* Required to have `envs` path to be specified in `codeception.yml`
|
||||
*/
|
||||
class GenerateEnvironment extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates empty environment config')
|
||||
->addArgument('env', InputArgument::REQUIRED, 'Environment name');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$config = $this->getGlobalConfig();
|
||||
if (Configuration::envsDir() === '') {
|
||||
throw new ConfigurationException(
|
||||
"Path for environments configuration is not set.\n"
|
||||
. "Please specify envs path in your `codeception.yml`\n \n"
|
||||
. "envs: tests/_envs"
|
||||
);
|
||||
}
|
||||
|
||||
$relativePath = $config['paths']['envs'];
|
||||
$env = $input->getArgument('env');
|
||||
$file = $env . '.yml';
|
||||
|
||||
$path = $this->createDirectoryFor($relativePath, $file);
|
||||
$saved = $this->createFile($path . $file, sprintf('# `%s` environment config goes here', $env));
|
||||
|
||||
if ($saved) {
|
||||
$output->writeln(sprintf('<info>%s config was created in %s/%s</info>', $env, $relativePath, $file));
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('<error>File %s/%s already exists</error>', $relativePath, $file));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
60
vendor/codeception/codeception/src/Codeception/Command/GenerateFeature.php
vendored
Normal file
60
vendor/codeception/codeception/src/Codeception/Command/GenerateFeature.php
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Lib\Generator\Feature;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function basename;
|
||||
use function preg_match;
|
||||
use function rtrim;
|
||||
|
||||
/**
|
||||
* Generates Feature file (in Gherkin):
|
||||
*
|
||||
* * `codecept generate:feature suite Login`
|
||||
* * `codecept g:feature suite subdir/subdir/login.feature`
|
||||
* * `codecept g:feature suite login.feature -c path/to/project`
|
||||
*
|
||||
*/
|
||||
class GenerateFeature extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates empty feature file in suite')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'suite to be tested')
|
||||
->addArgument('feature', InputArgument::REQUIRED, 'feature to be generated')
|
||||
->addOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$suite = $input->getArgument('suite');
|
||||
$filename = (string)$input->getArgument('feature');
|
||||
|
||||
$config = $this->getSuiteConfig($suite);
|
||||
$this->createDirectoryFor($config['path'], $filename);
|
||||
|
||||
$feature = new Feature(basename($filename));
|
||||
if (!preg_match('#\.feature$#', $filename)) {
|
||||
$filename .= '.feature';
|
||||
}
|
||||
$fullPath = rtrim((string) $config['path'], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $filename;
|
||||
$res = $this->createFile($fullPath, $feature->produce());
|
||||
if (!$res) {
|
||||
$output->writeln("<error>Feature {$filename} already exists</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln("<info>Feature was created in {$fullPath}</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
56
vendor/codeception/codeception/src/Codeception/Command/GenerateGroup.php
vendored
Normal file
56
vendor/codeception/codeception/src/Codeception/Command/GenerateGroup.php
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Lib\Generator\Group as GroupGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function ucfirst;
|
||||
|
||||
/**
|
||||
* Creates empty GroupObject - extension which handles all group events.
|
||||
*
|
||||
* * `codecept g:group Admin`
|
||||
*/
|
||||
class GenerateGroup extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates Group subscriber')
|
||||
->addArgument('group', InputArgument::REQUIRED, 'Group class name');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$config = $this->getGlobalConfig();
|
||||
$groupInputArgument = (string)$input->getArgument('group');
|
||||
|
||||
$class = ucfirst($groupInputArgument);
|
||||
$path = $this->createDirectoryFor(Configuration::supportDir() . 'Group' . DIRECTORY_SEPARATOR, $class);
|
||||
|
||||
$filename = $path . $class . '.php';
|
||||
|
||||
$group = new GroupGenerator($config, $groupInputArgument);
|
||||
$res = $this->createFile($filename, $group->produce());
|
||||
|
||||
if (!$res) {
|
||||
$output->writeln("<error>Group {$filename} already exists</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$output->writeln("<info>Group extension was created in {$filename}</info>");
|
||||
$output->writeln(
|
||||
'To use this group extension, include it to "extensions" option of global Codeception config.'
|
||||
);
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
51
vendor/codeception/codeception/src/Codeception/Command/GenerateHelper.php
vendored
Normal file
51
vendor/codeception/codeception/src/Codeception/Command/GenerateHelper.php
vendored
Normal file
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Lib\Generator\Helper;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function ucfirst;
|
||||
|
||||
/**
|
||||
* Creates empty Helper class.
|
||||
*
|
||||
* * `codecept g:helper MyHelper`
|
||||
* * `codecept g:helper "My\Helper"`
|
||||
*
|
||||
*/
|
||||
class GenerateHelper extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates a new helper')
|
||||
->addArgument('name', InputArgument::REQUIRED, 'Helper name');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$name = ucfirst((string)$input->getArgument('name'));
|
||||
$config = $this->getGlobalConfig();
|
||||
|
||||
$path = $this->createDirectoryFor(Configuration::supportDir() . 'Helper', $name);
|
||||
$filename = $path . $this->getShortClassName($name) . '.php';
|
||||
|
||||
$res = $this->createFile($filename, (new Helper($config, $name))->produce());
|
||||
if ($res) {
|
||||
$output->writeln("<info>Helper {$filename} created</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$output->writeln(sprintf('<error>Error creating helper %s</error>', $filename));
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
70
vendor/codeception/codeception/src/Codeception/Command/GeneratePageObject.php
vendored
Normal file
70
vendor/codeception/codeception/src/Codeception/Command/GeneratePageObject.php
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Lib\Generator\PageObject as PageObjectGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function ucfirst;
|
||||
|
||||
/**
|
||||
* Generates PageObject. Can be generated either globally, or just for one suite.
|
||||
* If PageObject is generated globally it will act as UIMap, without any logic in it.
|
||||
*
|
||||
* * `codecept g:page Login`
|
||||
* * `codecept g:page Registration`
|
||||
* * `codecept g:page acceptance Login`
|
||||
*/
|
||||
class GeneratePageObject extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates empty PageObject class')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'Either suite name or page object name')
|
||||
->addArgument('page', InputArgument::OPTIONAL, 'Page name of pageobject to represent');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$suite = (string)$input->getArgument('suite');
|
||||
$class = $input->getArgument('page');
|
||||
|
||||
if (!$class) {
|
||||
$class = $suite;
|
||||
$suite = '';
|
||||
}
|
||||
|
||||
$conf = $suite
|
||||
? $this->getSuiteConfig($suite)
|
||||
: $this->getGlobalConfig();
|
||||
|
||||
if ($suite) {
|
||||
$suite = DIRECTORY_SEPARATOR . ucfirst($suite);
|
||||
}
|
||||
|
||||
$path = $this->createDirectoryFor(Configuration::supportDir() . 'Page' . $suite, $class);
|
||||
|
||||
$filename = $path . $this->getShortClassName($class) . '.php';
|
||||
|
||||
$output->writeln($filename);
|
||||
|
||||
$pageObject = new PageObjectGenerator($conf, ucfirst($suite) . '\\' . $class);
|
||||
$res = $this->createFile($filename, $pageObject->produce());
|
||||
|
||||
if (!$res) {
|
||||
$output->writeln("<error>PageObject {$filename} already exists</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln("<info>PageObject was created in {$filename}</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
138
vendor/codeception/codeception/src/Codeception/Command/GenerateScenarios.php
vendored
Normal file
138
vendor/codeception/codeception/src/Codeception/Command/GenerateScenarios.php
vendored
Normal file
@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\SuiteManager;
|
||||
use Codeception\Test\Cest;
|
||||
use Codeception\Test\Interfaces\Descriptive;
|
||||
use Codeception\Test\Interfaces\ScenarioDriven;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
use function basename;
|
||||
use function file_exists;
|
||||
use function is_writable;
|
||||
use function mkdir;
|
||||
use function preg_replace;
|
||||
|
||||
/**
|
||||
* Generates user-friendly text scenarios from scenario-driven tests (Cest).
|
||||
*
|
||||
* * `codecept g:scenarios acceptance` - for all acceptance tests
|
||||
* * `codecept g:scenarios acceptance --format html` - in html format
|
||||
* * `codecept g:scenarios acceptance --path doc` - generate scenarios to `doc` dir
|
||||
*/
|
||||
class GenerateScenarios extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates text representation for all scenarios')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'suite from which texts should be generated')
|
||||
->addOption('path', 'p', InputOption::VALUE_REQUIRED, 'Use specified path as destination instead of default')
|
||||
->addOption('single-file', '', InputOption::VALUE_NONE, 'Render all scenarios to only one file')
|
||||
->addOption('format', 'f', InputOption::VALUE_REQUIRED, 'Specify output format: html or text (default)', 'text');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$suite = $input->getArgument('suite');
|
||||
|
||||
$suiteConf = $this->getSuiteConfig($suite);
|
||||
|
||||
$path = $input->getOption('path') ?: Configuration::dataDir() . 'scenarios';
|
||||
|
||||
$format = $input->getOption('format');
|
||||
|
||||
@mkdir($path, 0777, true);
|
||||
|
||||
if (!is_writable($path)) {
|
||||
throw new ConfigurationException(
|
||||
"Path {$path} is not writable. Please, set valid permissions for folder to store scenarios."
|
||||
);
|
||||
}
|
||||
|
||||
$path .= DIRECTORY_SEPARATOR . $suite;
|
||||
if (!$input->getOption('single-file')) {
|
||||
@mkdir($path);
|
||||
}
|
||||
|
||||
$suiteManager = new SuiteManager(new EventDispatcher(), $suite, $suiteConf, []);
|
||||
|
||||
if ($suiteConf['bootstrap'] && file_exists($suiteConf['path'] . $suiteConf['bootstrap'])) {
|
||||
require_once $suiteConf['path'] . $suiteConf['bootstrap'];
|
||||
}
|
||||
|
||||
$tests = $this->getTests($suiteManager);
|
||||
$scenarios = '';
|
||||
|
||||
$output->writeln('<comment>This command is deprecated and will be removed in the next major version of Codeception.</comment>');
|
||||
|
||||
foreach ($tests as $test) {
|
||||
if (!$test instanceof ScenarioDriven || !$test instanceof Descriptive) {
|
||||
continue;
|
||||
}
|
||||
$feature = $test->getScenarioText($format);
|
||||
|
||||
$name = $this->underscore(basename($test->getFileName(), '.php'));
|
||||
|
||||
// create separate file for each test in Cest
|
||||
if ($test instanceof Cest && !$input->getOption('single-file')) {
|
||||
$name .= '.' . $this->underscore($test->getTestMethod());
|
||||
}
|
||||
|
||||
if ($input->getOption('single-file')) {
|
||||
$scenarios .= $feature;
|
||||
$output->writeln("* {$name} rendered");
|
||||
} else {
|
||||
$feature = $this->decorate($feature, $format);
|
||||
$this->createFile($path . DIRECTORY_SEPARATOR . $name . $this->formatExtension($format), $feature, true);
|
||||
$output->writeln("* {$name} generated");
|
||||
}
|
||||
}
|
||||
|
||||
if ($input->getOption('single-file')) {
|
||||
$this->createFile($path . $this->formatExtension($format), $this->decorate($scenarios, $format), true);
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function decorate(string $text, string $format): string
|
||||
{
|
||||
if ($format === 'html') {
|
||||
return "<html><body>{$text}</body></html>";
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
protected function getTests($suiteManager)
|
||||
{
|
||||
$suiteManager->loadTests();
|
||||
return $suiteManager->getSuite()->getTests();
|
||||
}
|
||||
|
||||
protected function formatExtension(string $format): string
|
||||
{
|
||||
return '.' . ($format === 'html' ? 'html' : 'txt');
|
||||
}
|
||||
|
||||
private function underscore(string $name): string
|
||||
{
|
||||
$name = preg_replace('#([A-Z]+)([A-Z][a-z])#', '\\1_\\2', $name);
|
||||
$name = preg_replace('#([a-z\d])([A-Z])#', '\\1_\\2', $name);
|
||||
$name = str_replace(['/', '\\'], ['.', '.'], $name);
|
||||
$name = preg_replace('#_Cept$#', '', $name);
|
||||
return preg_replace('#_Cest$#', '', $name);
|
||||
}
|
||||
}
|
||||
71
vendor/codeception/codeception/src/Codeception/Command/GenerateSnapshot.php
vendored
Normal file
71
vendor/codeception/codeception/src/Codeception/Command/GenerateSnapshot.php
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Lib\Generator\Snapshot as SnapshotGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function ucfirst;
|
||||
|
||||
/**
|
||||
* Generates Snapshot.
|
||||
* Snapshot can be used to test dynamical data.
|
||||
* If suite name is provided, an actor class will be included into placeholder
|
||||
*
|
||||
* * `codecept g:snapshot UserEmails`
|
||||
* * `codecept g:snapshot Products`
|
||||
* * `codecept g:snapshot acceptance UserEmails`
|
||||
*/
|
||||
class GenerateSnapshot extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates empty Snapshot class')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'Suite name or snapshot name)')
|
||||
->addArgument('snapshot', InputArgument::OPTIONAL, 'Name of snapshot');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$suite = (string)$input->getArgument('suite');
|
||||
$class = $input->getArgument('snapshot');
|
||||
|
||||
if (!$class) {
|
||||
$class = $suite;
|
||||
$suite = '';
|
||||
}
|
||||
|
||||
$conf = $suite
|
||||
? $this->getSuiteConfig($suite)
|
||||
: $this->getGlobalConfig();
|
||||
|
||||
if ($suite) {
|
||||
$suite = DIRECTORY_SEPARATOR . ucfirst($suite);
|
||||
}
|
||||
|
||||
$path = $this->createDirectoryFor(Configuration::supportDir() . 'Snapshot' . $suite, $class);
|
||||
|
||||
$filename = $path . $this->getShortClassName($class) . '.php';
|
||||
|
||||
$output->writeln($filename);
|
||||
|
||||
$snapshot = new SnapshotGenerator($conf, ucfirst($suite) . '\\' . $class);
|
||||
$res = $this->createFile($filename, $snapshot->produce());
|
||||
|
||||
if (!$res) {
|
||||
$output->writeln("<error>Snapshot {$filename} already exists</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln("<info>Snapshot was created in {$filename}</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
71
vendor/codeception/codeception/src/Codeception/Command/GenerateStepObject.php
vendored
Normal file
71
vendor/codeception/codeception/src/Codeception/Command/GenerateStepObject.php
vendored
Normal file
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Lib\Generator\StepObject as StepObjectGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Question\Question;
|
||||
|
||||
use function ucfirst;
|
||||
|
||||
/**
|
||||
* Generates StepObject class. You will be asked for steps you want to implement.
|
||||
*
|
||||
* * `codecept g:stepobject acceptance AdminSteps`
|
||||
* * `codecept g:stepobject acceptance UserSteps --silent` - skip action questions
|
||||
*
|
||||
*/
|
||||
class GenerateStepObject extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates empty StepObject class')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'Suite for StepObject')
|
||||
->addArgument('step', InputArgument::REQUIRED, 'StepObject name')
|
||||
->addOption('silent', '', InputOption::VALUE_NONE, 'Skip verification question');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$suite = (string)$input->getArgument('suite');
|
||||
$step = $input->getArgument('step');
|
||||
$config = $this->getSuiteConfig($suite);
|
||||
$class = $this->getShortClassName($step);
|
||||
$path = $this->createDirectoryFor(Configuration::supportDir() . 'Step' . DIRECTORY_SEPARATOR . ucfirst($suite), $step);
|
||||
|
||||
/** @var QuestionHelper $dialog */
|
||||
$dialog = $this->getHelper('question');
|
||||
$filename = $path . $class . '.php';
|
||||
$stepObject = new StepObjectGenerator($config, ucfirst($suite) . '\\' . $step);
|
||||
|
||||
if (!$input->getOption('silent')) {
|
||||
do {
|
||||
$question = new Question('Add action to StepObject class (ENTER to exit): ', null);
|
||||
$action = $dialog->ask($input, $output, $question);
|
||||
if ($action) {
|
||||
$stepObject->createAction($action);
|
||||
}
|
||||
} while ($action);
|
||||
}
|
||||
|
||||
$res = $this->createFile($filename, $stepObject->produce());
|
||||
|
||||
if (!$res) {
|
||||
$output->writeln("<error>StepObject {$filename} already exists</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln("<info>StepObject was created in {$filename}</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
107
vendor/codeception/codeception/src/Codeception/Command/GenerateSuite.php
vendored
Normal file
107
vendor/codeception/codeception/src/Codeception/Command/GenerateSuite.php
vendored
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Lib\Generator\Actor as ActorGenerator;
|
||||
use Codeception\Util\Template;
|
||||
use Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
use function file_exists;
|
||||
use function preg_match;
|
||||
use function ucfirst;
|
||||
|
||||
/**
|
||||
* Create new test suite. Requires suite name and actor name
|
||||
*
|
||||
* * ``
|
||||
* * `codecept g:suite api` -> api + ApiTester
|
||||
* * `codecept g:suite integration Code` -> integration + CodeTester
|
||||
* * `codecept g:suite frontend Front` -> frontend + FrontTester
|
||||
*
|
||||
*/
|
||||
class GenerateSuite extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
use Shared\StyleTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates new test suite')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'suite to be generated')
|
||||
->addArgument('actor', InputArgument::OPTIONAL, 'name of new actor class');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->addStyles($output);
|
||||
$suite = ucfirst((string)$input->getArgument('suite'));
|
||||
$config = $this->getGlobalConfig();
|
||||
$actor = $input->getArgument('actor') ?: $suite . $config['actor_suffix'];
|
||||
|
||||
if ($this->containsInvalidCharacters($suite)) {
|
||||
$output->writeln("<error>Suite name '{$suite}' contains invalid characters. ([A-Za-z0-9_]).</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
$dir = Configuration::testsDir();
|
||||
if (file_exists($dir . $suite . '.suite.yml')) {
|
||||
throw new Exception("Suite configuration file '{$suite}.suite.yml' already exists.");
|
||||
}
|
||||
|
||||
$this->createDirectoryFor($dir . $suite);
|
||||
|
||||
if ($config['settings']['bootstrap']) {
|
||||
// generate bootstrap file
|
||||
$this->createFile(
|
||||
$dir . $suite . DIRECTORY_SEPARATOR . $config['settings']['bootstrap'],
|
||||
"<?php\n",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
$yamlSuiteConfigTemplate = <<<EOF
|
||||
actor: {{actor}}
|
||||
suite_namespace: {{suite_namespace}}
|
||||
modules:
|
||||
# enable helpers as array
|
||||
enabled: []
|
||||
EOF;
|
||||
$yamlSuiteConfig = (new Template($yamlSuiteConfigTemplate))
|
||||
->place('actor', $actor)
|
||||
->place('suite_namespace', $config['namespace'] . '\\' . $suite)
|
||||
->produce();
|
||||
$this->createFile($dir . $suite . '.suite.yml', $yamlSuiteConfig);
|
||||
Configuration::append(Yaml::parse($yamlSuiteConfig));
|
||||
$actorGenerator = new ActorGenerator(Configuration::config());
|
||||
|
||||
$content = $actorGenerator->produce();
|
||||
$file = $this->createDirectoryFor(Configuration::supportDir(), $actor) . $this->getShortClassName($actor) . '.php';
|
||||
$this->createFile($file, $content);
|
||||
|
||||
$output->writeln("Actor <info>" . $actor . "</info> was created in {$file}");
|
||||
|
||||
$output->writeln("Suite config <info>{$suite}.suite.yml</info> was created.");
|
||||
$output->writeln(' ');
|
||||
$output->writeln("Next steps:");
|
||||
$output->writeln("1. Edit <bold>{$suite}.suite.yml</bold> to enable modules for this suite");
|
||||
$output->writeln("2. Create first test with <bold>generate:cest testName</bold> ( or test|cept) command");
|
||||
$output->writeln("3. Run tests of this suite with <bold>codecept run {$suite}</bold> command");
|
||||
|
||||
$output->writeln("<info>Suite {$suite} generated</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
private function containsInvalidCharacters(string $suite): bool
|
||||
{
|
||||
return (bool)preg_match('#[^A-Za-z0-9_]#', $suite);
|
||||
}
|
||||
}
|
||||
52
vendor/codeception/codeception/src/Codeception/Command/GenerateTest.php
vendored
Normal file
52
vendor/codeception/codeception/src/Codeception/Command/GenerateTest.php
vendored
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Lib\Generator\Test as TestGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Generates skeleton for Unit Test that extends `Codeception\TestCase\Test`.
|
||||
*
|
||||
* * `codecept g:test unit User`
|
||||
* * `codecept g:test unit "App\User"`
|
||||
*/
|
||||
class GenerateTest extends Command
|
||||
{
|
||||
use Shared\FileSystemTrait;
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Generates empty unit test file in suite')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'Suite where tests will be put')
|
||||
->addArgument('class', InputArgument::REQUIRED, 'Class name');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$suite = $input->getArgument('suite');
|
||||
$class = $input->getArgument('class');
|
||||
|
||||
$config = $this->getSuiteConfig($suite);
|
||||
|
||||
$className = $this->getShortClassName($class);
|
||||
$path = $this->createDirectoryFor($config['path'], $class);
|
||||
$filename = $path . $this->completeSuffix($className, 'Test');
|
||||
|
||||
$test = new TestGenerator($config, $class);
|
||||
|
||||
$res = $this->createFile($filename, $test->produce());
|
||||
if (!$res) {
|
||||
$output->writeln("<error>Test {$filename} already exists</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
$output->writeln("<info>Test was created in {$filename}</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
68
vendor/codeception/codeception/src/Codeception/Command/GherkinSnippets.php
vendored
Normal file
68
vendor/codeception/codeception/src/Codeception/Command/GherkinSnippets.php
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Lib\Generator\GherkinSnippets as GherkinSnippetsGenerator;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Generates code snippets for matched feature files in a suite.
|
||||
* Code snippets are expected to be implemented in Actor or PageObjects
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* * `codecept gherkin:snippets acceptance` - snippets from all feature of acceptance tests
|
||||
* * `codecept gherkin:snippets acceptance/feature/users` - snippets from `feature/users` dir of acceptance tests
|
||||
* * `codecept gherkin:snippets acceptance user_account.feature` - snippets from a single feature file
|
||||
* * `codecept gherkin:snippets acceptance/feature/users/user_accout.feature` - snippets from feature file in a dir
|
||||
*/
|
||||
class GherkinSnippets extends Command
|
||||
{
|
||||
use Shared\ConfigTrait;
|
||||
use Shared\StyleTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Fetches empty steps from feature files of suite and prints code snippets for them')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'Suite to scan for feature files')
|
||||
->addArgument('test', InputArgument::OPTIONAL, 'Test to be scanned')
|
||||
->addOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->addStyles($output);
|
||||
$suite = $input->getArgument('suite');
|
||||
$test = $input->getArgument('test');
|
||||
$config = $this->getSuiteConfig($suite);
|
||||
|
||||
$generator = new GherkinSnippetsGenerator($config, $test);
|
||||
$snippets = $generator->getSnippets();
|
||||
if ($snippets === []) {
|
||||
$output->writeln("<notice> All Gherkin steps are defined. Exiting... </notice>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
$output->writeln("<comment> Snippets found in: </comment>");
|
||||
|
||||
foreach ($generator->getFeatures() as $feature) {
|
||||
$output->writeln("<info> - {$feature} </info>");
|
||||
}
|
||||
$output->writeln("<comment> Generated Snippets: </comment>");
|
||||
$output->writeln("<info> ----------------------------------------- </info>");
|
||||
foreach ($snippets as $snippet) {
|
||||
$output->writeln($snippet);
|
||||
}
|
||||
$output->writeln("<info> ----------------------------------------- </info>");
|
||||
$output->writeln(sprintf(' <bold>%d</bold> snippets proposed', count($snippets)));
|
||||
$output->writeln("<notice> Copy generated snippets to {$config['actor']} or a specific Gherkin context </notice>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
66
vendor/codeception/codeception/src/Codeception/Command/GherkinSteps.php
vendored
Normal file
66
vendor/codeception/codeception/src/Codeception/Command/GherkinSteps.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Test\Loader\Gherkin as GherkinLoader;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\Table;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function count;
|
||||
|
||||
/**
|
||||
* Prints all steps from all Gherkin contexts for a specific suite
|
||||
*
|
||||
* ```
|
||||
* codecept gherkin:steps acceptance
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
class GherkinSteps extends Command
|
||||
{
|
||||
use Shared\ConfigTrait;
|
||||
use Shared\StyleTrait;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Prints all defined feature steps')
|
||||
->addArgument('suite', InputArgument::REQUIRED, 'suite to scan for feature files')
|
||||
->addOption('config', 'c', InputOption::VALUE_OPTIONAL, 'Use custom path for config');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->addStyles($output);
|
||||
$suite = $input->getArgument('suite');
|
||||
$config = $this->getSuiteConfig($suite);
|
||||
$config['describe_steps'] = true;
|
||||
|
||||
$loader = new GherkinLoader($config);
|
||||
$steps = $loader->getSteps();
|
||||
|
||||
foreach ($steps as $name => $context) {
|
||||
$table = new Table($output);
|
||||
$table->setHeaders(['Step', 'Implementation']);
|
||||
$output->writeln("Steps from <bold>{$name}</bold> context:");
|
||||
|
||||
foreach ($context as $step => $callable) {
|
||||
if (count($callable) >= 2) {
|
||||
$method = $callable[0] . '::' . $callable[1];
|
||||
$table->addRow([$step, $method]);
|
||||
}
|
||||
}
|
||||
$table->render();
|
||||
}
|
||||
|
||||
if (!isset($table)) {
|
||||
$output->writeln("No steps are defined, start creating them by running <bold>gherkin:snippets</bold>");
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
46
vendor/codeception/codeception/src/Codeception/Command/Init.php
vendored
Normal file
46
vendor/codeception/codeception/src/Codeception/Command/Init.php
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\InitTemplate;
|
||||
use Exception;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function class_exists;
|
||||
use function ucfirst;
|
||||
|
||||
class Init extends Command
|
||||
{
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription("Creates test suites by a template")
|
||||
->addArgument('template', InputArgument::REQUIRED, 'Init template for the setup')
|
||||
->addOption('path', null, InputOption::VALUE_REQUIRED, 'Change current directory')
|
||||
->addOption('namespace', null, InputOption::VALUE_OPTIONAL, 'Namespace to add for actor classes and helpers');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$template = (string) $input->getArgument('template');
|
||||
$className = class_exists($template) ? $template : 'Codeception\Template\\' . ucfirst($template);
|
||||
if (!class_exists($className)) {
|
||||
throw new Exception("Template from a {$className} can't be loaded; Init can't be executed");
|
||||
}
|
||||
|
||||
$initProcess = new $className($input, $output);
|
||||
if (!$initProcess instanceof InitTemplate) {
|
||||
throw new Exception($className . ' is not a valid template');
|
||||
}
|
||||
if ($path = $input->getOption('path')) {
|
||||
$initProcess->initDir($path);
|
||||
}
|
||||
$initProcess->setup();
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
727
vendor/codeception/codeception/src/Codeception/Command/Run.php
vendored
Normal file
727
vendor/codeception/codeception/src/Codeception/Command/Run.php
vendored
Normal file
@ -0,0 +1,727 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Codecept;
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Exception\ParseException;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use RuntimeException;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Exception\InvalidArgumentException as SymfonyConsoleInvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\InvalidOptionException;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function array_flip;
|
||||
use function array_intersect_key;
|
||||
use function array_merge;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function extension_loaded;
|
||||
use function getcwd;
|
||||
use function implode;
|
||||
use function in_array;
|
||||
use function preg_match;
|
||||
use function preg_replace;
|
||||
use function rtrim;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function str_starts_with;
|
||||
use function strpos;
|
||||
use function strtolower;
|
||||
use function substr;
|
||||
use function substr_replace;
|
||||
|
||||
/**
|
||||
* Executes tests.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* * `codecept run acceptance`: run all acceptance tests
|
||||
* * `codecept run tests/acceptance/MyCest.php`: run only MyCest
|
||||
* * `codecept run acceptance MyCest`: same as above
|
||||
* * `codecept run acceptance MyCest:myTestInIt`: run one test from a Cest
|
||||
* * `codecept run acceptance MyCest:myTestInIt#1`: run one example or data provider item by number
|
||||
* * `codecept run acceptance MyCest:myTestInIt#1-3`: run a range of examples or data provider items
|
||||
* * `codecept run acceptance MyCest:myTestInIt@name.*`: run data provider items with matching names
|
||||
* * `codecept run acceptance checkout.feature`: run feature-file
|
||||
* * `codecept run acceptance -g slow`: run tests from *slow* group
|
||||
* * `codecept run unit,functional`: run only unit and functional suites
|
||||
*
|
||||
* Verbosity modes:
|
||||
*
|
||||
* * `codecept run -v`:
|
||||
* * `codecept run --steps`: print step-by-step execution
|
||||
* * `codecept run -vv`: print steps and debug information
|
||||
* * `codecept run --debug`: alias for `-vv`
|
||||
* * `codecept run -vvv`: print Codeception-internal debug information
|
||||
*
|
||||
* Load config:
|
||||
*
|
||||
* * `codecept run -c path/to/another/config`: from another dir
|
||||
* * `codecept run -c another_config.yml`: from another config file
|
||||
*
|
||||
* Override config values:
|
||||
*
|
||||
* * `codecept run -o "settings: shuffle: true"`: enable shuffle
|
||||
* * `codecept run -o "settings: lint: false"`: disable linting
|
||||
*
|
||||
* Run with specific extension
|
||||
*
|
||||
* * `codecept run --ext Recorder` run with Recorder extension enabled
|
||||
* * `codecept run --ext DotReporter` run with DotReporter printer
|
||||
* * `codecept run --ext "My\Custom\Extension"` run with an extension loaded by class name
|
||||
*
|
||||
* Full reference:
|
||||
* ```
|
||||
* Arguments:
|
||||
* suite suite to be tested
|
||||
* test test to be run
|
||||
*
|
||||
* Options:
|
||||
* -o, --override=OVERRIDE Override config values (multiple values allowed)
|
||||
* --config (-c) Use custom path for config
|
||||
* --report Show output in compact style
|
||||
* --html Generate html with results (default: "report.html")
|
||||
* --xml Generate JUnit XML Log (default: "report.xml")
|
||||
* --phpunit-xml Generate PhpUnit XML Log (default: "phpunit-report.xml")
|
||||
* --no-redirect Do not redirect to Composer-installed version in vendor/codeception
|
||||
* --colors Use colors in output
|
||||
* --no-colors Force no colors in output (useful to override config file)
|
||||
* --silent Only outputs suite names and final results. Almost the same as `--quiet`
|
||||
* --steps Show steps in output
|
||||
* --debug (-d) Alias for `-vv`
|
||||
* --bootstrap Execute bootstrap script before the test
|
||||
* --coverage Run with code coverage (default: "coverage.serialized")
|
||||
* --disable-coverage-php Don't generate CodeCoverage report in raw PHP serialized format
|
||||
* --coverage-html Generate CodeCoverage HTML report in path (default: "coverage")
|
||||
* --coverage-xml Generate CodeCoverage XML report in file (default: "coverage.xml")
|
||||
* --coverage-text Generate CodeCoverage text report in file (default: "coverage.txt")
|
||||
* --coverage-phpunit Generate CodeCoverage PHPUnit report in file (default: "coverage-phpunit")
|
||||
* --coverage-cobertura Generate CodeCoverage Cobertura report in file (default: "coverage-cobertura")
|
||||
* --no-exit Don't finish with exit code
|
||||
* --group (-g) Groups of tests to be executed (multiple values allowed)
|
||||
* --skip (-s) Skip selected suites (multiple values allowed)
|
||||
* --skip-group (-x) Skip selected groups (multiple values allowed)
|
||||
* --env Run tests in selected environments. (multiple values allowed, environments can be merged with ',')
|
||||
* --fail-fast (-f) Stop after nth failure (defaults to 1)
|
||||
* --no-rebuild Do not rebuild actor classes on start
|
||||
* --help (-h) Display this help message.
|
||||
* --quiet (-q) Do not output any message. Almost the same as `--silent`
|
||||
* --verbose (-v|vv|vvv) Increase the verbosity of messages: `v` for normal output, `vv` for steps and debug, `vvv` for Codeception-internal debug
|
||||
* --version (-V) Display this application version.
|
||||
* --ansi Force ANSI output.
|
||||
* --no-ansi Disable ANSI output.
|
||||
* --no-interaction (-n) Do not ask any interactive question.
|
||||
* --seed Use the given seed for shuffling tests
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
class Run extends Command
|
||||
{
|
||||
use Shared\ConfigTrait;
|
||||
|
||||
protected ?Codecept $codecept = null;
|
||||
|
||||
/**
|
||||
* @var int Executed suites
|
||||
*/
|
||||
protected int $executed = 0;
|
||||
|
||||
protected array $options = [];
|
||||
|
||||
protected ?OutputInterface $output = null;
|
||||
|
||||
/**
|
||||
* Sets Run arguments
|
||||
*
|
||||
* @throws SymfonyConsoleInvalidArgumentException
|
||||
*/
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Runs the test suites')
|
||||
->addArgument('suite', InputArgument::OPTIONAL, 'suite to be tested')
|
||||
->addArgument('test', InputArgument::OPTIONAL, 'test to be run')
|
||||
->addOption('override', 'o', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Override config values')
|
||||
->addOption('ext', 'e', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Run with extension enabled')
|
||||
->addOption('report', '', InputOption::VALUE_NONE, 'Show output in compact style')
|
||||
->addOption('html', '', InputOption::VALUE_OPTIONAL, 'Generate html with results', 'report.html')
|
||||
->addOption('xml', '', InputOption::VALUE_OPTIONAL, 'Generate JUnit XML Log', 'report.xml')
|
||||
->addOption('phpunit-xml', '', InputOption::VALUE_OPTIONAL, 'Generate PhpUnit XML Log', 'phpunit-report.xml')
|
||||
->addOption('colors', '', InputOption::VALUE_NONE, 'Use colors in output')
|
||||
->addOption('no-colors', '', InputOption::VALUE_NONE, 'Force no colors in output (useful to override config file)')
|
||||
->addOption('silent', '', InputOption::VALUE_NONE, 'Only outputs suite names and final results')
|
||||
->addOption('steps', '', InputOption::VALUE_NONE, 'Show steps in output')
|
||||
->addOption('debug', 'd', InputOption::VALUE_NONE, 'Show debug and scenario output')
|
||||
->addOption('shard', '', InputOption::VALUE_REQUIRED, 'Execute subset of tests to run tests on different machine. To split tests on 3 machines to run with shards: 1/3, 2/3, 3/3')
|
||||
->addOption('filter', '', InputOption::VALUE_REQUIRED, 'Filter tests by name')
|
||||
->addOption('grep', '', InputOption::VALUE_REQUIRED, 'Filter tests by name (alias to --filter)')
|
||||
->addOption('bootstrap', '', InputOption::VALUE_OPTIONAL, 'Execute custom PHP script before running tests. Path can be absolute or relative to current working directory', false)
|
||||
->addOption('no-redirect', '', InputOption::VALUE_NONE, 'Do not redirect to Composer-installed version in vendor/codeception')
|
||||
->addOption('coverage', '', InputOption::VALUE_OPTIONAL, 'Run with code coverage')
|
||||
->addOption('coverage-html', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage HTML report in path')
|
||||
->addOption('coverage-xml', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage XML report in file')
|
||||
->addOption('coverage-text', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage text report in file')
|
||||
->addOption('coverage-crap4j', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage report in Crap4J XML format')
|
||||
->addOption('coverage-cobertura', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage report in Cobertura XML format')
|
||||
->addOption('coverage-phpunit', '', InputOption::VALUE_OPTIONAL, 'Generate CodeCoverage PHPUnit report in path')
|
||||
->addOption('disable-coverage-php', '', InputOption::VALUE_NONE, "Don't generate CodeCoverage report in raw PHP serialized format")
|
||||
->addOption('no-exit', '', InputOption::VALUE_NONE, "Don't finish with exit code")
|
||||
->addOption('group', 'g', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Groups of tests to be executed')
|
||||
->addOption('skip', 's', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Skip selected suites')
|
||||
->addOption('skip-group', 'x', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Skip selected groups')
|
||||
->addOption('env', '', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_REQUIRED, 'Run tests in selected environments.')
|
||||
->addOption('fail-fast', 'f', InputOption::VALUE_OPTIONAL, 'Stop after nth failure')
|
||||
->addOption('no-rebuild', '', InputOption::VALUE_NONE, 'Do not rebuild actor classes on start')
|
||||
->addOption('seed', '', InputOption::VALUE_REQUIRED, 'Define random seed for shuffle setting')
|
||||
->addOption('no-artifacts', '', InputOption::VALUE_NONE, "Don't report about artifacts");
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes Run
|
||||
*
|
||||
* @throws ConfigurationException|ParseException
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->ensurePhpExtIsAvailable('CURL');
|
||||
$this->ensurePhpExtIsAvailable('mbstring');
|
||||
$this->options = $input->getOptions();
|
||||
$this->output = $output;
|
||||
|
||||
if ($this->options['bootstrap']) {
|
||||
Configuration::loadBootstrap($this->options['bootstrap'], getcwd());
|
||||
}
|
||||
|
||||
$config = $this->getGlobalConfig();
|
||||
$config = $this->addRuntimeOptionsToCurrentConfig($config);
|
||||
|
||||
if (!$this->options['colors']) {
|
||||
$this->options['colors'] = $config['settings']['colors'];
|
||||
}
|
||||
|
||||
if (!$this->options['silent']) {
|
||||
$this->output->writeln(
|
||||
Codecept::versionString() . ' https://stand-with-ukraine.pp.ua'
|
||||
);
|
||||
|
||||
if ($this->options['seed']) {
|
||||
$this->output->writeln(
|
||||
"Running with seed: <info>" . $this->options['seed'] . "</info>\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
if ($this->options['debug']) {
|
||||
$this->output->setVerbosity(OutputInterface::VERBOSITY_VERY_VERBOSE);
|
||||
}
|
||||
|
||||
$userOptions = array_intersect_key($this->options, array_flip($this->passedOptionKeys($input)));
|
||||
$userOptions = array_merge(
|
||||
$userOptions,
|
||||
$this->booleanOptions($input, [
|
||||
'xml' => 'report.xml',
|
||||
'phpunit-xml' => 'phpunit-report.xml',
|
||||
'html' => 'report.html',
|
||||
'coverage' => 'coverage.serialized',
|
||||
'coverage-xml' => 'coverage.xml',
|
||||
'coverage-html' => 'coverage',
|
||||
'coverage-text' => 'coverage.txt',
|
||||
'coverage-crap4j' => 'crap4j.xml',
|
||||
'coverage-cobertura' => 'cobertura.xml',
|
||||
'coverage-phpunit' => 'coverage-phpunit'])
|
||||
);
|
||||
$userOptions['verbosity'] = $this->output->getVerbosity();
|
||||
$userOptions['interactive'] = !$input->hasParameterOption(['--no-interaction', '-n']);
|
||||
$userOptions['ansi'] = (!$input->hasParameterOption('--no-ansi') xor $input->hasParameterOption('ansi'));
|
||||
$userOptions['disable-coverage-php'] = (bool) $this->options['disable-coverage-php'];
|
||||
|
||||
$userOptions['seed'] = $this->options['seed'] ? (int)$this->options['seed'] : rand();
|
||||
if ($this->options['no-colors'] || !$userOptions['ansi']) {
|
||||
$userOptions['colors'] = false;
|
||||
}
|
||||
if ($this->options['group']) {
|
||||
$userOptions['groups'] = $this->options['group'];
|
||||
}
|
||||
if ($this->options['skip-group']) {
|
||||
$userOptions['excludeGroups'] = $this->options['skip-group'];
|
||||
}
|
||||
if ($this->options['coverage-xml'] || $this->options['coverage-html'] || $this->options['coverage-text'] || $this->options['coverage-crap4j'] || $this->options['coverage-phpunit']) {
|
||||
$this->options['coverage'] = true;
|
||||
}
|
||||
if (!$userOptions['ansi'] && $input->getOption('colors')) {
|
||||
$userOptions['colors'] = true; // turn on colors even in non-ansi mode if strictly passed
|
||||
}
|
||||
// array key will exist if fail-fast option is used
|
||||
if (array_key_exists('fail-fast', $userOptions)) {
|
||||
$userOptions['fail-fast'] = (int)$this->options['fail-fast'] ?: 1;
|
||||
}
|
||||
|
||||
$suite = (string)$input->getArgument('suite');
|
||||
$test = $input->getArgument('test');
|
||||
|
||||
if ($this->options['group']) {
|
||||
$this->output->writeln(sprintf("[Groups] <info>%s</info> ", implode(', ', $this->options['group'])));
|
||||
}
|
||||
if ($input->getArgument('test')) {
|
||||
$this->options['steps'] = true;
|
||||
}
|
||||
|
||||
if (!$test) {
|
||||
// Check if suite is given and is in an included path
|
||||
if (!empty($suite) && !empty($config['include'])) {
|
||||
$isIncludeTest = false;
|
||||
// Remember original projectDir
|
||||
$projectDir = Configuration::projectDir();
|
||||
|
||||
foreach ($config['include'] as $include) {
|
||||
// Find if the suite begins with an include path
|
||||
if (str_starts_with($suite, (string)$include)) {
|
||||
// Use include config
|
||||
$config = Configuration::config($projectDir . $include);
|
||||
$config = $this->addRuntimeOptionsToCurrentConfig($config);
|
||||
|
||||
if (!empty($this->options['override'])) {
|
||||
$config = $this->overrideConfig($this->options['override']);
|
||||
}
|
||||
|
||||
if (!isset($config['paths']['tests'])) {
|
||||
throw new RuntimeException(
|
||||
sprintf("Included '%s' has no tests path configured", $include)
|
||||
);
|
||||
}
|
||||
|
||||
$testsPath = $include . DIRECTORY_SEPARATOR . $config['paths']['tests'];
|
||||
|
||||
try {
|
||||
[, $suite, $test] = $this->matchTestFromFilename($suite, $testsPath);
|
||||
$isIncludeTest = true;
|
||||
} catch (InvalidArgumentException) {
|
||||
// Incorrect include match, continue trying to find one
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
$result = $this->matchSingleTest($suite, $config);
|
||||
if ($result) {
|
||||
[, $suite, $test] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Restore main config
|
||||
if (!$isIncludeTest) {
|
||||
$config = $this->addRuntimeOptionsToCurrentConfig(
|
||||
Configuration::config($projectDir)
|
||||
);
|
||||
}
|
||||
} elseif (!empty($suite)) {
|
||||
$result = $this->matchSingleTest($suite, $config);
|
||||
if ($result) {
|
||||
[, $suite, $test] = $result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$filter = $input->getOption('filter') ?? $input->getOption('grep') ?? null;
|
||||
if ($test) {
|
||||
$userOptions['filter'] = $this->matchFilteredTestName($test);
|
||||
} elseif (
|
||||
$suite
|
||||
&& !$this->isWildcardSuiteName($suite)
|
||||
&& !$this->isSuiteInMultiApplication($suite)
|
||||
) {
|
||||
$userOptions['filter'] = $this->matchFilteredTestName($suite);
|
||||
}
|
||||
|
||||
if (isset($userOptions['filter']) && $filter) {
|
||||
throw new InvalidOptionException("--filter and --grep can't be used with a test name");
|
||||
} elseif ($filter) {
|
||||
$userOptions['filter'] = $filter;
|
||||
}
|
||||
|
||||
if ($this->options['shard']) {
|
||||
$this->output->writeln(
|
||||
"[Shard {$userOptions['shard']}] <info>Running subset of tests</info>"
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->options['silent'] && $config['settings']['shuffle']) {
|
||||
$this->output->writeln(
|
||||
"[Seed] <info>" . $userOptions['seed'] . "</info>"
|
||||
);
|
||||
}
|
||||
|
||||
$this->codecept = new Codecept($userOptions);
|
||||
|
||||
if ($suite && $test) {
|
||||
$this->codecept->run($suite, $test, $config);
|
||||
}
|
||||
|
||||
// Run all tests of given suite or all suites
|
||||
if (!$test) {
|
||||
$didPassCliSuite = !empty($suite);
|
||||
|
||||
$rawSuites = $didPassCliSuite ? explode(',', $suite) : Configuration::suites();
|
||||
|
||||
/** @var string[] $mainAppSuites */
|
||||
$mainAppSuites = [];
|
||||
|
||||
/** @var array<string,string> $appSpecificSuites */
|
||||
$appSpecificSuites = [];
|
||||
|
||||
/** @var string[] $wildcardSuites */
|
||||
$wildcardSuites = [];
|
||||
|
||||
foreach ($rawSuites as $rawSuite) {
|
||||
if ($this->isWildcardSuiteName($rawSuite)) {
|
||||
$wildcardSuites[] = explode('*::', $rawSuite)[1];
|
||||
continue;
|
||||
}
|
||||
if ($this->isSuiteInMultiApplication($rawSuite)) {
|
||||
$appAndSuite = explode('::', $rawSuite);
|
||||
$appSpecificSuites[$appAndSuite[0]][] = $appAndSuite[1];
|
||||
continue;
|
||||
}
|
||||
$mainAppSuites[] = $rawSuite;
|
||||
}
|
||||
|
||||
if ([] !== $mainAppSuites) {
|
||||
$this->executed = $this->runSuites($mainAppSuites, $this->options['skip']);
|
||||
}
|
||||
|
||||
if (!empty($wildcardSuites) && ! empty($appSpecificSuites)) {
|
||||
$this->output->writeLn('<error>Wildcard options can not be combined with specific suites of included apps.</error>');
|
||||
return Command::INVALID;
|
||||
}
|
||||
|
||||
if (
|
||||
!empty($config['include'])
|
||||
&& (!$didPassCliSuite || !empty($wildcardSuites) || !empty($appSpecificSuites))
|
||||
) {
|
||||
$currentDir = Configuration::projectDir();
|
||||
$includedApps = $config['include'];
|
||||
|
||||
if (!empty($appSpecificSuites)) {
|
||||
$includedApps = array_intersect($includedApps, array_keys($appSpecificSuites));
|
||||
}
|
||||
|
||||
$this->runIncludedSuites(
|
||||
$includedApps,
|
||||
$currentDir,
|
||||
$appSpecificSuites,
|
||||
$wildcardSuites
|
||||
);
|
||||
}
|
||||
|
||||
if ($this->executed === 0) {
|
||||
throw new RuntimeException(
|
||||
sprintf("Suite '%s' could not be found", implode(', ', $rawSuites))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->codecept->printResult();
|
||||
|
||||
if ($this->options['shard']) {
|
||||
$this->output->writeln(
|
||||
"[Shard {$userOptions['shard']}] <info>Merge this result with other shards to see the complete report</info>"
|
||||
);
|
||||
}
|
||||
|
||||
if (!$input->getOption('no-exit') && !$this->codecept->getResultAggregator()->wasSuccessfulIgnoringWarnings()) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
protected function matchSingleTest(string $suite, array $config): ?array
|
||||
{
|
||||
// Workaround when codeception.yml is inside tests directory and tests path is set to "."
|
||||
// @see https://github.com/Codeception/Codeception/issues/4432
|
||||
if (isset($config['paths']['tests']) && $config['paths']['tests'] === '.' && !preg_match('#^\.[/\\\]#', $suite)) {
|
||||
$suite = './' . $suite;
|
||||
}
|
||||
|
||||
// running a single test when suite has a configured path
|
||||
if (isset($config['suites'])) {
|
||||
foreach ($config['suites'] as $s => $suiteConfig) {
|
||||
if (!isset($suiteConfig['path'])) {
|
||||
continue;
|
||||
}
|
||||
$testsPath = $config['paths']['tests'] . DIRECTORY_SEPARATOR . $suiteConfig['path'];
|
||||
if ($suiteConfig['path'] === '.') {
|
||||
$testsPath = $config['paths']['tests'];
|
||||
}
|
||||
if (preg_match("#^{$testsPath}/(.*?)$#", $suite, $matches)) {
|
||||
$matches[2] = $matches[1];
|
||||
$matches[1] = $s;
|
||||
return $matches;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!Configuration::isEmpty()) {
|
||||
// Run single test without included tests
|
||||
if (str_starts_with($suite, (string)$config['paths']['tests'])) {
|
||||
return $this->matchTestFromFilename($suite, $config['paths']['tests']);
|
||||
}
|
||||
|
||||
// Run single test from working directory
|
||||
$realTestDir = (string)realpath(Configuration::testsDir());
|
||||
$cwd = (string)getcwd();
|
||||
if (str_starts_with($realTestDir, $cwd)) {
|
||||
$file = $suite;
|
||||
if (str_contains($file, ':')) {
|
||||
[$file] = explode(':', $suite, -1);
|
||||
}
|
||||
$realPath = $cwd . DIRECTORY_SEPARATOR . $file;
|
||||
if (file_exists($realPath) && str_starts_with($realPath, $realTestDir)) {
|
||||
//only match test if file is in tests directory
|
||||
return $this->matchTestFromFilename(
|
||||
$cwd . DIRECTORY_SEPARATOR . $suite,
|
||||
$realTestDir
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs included suites recursively
|
||||
*
|
||||
* @param string[] $suites
|
||||
* @param array<string,string[]> $filterAppSuites An array keyed by included app name where values are suite names to run.
|
||||
* @param string[] $filterSuitesByWildcard A list of suite names (applies to all included apps)
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected function runIncludedSuites(
|
||||
array $suites,
|
||||
string $parentDir,
|
||||
array $filterAppSuites = [],
|
||||
array $filterSuitesByWildcard = [],
|
||||
): void {
|
||||
$defaultConfig = Configuration::config();
|
||||
$absolutePath = Configuration::projectDir();
|
||||
|
||||
foreach ($suites as $relativePath) {
|
||||
$currentDir = rtrim($parentDir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $relativePath;
|
||||
$config = Configuration::config($currentDir);
|
||||
|
||||
if (!empty($defaultConfig['groups'])) {
|
||||
$groups = array_map(fn ($g): string => $absolutePath . $g, $defaultConfig['groups']);
|
||||
Configuration::append(['groups' => $groups]);
|
||||
}
|
||||
|
||||
$suites = Configuration::suites();
|
||||
|
||||
if ($filterSuitesByWildcard !== []) {
|
||||
$suites = array_intersect($suites, $filterSuitesByWildcard);
|
||||
}
|
||||
|
||||
if (isset($filterAppSuites[$relativePath])) {
|
||||
$suites = array_intersect($suites, $filterAppSuites[$relativePath]);
|
||||
}
|
||||
|
||||
$namespace = $this->currentNamespace();
|
||||
$this->output->writeln(
|
||||
"\n<fg=white;bg=magenta>\n[{$namespace}]: tests from {$currentDir}\n</fg=white;bg=magenta>"
|
||||
);
|
||||
|
||||
$this->executed += $this->runSuites($suites, $this->options['skip']);
|
||||
if (!empty($config['include'])) {
|
||||
$this->runIncludedSuites($config['include'], $currentDir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function currentNamespace(): string
|
||||
{
|
||||
$config = Configuration::config();
|
||||
if (!$config['namespace']) {
|
||||
throw new RuntimeException(
|
||||
"Can't include into runner suite without a namespace;\n"
|
||||
. "Please add `namespace` section into included codeception.yml file"
|
||||
);
|
||||
}
|
||||
|
||||
return $config['namespace'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $suites
|
||||
* @param string[] $skippedSuites
|
||||
* @return int Number of executed test suites
|
||||
*/
|
||||
protected function runSuites(array $suites, array $skippedSuites = []): int
|
||||
{
|
||||
$executed = 0;
|
||||
foreach ($suites as $suite) {
|
||||
if (in_array($suite, $skippedSuites)) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($suite, Configuration::suites())) {
|
||||
continue;
|
||||
}
|
||||
$this->codecept->run($suite);
|
||||
++$executed;
|
||||
}
|
||||
|
||||
return $executed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function matchTestFromFilename(string $filename, string $testsPath): array
|
||||
{
|
||||
$filter = '';
|
||||
if (str_contains($filename, ':')) {
|
||||
if ((PHP_OS === 'Windows' || PHP_OS === 'WINNT') && $filename[1] === ':') {
|
||||
// match C:\...
|
||||
[$drive, $path, $filter] = explode(':', $filename, 3);
|
||||
$filename = $drive . ':' . $path;
|
||||
} else {
|
||||
[$filename, $filter] = explode(':', $filename, 2);
|
||||
}
|
||||
|
||||
if ($filter !== '') {
|
||||
$filter = ':' . $filter;
|
||||
}
|
||||
}
|
||||
|
||||
$testsPath = str_replace(['//', '\/', '\\'], '/', $testsPath);
|
||||
$filename = str_replace(['//', '\/', '\\'], '/', $filename);
|
||||
|
||||
if (rtrim($filename, '/') === $testsPath) {
|
||||
//codecept run tests
|
||||
return ['', '', $filter];
|
||||
}
|
||||
$res = preg_match("#^{$testsPath}/(.*?)(?>/(.*))?$#", $filename, $matches);
|
||||
|
||||
if (!$res) {
|
||||
throw new InvalidArgumentException("Test file can't be matched");
|
||||
}
|
||||
if (!isset($matches[2])) {
|
||||
$matches[2] = '';
|
||||
}
|
||||
if ($filter !== '') {
|
||||
$matches[2] .= $filter;
|
||||
}
|
||||
|
||||
return $matches;
|
||||
}
|
||||
|
||||
private function matchFilteredTestName(string &$path): ?string
|
||||
{
|
||||
$testParts = explode(':', $path, 2);
|
||||
if (count($testParts) > 1) {
|
||||
[$path, $filter] = $testParts;
|
||||
// use carat to signify start of string like in normal regex
|
||||
// phpunit --filter matches against the fully qualified method name, so tests actually begin with :
|
||||
$caratPos = strpos($filter, '^');
|
||||
if ($caratPos !== false) {
|
||||
return substr_replace($filter, ':', $caratPos, 1);
|
||||
}
|
||||
return $filter;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function passedOptionKeys(ArgvInput $input): array
|
||||
{
|
||||
$options = [];
|
||||
$request = (string)$input;
|
||||
$tokens = explode(' ', $request);
|
||||
foreach ($tokens as $token) {
|
||||
$token = preg_replace('#=.*#', '', $token); // strip = from options
|
||||
|
||||
if (empty($token)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($token == '--') {
|
||||
break; // there should be no options after ' -- ', only arguments
|
||||
}
|
||||
|
||||
if (str_starts_with($token, '--')) {
|
||||
$options[] = substr($token, 2);
|
||||
} elseif ($token[0] === '-') {
|
||||
$shortOption = substr($token, 1);
|
||||
$options[] = $this->getDefinition()->getOptionForShortcut($shortOption)->getName();
|
||||
}
|
||||
}
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
protected function booleanOptions(ArgvInput $input, array $options = []): array
|
||||
{
|
||||
$values = [];
|
||||
$request = (string)$input;
|
||||
foreach ($options as $option => $defaultValue) {
|
||||
if (strpos($request, sprintf('--%s', $option))) {
|
||||
$values[$option] = $input->getOption($option) ?: $defaultValue;
|
||||
} else {
|
||||
$values[$option] = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function ensurePhpExtIsAvailable(string $ext): void
|
||||
{
|
||||
if (!extension_loaded(strtolower($ext))) {
|
||||
throw new Exception(
|
||||
"Codeception requires \"{$ext}\" extension installed to make tests run\n"
|
||||
. "If you are not sure, how to install \"{$ext}\", please refer to StackOverflow\n\n"
|
||||
. "Notice: PHP for Apache/Nginx and CLI can have different php.ini files.\n"
|
||||
. "Please make sure that your PHP you run from console has \"{$ext}\" enabled."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function isWildcardSuiteName(string $suiteName): bool
|
||||
{
|
||||
return str_starts_with($suiteName, '*::');
|
||||
}
|
||||
|
||||
private function isSuiteInMultiApplication(string $suiteName): bool
|
||||
{
|
||||
return str_contains($suiteName, '::');
|
||||
}
|
||||
|
||||
private function addRuntimeOptionsToCurrentConfig(array $config): array
|
||||
{
|
||||
// update config from options
|
||||
if (count($this->options['override'])) {
|
||||
$config = $this->overrideConfig($this->options['override']);
|
||||
}
|
||||
// enable extensions
|
||||
if ($this->options['ext']) {
|
||||
return $this->enableExtensions($this->options['ext']);
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
85
vendor/codeception/codeception/src/Codeception/Command/SelfUpdate.php
vendored
Normal file
85
vendor/codeception/codeception/src/Codeception/Command/SelfUpdate.php
vendored
Normal file
@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command;
|
||||
|
||||
use Codeception\Codecept;
|
||||
use Exception;
|
||||
use Humbug\SelfUpdate\Updater;
|
||||
use Phar;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
use function sprintf;
|
||||
|
||||
/**
|
||||
* Auto-updates phar archive from official site: 'https://codeception.com/codecept.phar' .
|
||||
*
|
||||
* * `php codecept.phar self-update`
|
||||
*
|
||||
* @author Franck Cassedanne <franck@cassedanne.com>
|
||||
*/
|
||||
class SelfUpdate extends Command
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const NAME = 'Codeception';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const GITHUB_REPO = 'Codeception/Codeception';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const PHAR_URL = 'https://codeception.com/php80/';
|
||||
|
||||
/**
|
||||
* Holds the current script filename.
|
||||
*/
|
||||
protected string $filename;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->filename = $_SERVER['argv'][0] ?? Phar::running(false);
|
||||
$this
|
||||
->setAliases(['selfupdate'])
|
||||
->setDescription(sprintf('Upgrade <comment>%s</comment> to the latest version', $this->filename));
|
||||
}
|
||||
|
||||
protected function getCurrentVersion(): string
|
||||
{
|
||||
return Codecept::VERSION;
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$output->writeln(
|
||||
sprintf('<info>%s</info> version <comment>%s</comment>', self::NAME, $this->getCurrentVersion())
|
||||
);
|
||||
|
||||
$updater = new Updater(null, false);
|
||||
$updater->getStrategy()->setPharUrl(self::PHAR_URL . 'codecept.phar');
|
||||
$updater->getStrategy()->setVersionUrl(self::PHAR_URL . 'codecept.version');
|
||||
|
||||
try {
|
||||
if ($updater->hasUpdate()) {
|
||||
$output->writeln("\n<info>Updating...</info>");
|
||||
$updater->update();
|
||||
|
||||
$output->writeln("\n<comment>{$this->filename}</comment> has been updated.\n");
|
||||
} else {
|
||||
$output->writeln('You are already using the latest version.');
|
||||
}
|
||||
} catch (Exception $exception) {
|
||||
$output->writeln("<error>\n{$exception->getMessage()}\n</error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
38
vendor/codeception/codeception/src/Codeception/Command/Shared/ActorTrait.php
vendored
Normal file
38
vendor/codeception/codeception/src/Codeception/Command/Shared/ActorTrait.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command\Shared;
|
||||
|
||||
use Codeception\Scenario;
|
||||
|
||||
trait ActorTrait
|
||||
{
|
||||
protected function getActorClassName(): ?string
|
||||
{
|
||||
if (empty($this->settings['actor'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$namespace = "";
|
||||
|
||||
if ($this->settings['namespace']) {
|
||||
$namespace .= '\\' . $this->settings['namespace'];
|
||||
}
|
||||
|
||||
if (isset($this->settings['support_namespace'])) {
|
||||
$namespace .= '\\' . $this->settings['support_namespace'];
|
||||
}
|
||||
|
||||
$namespace = rtrim($namespace, '\\') . '\\';
|
||||
|
||||
return $namespace . $this->settings['actor'];
|
||||
}
|
||||
|
||||
private function getActor($test): ?object
|
||||
{
|
||||
$actorClass = $this->getActorClassName();
|
||||
|
||||
return $actorClass ? new $actorClass(new Scenario($test)) : null;
|
||||
}
|
||||
}
|
||||
82
vendor/codeception/codeception/src/Codeception/Command/Shared/ConfigTrait.php
vendored
Normal file
82
vendor/codeception/codeception/src/Codeception/Command/Shared/ConfigTrait.php
vendored
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command\Shared;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Console\Exception\InvalidOptionException;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
use function array_merge_recursive;
|
||||
use function array_pop;
|
||||
use function array_shift;
|
||||
use function class_exists;
|
||||
use function count;
|
||||
use function explode;
|
||||
use function str_repeat;
|
||||
use function ucfirst;
|
||||
|
||||
trait ConfigTrait
|
||||
{
|
||||
protected function getSuiteConfig(string $suite): array
|
||||
{
|
||||
return Configuration::suiteSettings($suite, $this->getGlobalConfig());
|
||||
}
|
||||
|
||||
protected function getGlobalConfig(?string $conf = null): array
|
||||
{
|
||||
return Configuration::config($conf);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getSuites(): array
|
||||
{
|
||||
return Configuration::suites();
|
||||
}
|
||||
|
||||
protected function overrideConfig($configOptions): array
|
||||
{
|
||||
$updatedConfig = [];
|
||||
foreach ($configOptions as $option) {
|
||||
$keys = explode(': ', $option);
|
||||
if (count($keys) < 2) {
|
||||
throw new InvalidArgumentException('--override should have config passed as "key: value"');
|
||||
}
|
||||
$value = array_pop($keys);
|
||||
$yaml = '';
|
||||
for ($ind = 0; count($keys); $ind += 2) {
|
||||
$yaml .= "\n" . str_repeat(' ', $ind) . array_shift($keys) . ': ';
|
||||
}
|
||||
$yaml .= $value;
|
||||
try {
|
||||
$config = Yaml::parse($yaml);
|
||||
} catch (ParseException $e) {
|
||||
throw new \Codeception\Exception\ParseException("Overridden config can't be parsed: \n{$yaml}\n" . $e->getParsedLine());
|
||||
}
|
||||
$updatedConfig = array_merge_recursive($updatedConfig, $config);
|
||||
}
|
||||
return Configuration::append($updatedConfig);
|
||||
}
|
||||
|
||||
protected function enableExtensions($extensions): array
|
||||
{
|
||||
$config = ['extensions' => ['enabled' => []]];
|
||||
foreach ($extensions as $name) {
|
||||
if (!class_exists($name)) {
|
||||
$className = 'Codeception\\Extension\\' . ucfirst($name);
|
||||
if (!class_exists($className)) {
|
||||
throw new InvalidOptionException("Extension {$name} can't be loaded (tried by {$name} and {$className})");
|
||||
}
|
||||
$config['extensions']['enabled'][] = $className;
|
||||
continue;
|
||||
}
|
||||
$config['extensions']['enabled'][] = $name;
|
||||
}
|
||||
return Configuration::append($config);
|
||||
}
|
||||
}
|
||||
66
vendor/codeception/codeception/src/Codeception/Command/Shared/FileSystemTrait.php
vendored
Normal file
66
vendor/codeception/codeception/src/Codeception/Command/Shared/FileSystemTrait.php
vendored
Normal file
@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command\Shared;
|
||||
|
||||
use Codeception\Util\Shared\Namespaces;
|
||||
|
||||
use function file_exists;
|
||||
use function file_put_contents;
|
||||
use function mkdir;
|
||||
use function pathinfo;
|
||||
use function preg_replace;
|
||||
use function rtrim;
|
||||
use function str_replace;
|
||||
use function strrev;
|
||||
|
||||
trait FileSystemTrait
|
||||
{
|
||||
use Namespaces;
|
||||
|
||||
protected function createDirectoryFor(string $basePath, string $className = ''): string
|
||||
{
|
||||
$basePath = rtrim($basePath, DIRECTORY_SEPARATOR);
|
||||
if ($className) {
|
||||
$className = str_replace(['/', '\\'], [DIRECTORY_SEPARATOR, DIRECTORY_SEPARATOR], $className);
|
||||
$path = $basePath . DIRECTORY_SEPARATOR . $className;
|
||||
$basePath = pathinfo($path, PATHINFO_DIRNAME) . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
if (!file_exists($basePath)) {
|
||||
// Second argument should be mode. Well, umask() doesn't seem to return any if not set. Config may fix this.
|
||||
mkdir($basePath, 0775, true); // Third parameter commands to create directories recursively
|
||||
}
|
||||
return $basePath;
|
||||
}
|
||||
|
||||
protected function completeSuffix(string $filename, string $suffix): string
|
||||
{
|
||||
if (str_starts_with(strrev($filename), strrev($suffix))) {
|
||||
$filename .= '.php';
|
||||
}
|
||||
if (!str_starts_with(strrev($filename), strrev($suffix . '.php'))) {
|
||||
$filename .= $suffix . '.php';
|
||||
}
|
||||
if (!str_starts_with(strrev($filename), strrev('.php'))) {
|
||||
$filename .= '.php';
|
||||
}
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
protected function removeSuffix(string $classname, string $suffix): string
|
||||
{
|
||||
$classname = preg_replace('#\.php$#', '', $classname);
|
||||
return preg_replace("#{$suffix}$#", '', $classname);
|
||||
}
|
||||
|
||||
protected function createFile(string $filename, string $contents, bool $force = false, int $flags = 0): bool
|
||||
{
|
||||
if (file_exists($filename) && !$force) {
|
||||
return false;
|
||||
}
|
||||
file_put_contents($filename, $contents, $flags);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
19
vendor/codeception/codeception/src/Codeception/Command/Shared/StyleTrait.php
vendored
Normal file
19
vendor/codeception/codeception/src/Codeception/Command/Shared/StyleTrait.php
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Command\Shared;
|
||||
|
||||
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
trait StyleTrait
|
||||
{
|
||||
public function addStyles(OutputInterface $output): void
|
||||
{
|
||||
$output->getFormatter()->setStyle('notice', new OutputFormatterStyle('white', 'green', ['bold']));
|
||||
$output->getFormatter()->setStyle('bold', new OutputFormatterStyle(null, null, ['bold']));
|
||||
$output->getFormatter()->setStyle('warning', new OutputFormatterStyle(null, 'yellow', ['bold']));
|
||||
$output->getFormatter()->setStyle('debug', new OutputFormatterStyle('cyan'));
|
||||
}
|
||||
}
|
||||
779
vendor/codeception/codeception/src/Codeception/Configuration.php
vendored
Normal file
779
vendor/codeception/codeception/src/Codeception/Configuration.php
vendored
Normal file
@ -0,0 +1,779 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception;
|
||||
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Lib\ParamsLoader;
|
||||
use Codeception\Step\ConditionalAssertion;
|
||||
use Codeception\Util\Autoload;
|
||||
use Codeception\Util\PathResolver;
|
||||
use Codeception\Util\Template;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
use Symfony\Component\Finder\SplFileInfo;
|
||||
use Symfony\Component\Yaml\Exception\ParseException;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
use function array_unique;
|
||||
|
||||
class Configuration
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected static array $suites = [];
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>|null Current configuration
|
||||
*/
|
||||
protected static ?array $config = null;
|
||||
|
||||
/**
|
||||
* @var array<mixed> environmental files configuration cache
|
||||
*/
|
||||
protected static array $envConfig = [];
|
||||
|
||||
/**
|
||||
* @var string|null Directory containing main configuration file.
|
||||
* @see self::projectDir()
|
||||
*/
|
||||
protected static ?string $dir = null;
|
||||
|
||||
/**
|
||||
* @var string|null Directory of a base configuration file for the project with includes.
|
||||
* @see self::projectDir()
|
||||
*/
|
||||
protected static ?string $baseDir = null;
|
||||
|
||||
/**
|
||||
* @var string Current project output directory.
|
||||
*/
|
||||
protected static ?string $outputDir = null;
|
||||
|
||||
/**
|
||||
* @var string|null Current project data directory. This directory is used to hold
|
||||
* sql dumps and other things needed for current project tests.
|
||||
*/
|
||||
protected static ?string $dataDir = null;
|
||||
|
||||
/**
|
||||
* @var string|null Directory with test support files like Actors, Helpers, PageObjects, etc
|
||||
*/
|
||||
protected static ?string $supportDir = null;
|
||||
|
||||
/**
|
||||
* @var string|null Directory containing environment configuration files.
|
||||
*/
|
||||
protected static ?string $envsDir = null;
|
||||
|
||||
/**
|
||||
* @var string|null Directory containing tests and suites of the current project.
|
||||
*/
|
||||
protected static ?string $testsDir = null;
|
||||
|
||||
public static bool $lock = false;
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public static array $defaultConfig = [
|
||||
'actor_suffix' => 'Tester',
|
||||
'support_namespace' => null,
|
||||
'namespace' => '',
|
||||
|
||||
'include' => [],
|
||||
'paths' => [],
|
||||
'extends' => null,
|
||||
'suites' => [],
|
||||
'modules' => [],
|
||||
'extensions' => [
|
||||
'enabled' => [],
|
||||
'config' => [],
|
||||
'commands' => [],
|
||||
],
|
||||
'groups' => [],
|
||||
'bootstrap' => false,
|
||||
'settings' => [
|
||||
'colors' => true,
|
||||
'bootstrap' => false,
|
||||
'strict_xml' => false,
|
||||
'lint' => true,
|
||||
'backup_globals' => true,
|
||||
'report_useless_tests' => false,
|
||||
'be_strict_about_changes_to_global_state' => false,
|
||||
'shuffle' => false,
|
||||
],
|
||||
'coverage' => [],
|
||||
'params' => [],
|
||||
'gherkin' => []
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
public static array $defaultSuiteSettings = [
|
||||
'actor' => null,
|
||||
'modules' => [
|
||||
'enabled' => [],
|
||||
'config' => [],
|
||||
'depends' => []
|
||||
],
|
||||
'step_decorators' => ConditionalAssertion::class,
|
||||
'path' => null,
|
||||
'extends' => null,
|
||||
'namespace' => null,
|
||||
'groups' => [],
|
||||
'formats' => [],
|
||||
'shuffle' => false,
|
||||
'extensions' => [ // suite extensions
|
||||
'enabled' => [],
|
||||
'config' => [],
|
||||
],
|
||||
'error_level' => 'E_ALL & ~E_DEPRECATED',
|
||||
'convert_deprecations_to_exceptions' => false,
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array<string, mixed>|null
|
||||
*/
|
||||
protected static ?array $params = null;
|
||||
|
||||
/**
|
||||
* Loads global config file which is `codeception.yml` by default.
|
||||
* When config is already loaded - returns it.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public static function config(?string $configFile = null): array
|
||||
{
|
||||
if (!$configFile && self::$config) {
|
||||
return self::$config;
|
||||
}
|
||||
|
||||
if (self::$config && self::$lock) {
|
||||
return self::$config;
|
||||
}
|
||||
|
||||
if ($configFile === null) {
|
||||
$configFile = getcwd() . DIRECTORY_SEPARATOR . 'codeception.yml';
|
||||
}
|
||||
|
||||
if (is_dir($configFile)) {
|
||||
$configFile = $configFile . DIRECTORY_SEPARATOR . 'codeception.yml';
|
||||
}
|
||||
|
||||
$dir = realpath(dirname($configFile));
|
||||
if ($dir !== false) {
|
||||
self::$dir = $dir;
|
||||
$configDistFile = $dir . DIRECTORY_SEPARATOR . 'codeception.dist.yml';
|
||||
|
||||
// set the one default base directory for included setup
|
||||
if (!self::$baseDir) {
|
||||
self::$baseDir = $dir;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file_exists($configFile) && (!isset($configDistFile) || !file_exists($configDistFile))) {
|
||||
throw new ConfigurationException("Configuration file could not be found.\nRun `bootstrap` to initialize Codeception.", 404);
|
||||
}
|
||||
|
||||
// Preload config to retrieve params such that they are applied to codeception config file below
|
||||
$tempConfig = self::$defaultConfig;
|
||||
|
||||
$distConfigContents = '';
|
||||
if (isset($configDistFile) && file_exists($configDistFile)) {
|
||||
$distConfigContents = file_get_contents($configDistFile);
|
||||
if ($distConfigContents === false) {
|
||||
throw new ConfigurationException("Failed to read {$configDistFile}");
|
||||
}
|
||||
$tempConfig = self::mergeConfigs($tempConfig, self::getConfFromContents($distConfigContents, $configDistFile));
|
||||
}
|
||||
|
||||
$configContents = '';
|
||||
if (file_exists($configFile)) {
|
||||
$configContents = file_get_contents($configFile);
|
||||
if ($configContents === false) {
|
||||
throw new ConfigurationException("Failed to read {$configFile}");
|
||||
}
|
||||
$tempConfig = self::mergeConfigs($tempConfig, self::getConfFromContents($configContents, $configFile));
|
||||
}
|
||||
self::prepareParams($tempConfig);
|
||||
|
||||
// load config using params
|
||||
$config = self::$defaultConfig;
|
||||
if (isset($configDistFile) && $distConfigContents !== '') {
|
||||
$config = self::mergeConfigs(self::$defaultConfig, self::getConfFromContents($distConfigContents, $configDistFile));
|
||||
}
|
||||
if ($configContents !== '') {
|
||||
$config = self::mergeConfigs($config, self::getConfFromContents($configContents, $configFile));
|
||||
}
|
||||
|
||||
if ($config === self::$defaultConfig) {
|
||||
throw new ConfigurationException("Configuration file is invalid");
|
||||
}
|
||||
|
||||
// we check for the "extends" key in the yml file
|
||||
if (isset($config['extends'])) {
|
||||
// and now we search for the file
|
||||
$presetFilePath = codecept_absolute_path($config['extends']);
|
||||
if (file_exists($presetFilePath)) {
|
||||
// and merge it with our configuration file
|
||||
$config = self::mergeConfigs(self::getConfFromFile($presetFilePath), $config);
|
||||
}
|
||||
}
|
||||
|
||||
self::$config = $config;
|
||||
|
||||
if (!isset($config['paths']['support']) && isset($config['paths']['helpers'])) {
|
||||
$config['paths']['support'] = $config['paths']['helpers'];
|
||||
}
|
||||
|
||||
if (!isset($config['paths']['output'])) {
|
||||
throw new ConfigurationException('Output path is not defined by key "paths: output"');
|
||||
}
|
||||
|
||||
self::$outputDir = $config['paths']['output'];
|
||||
|
||||
// fill up includes with wildcard expansions
|
||||
$config['include'] = self::expandWildcardedIncludes($config['include']);
|
||||
|
||||
// config without tests, for inclusion of other configs
|
||||
if (!empty($config['include'])) {
|
||||
self::$config = $config;
|
||||
if (!isset($config['paths']['tests'])) {
|
||||
return $config;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($config['paths']['tests'])) {
|
||||
throw new ConfigurationException(
|
||||
'Tests directory is not defined in Codeception config by key "paths: tests:"'
|
||||
);
|
||||
}
|
||||
|
||||
if (!isset($config['paths']['data'])) {
|
||||
throw new ConfigurationException('Data path is not defined Codeception config by key "paths: data"');
|
||||
}
|
||||
|
||||
if (!isset($config['paths']['support'])) {
|
||||
throw new ConfigurationException('Helpers path is not defined by key "paths: support"');
|
||||
}
|
||||
|
||||
self::$dataDir = $config['paths']['data'];
|
||||
self::$supportDir = $config['paths']['support'];
|
||||
self::$testsDir = $config['paths']['tests'];
|
||||
|
||||
if (isset($config['paths']['envs'])) {
|
||||
self::$envsDir = $config['paths']['envs'];
|
||||
}
|
||||
|
||||
Autoload::addNamespace(self::$config['namespace'] . '\\' . self::$config['support_namespace'], self::supportDir());
|
||||
|
||||
self::loadBootstrap($config['bootstrap'], self::testsDir());
|
||||
self::loadSuites();
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public static function loadBootstrap(string|false $bootstrap, string $path): void
|
||||
{
|
||||
if (!$bootstrap) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bootstrap = PathResolver::isPathAbsolute($bootstrap)
|
||||
? $bootstrap
|
||||
: rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $bootstrap;
|
||||
|
||||
if (!file_exists($bootstrap)) {
|
||||
throw new ConfigurationException("Bootstrap file {$bootstrap} can't be loaded");
|
||||
}
|
||||
require_once $bootstrap;
|
||||
}
|
||||
|
||||
protected static function loadSuites(): void
|
||||
{
|
||||
$suites = Finder::create()
|
||||
->files()
|
||||
->name('*.{suite,suite.dist}.yml')
|
||||
->in(self::$dir . DIRECTORY_SEPARATOR . self::$testsDir)
|
||||
->depth('< 1')
|
||||
->sortByName();
|
||||
|
||||
self::$suites = [];
|
||||
|
||||
foreach (array_keys(self::$config['suites']) as $suite) {
|
||||
self::$suites[$suite] = $suite;
|
||||
}
|
||||
|
||||
/** @var SplFileInfo $suite */
|
||||
foreach ($suites as $suite) {
|
||||
preg_match('#(.*?)(\.suite|\.suite\.dist)\.yml#', $suite->getFilename(), $matches);
|
||||
self::$suites[$matches[1]] = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns suite configuration. Requires suite name and global config used (Configuration::config)
|
||||
*
|
||||
* @param array<mixed> $config
|
||||
* @return array<string, string>
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function suiteSettings(string $suite, array $config): array
|
||||
{
|
||||
// cut namespace name from suite name
|
||||
if ($suite != $config['namespace'] && str_starts_with($suite, $config['namespace'])) {
|
||||
$suite = ltrim(substr($suite, strlen($config['namespace'])), '.');
|
||||
}
|
||||
|
||||
if (!in_array($suite, self::$suites)) {
|
||||
throw new ConfigurationException("Suite {$suite} was not loaded");
|
||||
}
|
||||
|
||||
// load global config
|
||||
$globalConf = $config['settings'];
|
||||
foreach (['modules', 'coverage', 'support_namespace', 'namespace', 'groups', 'env', 'gherkin', 'extensions'] as $key) {
|
||||
if (isset($config[$key])) {
|
||||
$globalConf[$key] = $config[$key];
|
||||
}
|
||||
}
|
||||
|
||||
$settings = self::mergeConfigs(self::$defaultSuiteSettings, $globalConf);
|
||||
|
||||
// load suite config
|
||||
$settings = self::loadSuiteConfig($suite, $config['paths']['tests'], $settings);
|
||||
// load from environment configs
|
||||
if (isset($config['paths']['envs'])) {
|
||||
$envConf = self::loadEnvConfigs(self::$dir . DIRECTORY_SEPARATOR . $config['paths']['envs']);
|
||||
$settings = self::mergeConfigs($settings, $envConf);
|
||||
}
|
||||
|
||||
if (!$settings['path']) {
|
||||
// take a suite path from its name
|
||||
$settings['path'] = $suite;
|
||||
}
|
||||
|
||||
$config['paths']['tests'] = str_replace('/', DIRECTORY_SEPARATOR, (string) $config['paths']['tests']);
|
||||
|
||||
$settings['path'] = self::$dir . DIRECTORY_SEPARATOR . $config['paths']['tests']
|
||||
. DIRECTORY_SEPARATOR . $settings['path'] . DIRECTORY_SEPARATOR;
|
||||
|
||||
$settings['suite'] = $suite;
|
||||
$settings['suite_namespace'] = $settings['namespace'] . '\\' . $suite;
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads environments configuration from set directory
|
||||
*
|
||||
* @param string $path Path to the directory
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
protected static function loadEnvConfigs(string $path): array
|
||||
{
|
||||
if (isset(self::$envConfig[$path])) {
|
||||
return self::$envConfig[$path];
|
||||
}
|
||||
if (!is_dir($path)) {
|
||||
self::$envConfig[$path] = [];
|
||||
return self::$envConfig[$path];
|
||||
}
|
||||
|
||||
$envFiles = Finder::create()
|
||||
->files()
|
||||
->name('*.yml')
|
||||
->in($path)
|
||||
->depth('< 2');
|
||||
|
||||
$envConfig = [];
|
||||
/** @var SplFileInfo $envFile */
|
||||
foreach ($envFiles as $envFile) {
|
||||
$env = str_replace(['.dist.yml', '.yml'], '', $envFile->getFilename());
|
||||
$envConfig[$env] = [];
|
||||
$envPath = $path;
|
||||
if ($envFile->getRelativePath() !== '') {
|
||||
$envPath .= DIRECTORY_SEPARATOR . $envFile->getRelativePath();
|
||||
}
|
||||
foreach (['.dist.yml', '.yml'] as $suffix) {
|
||||
$envConf = self::getConfFromFile($envPath . DIRECTORY_SEPARATOR . $env . $suffix, []);
|
||||
$envConfig[$env] = self::mergeConfigs($envConfig[$env], $envConf);
|
||||
}
|
||||
}
|
||||
|
||||
self::$envConfig[$path] = ['env' => $envConfig];
|
||||
return self::$envConfig[$path];
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration from Yaml data
|
||||
*
|
||||
* @param string $contents Yaml config file contents
|
||||
* @param string $filename which is supposed to be loaded
|
||||
* @return array<string, mixed>
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected static function getConfFromContents(string $contents, string $filename = '(.yml)'): array
|
||||
{
|
||||
if (self::$params) {
|
||||
// replace '%var%' with encoded value
|
||||
$singleQuoteTemplate = new Template($contents, "'%", "%'", 'json_encode');
|
||||
$singleQuoteTemplate->setVars(self::$params);
|
||||
$contents = $singleQuoteTemplate->produce();
|
||||
// replace "%var%" with encoded value
|
||||
$doubleQuoteTemplate = new Template($contents, '"%', '%"', 'json_encode');
|
||||
$doubleQuoteTemplate->setVars(self::$params);
|
||||
$contents = $doubleQuoteTemplate->produce();
|
||||
// replace %var% with string value as is
|
||||
$plainTemplate = new Template($contents, '%', '%');
|
||||
$plainTemplate->setVars(self::$params);
|
||||
$contents = $plainTemplate->produce();
|
||||
}
|
||||
|
||||
try {
|
||||
$conf = Yaml::parse($contents);
|
||||
} catch (ParseException $exception) {
|
||||
throw new ConfigurationException(
|
||||
sprintf(
|
||||
"Error loading Yaml config from `%s`\n \n%s\nRead more about Yaml format https://goo.gl/9UPuEC",
|
||||
$filename,
|
||||
$exception->getMessage()
|
||||
)
|
||||
);
|
||||
}
|
||||
if ($conf === null) {
|
||||
throw new ConfigurationException("Configuration file {$filename} is empty.");
|
||||
}
|
||||
if (!is_array($conf)) {
|
||||
throw new ConfigurationException("Configuration file {$filename} is invalid.");
|
||||
}
|
||||
return $conf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads configuration from Yaml file or returns given value if the file doesn't exist
|
||||
*
|
||||
* @param array<string, mixed> $nonExistentValue Value used if filename is not found
|
||||
* @return array<string, mixed>
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected static function getConfFromFile(string $filename, array $nonExistentValue = []): array
|
||||
{
|
||||
if (file_exists($filename)) {
|
||||
$yaml = file_get_contents($filename);
|
||||
if ($yaml === false) {
|
||||
throw new ConfigurationException("Failed to read {$filename}");
|
||||
}
|
||||
return self::getConfFromContents($yaml, $filename);
|
||||
}
|
||||
return $nonExistentValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function suites(): array
|
||||
{
|
||||
return self::$suites;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of enabled modules according suite config.
|
||||
*
|
||||
* @param array<string, mixed> $settings Suite settings
|
||||
* @return string[]
|
||||
*/
|
||||
public static function modules(array $settings): array
|
||||
{
|
||||
return array_filter(
|
||||
array_map(
|
||||
fn ($m): mixed => is_array($m) ? key($m) : $m,
|
||||
$settings['modules']['enabled'],
|
||||
array_keys($settings['modules']['enabled'])
|
||||
),
|
||||
function ($m) use ($settings): bool {
|
||||
if (!isset($settings['modules']['disabled'])) {
|
||||
return true;
|
||||
}
|
||||
return !in_array($m, $settings['modules']['disabled']);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public static function isExtensionEnabled(string $extensionName): bool
|
||||
{
|
||||
return isset(self::$config['extensions']['enabled'])
|
||||
&& in_array($extensionName, self::$config['extensions']['enabled']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns current path to `_data` dir.
|
||||
* Use it to store database fixtures, sql dumps, or other files required by your tests.
|
||||
*/
|
||||
public static function dataDir(): string
|
||||
{
|
||||
return self::$dir . DIRECTORY_SEPARATOR . self::$dataDir . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current path to `_helpers` dir.
|
||||
* Helpers are custom modules.
|
||||
*/
|
||||
public static function supportDir(): string
|
||||
{
|
||||
return self::$dir . DIRECTORY_SEPARATOR . self::$supportDir . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns actual path to current `_output` dir.
|
||||
* Use it in Helpers or Groups to save result or temporary files.
|
||||
*
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public static function outputDir(): string
|
||||
{
|
||||
if (self::$outputDir === '') {
|
||||
throw new ConfigurationException("Path for output not specified. Please, set output path in global config");
|
||||
}
|
||||
|
||||
$dir = self::$outputDir . DIRECTORY_SEPARATOR;
|
||||
if (!codecept_is_path_absolute($dir)) {
|
||||
$dir = self::$dir . DIRECTORY_SEPARATOR . $dir;
|
||||
}
|
||||
|
||||
if (!file_exists($dir)) {
|
||||
@mkdir($dir, 0777, true);
|
||||
}
|
||||
|
||||
if (!is_writable($dir)) {
|
||||
@chmod($dir, 0777);
|
||||
}
|
||||
|
||||
if (!is_writable($dir)) {
|
||||
throw new ConfigurationException(
|
||||
"Path for output is not writable. Please, set appropriate access mode for output path: {$dir}"
|
||||
);
|
||||
}
|
||||
|
||||
return $dir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to the root of your project.
|
||||
* Basically returns path to current `codeception.yml` loaded.
|
||||
* Use this method instead of `__DIR__`, `getcwd()` or anything else.
|
||||
*/
|
||||
public static function projectDir(): string
|
||||
{
|
||||
return self::$dir . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to the base dir for config which consists with included setup
|
||||
* Returns path to `codeception.yml` which was executed.
|
||||
* If config doesn't have "include" section the result is the same as `projectDir()`
|
||||
*/
|
||||
public static function baseDir(): string
|
||||
{
|
||||
return self::$baseDir . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns path to tests directory
|
||||
*/
|
||||
public static function testsDir(): string
|
||||
{
|
||||
return self::$dir . DIRECTORY_SEPARATOR . self::$testsDir . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return current path to `_envs` dir.
|
||||
* Use it to store environment specific configuration.
|
||||
*/
|
||||
public static function envsDir(): string
|
||||
{
|
||||
if (self::$envsDir === '') {
|
||||
return '';
|
||||
}
|
||||
return self::$dir . DIRECTORY_SEPARATOR . self::$envsDir . DIRECTORY_SEPARATOR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a meta-configuration file that just points to other `codeception.yml`?
|
||||
* If so, it may have no tests by itself.
|
||||
*/
|
||||
public static function isEmpty(): bool
|
||||
{
|
||||
return !(bool)self::$testsDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds parameters to config
|
||||
* @param array<string, mixed> $config
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public static function append(array $config = []): array
|
||||
{
|
||||
self::$config = self::mergeConfigs(self::$config ?? [], $config);
|
||||
|
||||
if (isset(self::$config['paths']['output'])) {
|
||||
self::$outputDir = self::$config['paths']['output'];
|
||||
}
|
||||
if (isset(self::$config['paths']['data'])) {
|
||||
self::$dataDir = self::$config['paths']['data'];
|
||||
}
|
||||
if (isset(self::$config['paths']['support'])) {
|
||||
self::$supportDir = self::$config['paths']['support'];
|
||||
}
|
||||
if (isset(self::$config['paths']['tests'])) {
|
||||
self::$testsDir = self::$config['paths']['tests'];
|
||||
}
|
||||
|
||||
return self::$config;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<mixed> $a1
|
||||
* @param array<mixed> $a2
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function mergeConfigs(array $a1, array $a2): array
|
||||
{
|
||||
// for sequential arrays
|
||||
if (isset($a1[0], $a2[0])) {
|
||||
return array_values(array_unique(array_merge_recursive($a2, $a1), SORT_REGULAR));
|
||||
}
|
||||
|
||||
// for associative arrays
|
||||
$res = [];
|
||||
foreach ($a2 as $k2 => $v2) {
|
||||
if (!isset($a1[$k2]) || !is_array($a1[$k2])) { // if no such key
|
||||
$res[$k2] = $v2;
|
||||
unset($a1[$k2]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_array($v2)) {
|
||||
$res[$k2] = self::mergeConfigs($a1[$k2], $v2);
|
||||
unset($a1[$k2]);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($a1 as $k1 => $v1) { // only single elements here left
|
||||
$res[$k1] = $v1;
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads config from *.dist.suite.yml and *.suite.yml
|
||||
*
|
||||
* @param array<string ,mixed> $settings
|
||||
* @return array<string ,mixed>
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected static function loadSuiteConfig(string $suite, string $path, array $settings): array
|
||||
{
|
||||
if (isset(self::$config['suites'][$suite])) {
|
||||
// bundled config
|
||||
return self::mergeConfigs($settings, self::$config['suites'][$suite]);
|
||||
}
|
||||
|
||||
$suiteDir = self::$dir . DIRECTORY_SEPARATOR . $path;
|
||||
|
||||
$suiteDistConf = self::getConfFromFile($suiteDir . DIRECTORY_SEPARATOR . "{$suite}.suite.dist.yml", []);
|
||||
$suiteConf = self::getConfFromFile($suiteDir . DIRECTORY_SEPARATOR . "{$suite}.suite.yml", []);
|
||||
|
||||
// now we check the suite config file, if a extends key is defined
|
||||
if (isset($suiteConf['extends'])) {
|
||||
$presetFilePath = codecept_is_path_absolute($suiteConf['extends'])
|
||||
? $suiteConf['extends'] // If path is absolute – use it
|
||||
: realpath($suiteDir . DIRECTORY_SEPARATOR . $suiteConf['extends']); // Otherwise try to locate it in the suite dir
|
||||
|
||||
if ($presetFilePath === false) {
|
||||
throw new ConfigurationException(
|
||||
sprintf("Configuration file %s does not exist", $suiteConf['extends'])
|
||||
);
|
||||
}
|
||||
if (file_exists($presetFilePath)) {
|
||||
$settings = self::mergeConfigs(self::getConfFromFile($presetFilePath, []), $settings);
|
||||
}
|
||||
}
|
||||
|
||||
$settings = self::mergeConfigs($settings, $suiteDistConf);
|
||||
|
||||
return self::mergeConfigs($settings, $suiteConf);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces wildcarded items in include array with real paths.
|
||||
*
|
||||
* @param string[] $includes
|
||||
* @return string[]
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected static function expandWildcardedIncludes(array $includes): array
|
||||
{
|
||||
if ($includes === []) {
|
||||
return $includes;
|
||||
}
|
||||
$expandedIncludes = [];
|
||||
foreach ($includes as $include) {
|
||||
$expandedIncludes = array_merge($expandedIncludes, self::expandWildcardsFor($include));
|
||||
}
|
||||
return $expandedIncludes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds config files in given wildcarded include path.
|
||||
* Returns the expanded paths or the original if not a wildcard.
|
||||
*
|
||||
* @return string[]
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
protected static function expandWildcardsFor(string $include): array
|
||||
{
|
||||
if (1 !== preg_match('#[?.*]#', $include)) {
|
||||
return [$include,];
|
||||
}
|
||||
|
||||
try {
|
||||
$configFiles = Finder::create()->files()
|
||||
->name('/codeception(\.dist\.yml|\.yml)/')
|
||||
->in(self::$dir . DIRECTORY_SEPARATOR . $include);
|
||||
} catch (InvalidArgumentException) {
|
||||
throw new ConfigurationException(
|
||||
"Configuration file(s) could not be found in \"{$include}\"."
|
||||
);
|
||||
}
|
||||
|
||||
$paths = [];
|
||||
foreach ($configFiles as $file) {
|
||||
$paths[] = codecept_relative_path($file->getPath());
|
||||
}
|
||||
|
||||
return array_unique($paths);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $settings
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
private static function prepareParams(array $settings): void
|
||||
{
|
||||
self::$params = [];
|
||||
|
||||
foreach ($settings['params'] as $paramStorage) {
|
||||
static::$params = array_merge(self::$params, ParamsLoader::load($paramStorage));
|
||||
}
|
||||
}
|
||||
}
|
||||
195
vendor/codeception/codeception/src/Codeception/Coverage/Filter.php
vendored
Normal file
195
vendor/codeception/codeception/src/Codeception/Coverage/Filter.php
vendored
Normal file
@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Coverage;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Exception\ModuleException;
|
||||
use PHPUnit\Runner\Version as PHPUnitVersion;
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
use SebastianBergmann\CodeCoverage\Filter as PhpUnitFilter;
|
||||
use Symfony\Component\Finder\Exception\DirectoryNotFoundException;
|
||||
use Symfony\Component\Finder\Finder;
|
||||
|
||||
use function array_pop;
|
||||
use function explode;
|
||||
use function implode;
|
||||
use function is_array;
|
||||
use function iterator_to_array;
|
||||
use function str_replace;
|
||||
|
||||
class Filter
|
||||
{
|
||||
protected static ?self $codeceptionFilter = null;
|
||||
|
||||
protected ?PhpUnitFilter $phpUnitFilter = null;
|
||||
|
||||
public function __construct(protected ?CodeCoverage $phpCodeCoverage)
|
||||
{
|
||||
$this->phpUnitFilter = $this->phpCodeCoverage->filter();
|
||||
}
|
||||
|
||||
public static function setup(CodeCoverage $phpCoverage): self
|
||||
{
|
||||
self::$codeceptionFilter = new self($phpCoverage);
|
||||
return self::$codeceptionFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public function whiteList(array $config): self
|
||||
{
|
||||
$filter = $this->phpUnitFilter;
|
||||
if (!isset($config['coverage'])) {
|
||||
return $this;
|
||||
}
|
||||
$coverage = $config['coverage'];
|
||||
if (!isset($coverage['whitelist'])) {
|
||||
$coverage['whitelist'] = [];
|
||||
if (isset($coverage['include'])) {
|
||||
$coverage['whitelist']['include'] = $coverage['include'];
|
||||
}
|
||||
if (isset($coverage['exclude'])) {
|
||||
$coverage['whitelist']['exclude'] = $coverage['exclude'];
|
||||
}
|
||||
}
|
||||
|
||||
if (PHPUnitVersion::series() >= 11) {
|
||||
return $this->newWhiteList($coverage['whitelist']);
|
||||
}
|
||||
|
||||
foreach (['include', 'exclude'] as $type) {
|
||||
if (!isset($coverage['whitelist'][$type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!is_array($coverage['whitelist'][$type])) {
|
||||
throw new ConfigurationException("Error parsing yaml. Config `whitelist: {$type}:` should be an array");
|
||||
}
|
||||
|
||||
foreach ($coverage['whitelist'][$type] as $fileOrDir) {
|
||||
try {
|
||||
$finder = str_contains($fileOrDir, '*')
|
||||
? $this->matchWildcardPattern($fileOrDir)
|
||||
: [Configuration::projectDir() . DIRECTORY_SEPARATOR . $fileOrDir];
|
||||
|
||||
foreach ($finder as $file) {
|
||||
$file = (string) $file;
|
||||
$type === 'include' ? $filter->includeFile($file) : $filter->excludeFile($file);
|
||||
}
|
||||
} catch (DirectoryNotFoundException) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function newWhiteList(array $whitelist): self
|
||||
{
|
||||
$include = $whitelist['include'] ?? [];
|
||||
$exclude = $whitelist['exclude'] ?? [];
|
||||
|
||||
if (!is_array($include)) {
|
||||
throw new ConfigurationException('Error parsing yaml. Config `whitelist: include:` should be an array');
|
||||
}
|
||||
if (!is_array($exclude)) {
|
||||
throw new ConfigurationException('Error parsing yaml. Config `whitelist: exclude:` should be an array');
|
||||
}
|
||||
|
||||
if ($exclude === [] && $include === []) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
if ($include === []) {
|
||||
$include = [
|
||||
Configuration::projectDir() . DIRECTORY_SEPARATOR . '*'
|
||||
];
|
||||
}
|
||||
|
||||
$allIncludedFiles = $this->matchFiles($include);
|
||||
$allExcludedFiles = $this->matchFiles($exclude);
|
||||
|
||||
$coveredFiles = array_diff($allIncludedFiles, $allExcludedFiles);
|
||||
|
||||
foreach ($coveredFiles as $coveredFile) {
|
||||
$this->phpUnitFilter->includeFile((string) $coveredFile);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function matchFiles(array $files): array
|
||||
{
|
||||
$matchedFiles = [];
|
||||
|
||||
foreach ($files as $fileOrDir) {
|
||||
try {
|
||||
$finder = str_contains($fileOrDir, '*')
|
||||
? $this->matchWildcardPattern($fileOrDir)
|
||||
: $this->matchFileOrDirectory($fileOrDir);
|
||||
|
||||
$matchedFiles += iterator_to_array($finder->getIterator());
|
||||
} catch (DirectoryNotFoundException) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return $matchedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ModuleException
|
||||
*/
|
||||
public function blackList(array $config): self
|
||||
{
|
||||
if (isset($config['coverage']['blacklist'])) {
|
||||
throw new ModuleException($this, 'The blacklist functionality has been removed from PHPUnit 5,'
|
||||
. ' please remove blacklist section from configuration.');
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function matchFileOrDirectory(string $fileOrDir): Finder
|
||||
{
|
||||
$fullPath = Configuration::projectDir() . $fileOrDir;
|
||||
$finder = Finder::create();
|
||||
if (is_dir($fullPath)) {
|
||||
$finder->in($fullPath);
|
||||
$finder->name('*.php');
|
||||
} else {
|
||||
$finder->in(dirname($fullPath));
|
||||
$finder->name(basename($fullPath));
|
||||
}
|
||||
$finder->ignoreVCS(true)->files();
|
||||
return $finder;
|
||||
}
|
||||
|
||||
protected function matchWildcardPattern(string $pattern): Finder
|
||||
{
|
||||
$finder = Finder::create();
|
||||
$fileOrDir = str_replace('\\', '/', $pattern);
|
||||
$parts = explode('/', $fileOrDir);
|
||||
$file = array_pop($parts);
|
||||
if ($file === '*') {
|
||||
$file = '*.php';
|
||||
}
|
||||
$finder->name($file);
|
||||
if ($parts !== []) {
|
||||
$lastPath = array_pop($parts);
|
||||
$path = implode('/', ($lastPath === '*' ? $parts : [...$parts, $lastPath]));
|
||||
$finder->in(Configuration::projectDir() . $path);
|
||||
}
|
||||
$finder->ignoreVCS(true)->files();
|
||||
return $finder;
|
||||
}
|
||||
|
||||
public function getFilter(): PhpUnitFilter
|
||||
{
|
||||
return $this->phpUnitFilter;
|
||||
}
|
||||
}
|
||||
36
vendor/codeception/codeception/src/Codeception/Coverage/PhpCodeCoverageFactory.php
vendored
Normal file
36
vendor/codeception/codeception/src/Codeception/Coverage/PhpCodeCoverageFactory.php
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Coverage;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
use SebastianBergmann\CodeCoverage\Driver\Selector;
|
||||
use SebastianBergmann\CodeCoverage\Filter as CodeCoverageFilter;
|
||||
|
||||
class PhpCodeCoverageFactory
|
||||
{
|
||||
private static ?CodeCoverage $instance = null;
|
||||
|
||||
public static function build(): CodeCoverage
|
||||
{
|
||||
if (self::$instance instanceof CodeCoverage) {
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
$coverageConfig = Configuration::config()['coverage'];
|
||||
$pathCoverage = $coverageConfig['path_coverage'] ?? false;
|
||||
|
||||
$filter = new CodeCoverageFilter();
|
||||
$selector = new Selector();
|
||||
$driver = $pathCoverage ? $selector->forLineAndPathCoverage($filter) : $selector->forLineCoverage($filter);
|
||||
|
||||
return self::$instance = new CodeCoverage($driver, $filter);
|
||||
}
|
||||
|
||||
public static function clear(): void
|
||||
{
|
||||
self::$instance = null;
|
||||
}
|
||||
}
|
||||
67
vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/Local.php
vendored
Normal file
67
vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/Local.php
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Coverage\Subscriber;
|
||||
|
||||
use Codeception\Coverage\Filter;
|
||||
use Codeception\Coverage\PhpCodeCoverageFactory;
|
||||
use Codeception\Coverage\SuiteSubscriber;
|
||||
use Codeception\Event\SuiteEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Exception\ModuleException;
|
||||
use Codeception\Lib\Interfaces\Remote;
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Collects code coverage from unit and functional tests.
|
||||
* Results from all suites are merged.
|
||||
*/
|
||||
class Local extends SuiteSubscriber
|
||||
{
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $events = [
|
||||
Events::SUITE_BEFORE => 'beforeSuite',
|
||||
Events::SUITE_AFTER => 'afterSuite',
|
||||
];
|
||||
|
||||
protected ?Remote $module = null;
|
||||
|
||||
protected function isEnabled(): bool
|
||||
{
|
||||
return !$this->module instanceof Remote && $this->settings['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ConfigurationException|ModuleException|Exception
|
||||
*/
|
||||
public function beforeSuite(SuiteEvent $event): void
|
||||
{
|
||||
$this->applySettings($event->getSettings());
|
||||
$this->module = $this->getServerConnectionModule($event->getSuite()->getModules());
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event->getSuite()->collectCodeCoverage(true);
|
||||
|
||||
Filter::setup($this->coverage)
|
||||
->whiteList($this->filters)
|
||||
->blackList($this->filters);
|
||||
}
|
||||
|
||||
public function afterSuite(SuiteEvent $event): void
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$codeCoverage = PhpCodeCoverageFactory::build();
|
||||
PhpCodeCoverageFactory::clear();
|
||||
|
||||
$this->mergeToPrint($codeCoverage);
|
||||
}
|
||||
}
|
||||
328
vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/LocalServer.php
vendored
Normal file
328
vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/LocalServer.php
vendored
Normal file
@ -0,0 +1,328 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Coverage\Subscriber;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Coverage\SuiteSubscriber;
|
||||
use Codeception\Event\StepEvent;
|
||||
use Codeception\Event\SuiteEvent;
|
||||
use Codeception\Event\TestEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Exception\ModuleException;
|
||||
use Codeception\Exception\RemoteException;
|
||||
use Codeception\Lib\Interfaces\Web as WebInterface;
|
||||
use Codeception\Lib\Notification;
|
||||
use Codeception\Module\WebDriver as WebDriverModule;
|
||||
use Facebook\WebDriver\Exception\NoSuchAlertException;
|
||||
use RuntimeException;
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
|
||||
use function array_filter;
|
||||
use function array_replace_recursive;
|
||||
use function file_exists;
|
||||
use function file_get_contents;
|
||||
use function json_encode;
|
||||
use function parse_url;
|
||||
use function preg_match;
|
||||
use function str_replace;
|
||||
use function stream_context_create;
|
||||
use function unserialize;
|
||||
use function usleep;
|
||||
|
||||
/**
|
||||
* When collecting code coverage data from local server HTTP requests are sent to c3.php file.
|
||||
* Coverage Collection is started by sending cookies/headers.
|
||||
* Result is taken from the local file and merged with local code coverage results.
|
||||
*
|
||||
* Class LocalServer
|
||||
* @package Codeception\Coverage\Subscriber
|
||||
*/
|
||||
class LocalServer extends SuiteSubscriber
|
||||
{
|
||||
// headers
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const COVERAGE_HEADER = 'X-Codeception-CodeCoverage';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const COVERAGE_HEADER_ERROR = 'X-Codeception-CodeCoverage-Error';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const COVERAGE_HEADER_CONFIG = 'X-Codeception-CodeCoverage-Config';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const COVERAGE_HEADER_SUITE = 'X-Codeception-CodeCoverage-Suite';
|
||||
|
||||
// cookie names
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const COVERAGE_COOKIE = 'CODECEPTION_CODECOVERAGE';
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const COVERAGE_COOKIE_ERROR = 'CODECEPTION_CODECOVERAGE_ERROR';
|
||||
|
||||
protected string $suiteName = '';
|
||||
|
||||
protected array $c3Access = [
|
||||
'http' => [
|
||||
'method' => 'GET',
|
||||
'header' => ''
|
||||
]
|
||||
];
|
||||
|
||||
protected ?WebInterface $module = null;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $events = [
|
||||
Events::SUITE_BEFORE => 'beforeSuite',
|
||||
Events::TEST_BEFORE => 'beforeTest',
|
||||
Events::STEP_AFTER => 'afterStep',
|
||||
Events::SUITE_AFTER => 'afterSuite',
|
||||
];
|
||||
|
||||
protected function isEnabled(): bool
|
||||
{
|
||||
return $this->module && !$this->settings['remote'] && $this->settings['enabled'];
|
||||
}
|
||||
|
||||
public function beforeSuite(SuiteEvent $event): void
|
||||
{
|
||||
$this->module = $this->getServerConnectionModule($event->getSuite()->getModules());
|
||||
$this->applySettings($event->getSettings());
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->suiteName = $event->getSuite()->getBaseName();
|
||||
|
||||
if ($this->settings['remote_config']) {
|
||||
$this->addC3AccessHeader(self::COVERAGE_HEADER_CONFIG, $this->settings['remote_config']);
|
||||
if ($this->c3Request('clear') === false) {
|
||||
throw new RemoteException(
|
||||
'
|
||||
CodeCoverage Error.
|
||||
Check the file "c3.php" is included in your application.
|
||||
We tried to access "/c3/report/clear" but this URI was not accessible.
|
||||
You can review actual error messages in c3tmp dir.
|
||||
'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function beforeTest(TestEvent $event): void
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
$this->startCoverageCollection($event->getTest()->getName());
|
||||
}
|
||||
|
||||
public function afterStep(StepEvent $event): void
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
$this->fetchErrors();
|
||||
}
|
||||
|
||||
public function afterSuite(SuiteEvent $event): void
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$outputDir = Configuration::outputDir() . 'c3tmp/';
|
||||
$blockFile = $outputDir . 'block_report';
|
||||
$coverageFile = $outputDir . 'codecoverage.serialized';
|
||||
$errorFile = $outputDir . 'error.txt';
|
||||
|
||||
$this->waitForFile($blockFile, 120, 250_000);
|
||||
$this->waitForFile($coverageFile, 5, 500_000);
|
||||
|
||||
if (!file_exists($coverageFile)) {
|
||||
throw new RuntimeException(
|
||||
file_exists($errorFile) ? file_get_contents($errorFile) : "Code coverage file {$coverageFile} does not exist"
|
||||
);
|
||||
}
|
||||
|
||||
if ($coverage = @unserialize(file_get_contents($coverageFile))) {
|
||||
$this->preProcessCoverage($coverage)->mergeToPrint($coverage);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows Translating Remote Paths To Local (IE: When Using Docker)
|
||||
*/
|
||||
protected function preProcessCoverage(CodeCoverage $coverage): self
|
||||
{
|
||||
if (!$this->settings['work_dir']) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
$workDir = rtrim((string) $this->settings['work_dir'], '/\\') . DIRECTORY_SEPARATOR;
|
||||
$projectDir = Configuration::projectDir();
|
||||
$coverageData = $coverage->getData(true); // We only want covered files, not all whitelisted ones.
|
||||
|
||||
codecept_debug("Replacing all instances of {$workDir} with {$projectDir}");
|
||||
|
||||
foreach ($coverageData as $path => $datum) {
|
||||
unset($coverageData[$path]);
|
||||
$path = str_replace($workDir, $projectDir, (string) $path);
|
||||
$coverageData[$path] = $datum;
|
||||
}
|
||||
$coverage->setData($coverageData);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function c3Request(string $action): string|false
|
||||
{
|
||||
$this->addC3AccessHeader(self::COVERAGE_HEADER, 'remote-access');
|
||||
$context = stream_context_create($this->c3Access);
|
||||
$c3Url = $this->settings['c3_url'] ?? $this->module->_getUrl();
|
||||
$contents = file_get_contents("{$c3Url}/c3/report/{$action}", false, $context);
|
||||
|
||||
$okHeaders = array_filter(
|
||||
$http_response_header,
|
||||
fn ($h) => preg_match('#^HTTP(.*?)\s200#', $h)
|
||||
);
|
||||
if ($okHeaders === []) {
|
||||
throw new RemoteException("Request was not successful. See response header: " . $http_response_header[0]);
|
||||
}
|
||||
if ($contents === false) {
|
||||
$this->getRemoteError($http_response_header);
|
||||
}
|
||||
return $contents;
|
||||
}
|
||||
|
||||
protected function startCoverageCollection(string $testName): void
|
||||
{
|
||||
$coverageDataJson = json_encode([
|
||||
'CodeCoverage' => $testName,
|
||||
'CodeCoverage_Suite' => $this->suiteName,
|
||||
'CodeCoverage_Config' => $this->settings['remote_config']
|
||||
], JSON_THROW_ON_ERROR);
|
||||
|
||||
if ($this->module instanceof WebDriverModule) {
|
||||
$this->module->amOnPage('/');
|
||||
}
|
||||
|
||||
$cookieDomain = $this->settings['cookie_domain'] ??
|
||||
parse_url($this->settings['c3_url'] ?? $this->module->_getUrl(), PHP_URL_HOST) ??
|
||||
'localhost';
|
||||
|
||||
if (!$cookieDomain) {
|
||||
// we need to separate coverage cookies by host; we can't separate cookies by port.
|
||||
$cookieDomain = 'localhost';
|
||||
}
|
||||
|
||||
$cookieParams = $cookieDomain !== 'localhost' ? ['domain' => $cookieDomain] : [];
|
||||
|
||||
$this->module->setCookie(self::COVERAGE_COOKIE, $coverageDataJson, $cookieParams);
|
||||
// putting in configuration ensures the cookie is used for all sessions of a MultiSession test
|
||||
|
||||
$cookies = $this->module->_getConfig('cookies');
|
||||
if (!is_array($cookies)) {
|
||||
$cookies = [];
|
||||
}
|
||||
|
||||
$cookieUpdated = false;
|
||||
foreach ($cookies as &$cookie) {
|
||||
if (isset($cookie['Name'], $cookie['Value']) && $cookie['Name'] === self::COVERAGE_COOKIE) {
|
||||
$cookie['Value'] = $coverageDataJson;
|
||||
$cookieUpdated = true;
|
||||
break;
|
||||
}
|
||||
// \Codeception\Lib\InnerBrowser will complain about this
|
||||
}
|
||||
unset($cookie);
|
||||
|
||||
if (!$cookieUpdated) {
|
||||
$cookies[] = [
|
||||
'Name' => self::COVERAGE_COOKIE,
|
||||
'Value' => $coverageDataJson
|
||||
];
|
||||
}
|
||||
|
||||
$this->module->_setConfig(['cookies' => $cookies]);
|
||||
}
|
||||
|
||||
protected function fetchErrors(): void
|
||||
{
|
||||
// Calling grabCookie() while an alert is present dismisses the alert
|
||||
// @see https://github.com/Codeception/Codeception/issues/1485
|
||||
if ($this->module instanceof WebDriverModule) {
|
||||
try {
|
||||
$this->module->webDriver->switchTo()->alert()->getText();
|
||||
// If this succeeds an alert is present, abort
|
||||
return;
|
||||
} catch (NoSuchAlertException) {
|
||||
// No alert present, continue
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$error = $this->module->grabCookie(self::COVERAGE_COOKIE_ERROR);
|
||||
} catch (ModuleException) {
|
||||
// when a new session is started we can't get cookies because there is no
|
||||
// current page, but there can be no code coverage error either
|
||||
return;
|
||||
}
|
||||
if (!empty($error)) {
|
||||
$this->module->resetCookie(self::COVERAGE_COOKIE_ERROR);
|
||||
throw new RemoteException($error);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param string[] $headers */
|
||||
protected function getRemoteError(array $headers): void
|
||||
{
|
||||
foreach ($headers as $header) {
|
||||
if (str_starts_with($header, self::COVERAGE_HEADER_ERROR)) {
|
||||
throw new RemoteException($header);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function addC3AccessHeader(string $header, string $value): void
|
||||
{
|
||||
$headerString = "{$header}: {$value}\r\n";
|
||||
if (!str_contains((string) $this->c3Access['http']['header'], $headerString)) {
|
||||
$this->c3Access['http']['header'] .= $headerString;
|
||||
}
|
||||
}
|
||||
|
||||
protected function applySettings(array $settings): void
|
||||
{
|
||||
parent::applySettings($settings);
|
||||
if (isset($settings['coverage']['remote_context_options'])) {
|
||||
$this->c3Access = array_replace_recursive($this->c3Access, $settings['coverage']['remote_context_options']);
|
||||
}
|
||||
}
|
||||
|
||||
private function waitForFile(string $file, int $maxRetries, int $sleepTime): void
|
||||
{
|
||||
$retries = $maxRetries;
|
||||
while ($retries > 0 && (!file_exists($file) || file_get_contents($file) !== '0')) {
|
||||
usleep($sleepTime);
|
||||
$retries--;
|
||||
}
|
||||
|
||||
if (!file_exists($file) || file_get_contents($file) !== '0') {
|
||||
Notification::warning('Timeout: Some coverage data is not included in the coverage report.', '');
|
||||
}
|
||||
}
|
||||
}
|
||||
175
vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/Printer.php
vendored
Normal file
175
vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/Printer.php
vendored
Normal file
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Coverage\Subscriber;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Coverage\Filter;
|
||||
use Codeception\Coverage\PhpCodeCoverageFactory;
|
||||
use Codeception\Event\PrintResultEvent;
|
||||
use Codeception\Events;
|
||||
use Codeception\Lib\Console\Output;
|
||||
use Codeception\Subscriber\Shared\StaticEventsTrait;
|
||||
use PHPUnit\Runner\Version as PHPUnitVersion;
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
use SebastianBergmann\CodeCoverage\Report\Clover as CloverReport;
|
||||
use SebastianBergmann\CodeCoverage\Report\Cobertura as CoberturaReport;
|
||||
use SebastianBergmann\CodeCoverage\Report\Crap4j as Crap4jReport;
|
||||
use SebastianBergmann\CodeCoverage\Report\Html\Facade as HtmlFacadeReport;
|
||||
use SebastianBergmann\CodeCoverage\Report\PHP as PhpReport;
|
||||
use SebastianBergmann\CodeCoverage\Report\Text as TextReport;
|
||||
use SebastianBergmann\CodeCoverage\Report\Thresholds;
|
||||
use SebastianBergmann\CodeCoverage\Report\Xml\Facade as XmlFacadeReport;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
use function array_merge;
|
||||
use function file_put_contents;
|
||||
use function str_starts_with;
|
||||
use function strpos;
|
||||
|
||||
class Printer implements EventSubscriberInterface
|
||||
{
|
||||
use StaticEventsTrait;
|
||||
|
||||
/**
|
||||
* @var array<string, string>
|
||||
*/
|
||||
public static array $events = [
|
||||
Events::RESULT_PRINT_AFTER => 'printResult'
|
||||
];
|
||||
|
||||
protected array $settings = [
|
||||
'enabled' => true,
|
||||
'low_limit' => 35,
|
||||
'high_limit' => 70,
|
||||
'show_uncovered' => false,
|
||||
'show_only_summary' => false
|
||||
];
|
||||
|
||||
public static CodeCoverage $coverage;
|
||||
|
||||
protected string $logDir;
|
||||
|
||||
public function __construct(protected array $options, private readonly Output $output)
|
||||
{
|
||||
$this->logDir = Configuration::outputDir();
|
||||
$this->settings = array_merge($this->settings, Configuration::config()['coverage']);
|
||||
|
||||
self::$coverage = PhpCodeCoverageFactory::build();
|
||||
|
||||
// Apply filter
|
||||
$filter = new Filter(self::$coverage);
|
||||
$filter->whiteList(Configuration::config());
|
||||
$filter->blackList(Configuration::config());
|
||||
}
|
||||
|
||||
protected function absolutePath(string $path): string
|
||||
{
|
||||
if (str_starts_with($path, '/') || strpos($path, ':') === 1) { // absolute path
|
||||
return $path;
|
||||
}
|
||||
return $this->logDir . $path;
|
||||
}
|
||||
|
||||
public function printResult(PrintResultEvent $event): void
|
||||
{
|
||||
if (!$this->settings['enabled']) {
|
||||
$this->output->write("\nCodeCoverage is disabled in `codeception.yml` config\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->options['quiet']) {
|
||||
$this->printConsole();
|
||||
}
|
||||
$this->output->write("Remote CodeCoverage reports are not printed to console\n");
|
||||
if ($this->options['disable-coverage-php'] === true) {
|
||||
$this->output->write("PHP serialized report was skipped\n");
|
||||
} else {
|
||||
$this->printPHP();
|
||||
}
|
||||
$this->output->write("\n");
|
||||
|
||||
$reports = [
|
||||
'HTML' => ['name' => 'coverage-html', 'method' => 'printHTML'],
|
||||
'XML' => ['name' => 'coverage-xml', 'method' => 'printXML'],
|
||||
'Text' => ['name' => 'coverage-text', 'method' => 'printText'],
|
||||
'Crap4j' => ['name' => 'coverage-crap4j', 'method' => 'printCrap4j'],
|
||||
'Cobertura' => ['name' => 'coverage-cobertura', 'method' => 'printCobertura'],
|
||||
'PHPUnit' => ['name' => 'coverage-phpunit', 'method' => 'printPHPUnit'],
|
||||
];
|
||||
|
||||
foreach ($reports as $reportType => $reportData) {
|
||||
if ($option = $this->options[$reportData['name']]) {
|
||||
$this->{$reportData['method']}();
|
||||
$this->output->write("{$reportType} report generated in {$option}\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function printConsole(): void
|
||||
{
|
||||
$writer = $this->createTextWriter();
|
||||
$this->output->write($writer->process(self::$coverage, $this->options['colors']));
|
||||
}
|
||||
|
||||
protected function printHtml(): void
|
||||
{
|
||||
$writer = $this->createHtmlFacadeWriter();
|
||||
$writer->process(self::$coverage, $this->absolutePath($this->options['coverage-html']));
|
||||
}
|
||||
|
||||
protected function printXml(): void
|
||||
{
|
||||
$writer = new CloverReport();
|
||||
$writer->process(self::$coverage, $this->absolutePath($this->options['coverage-xml']));
|
||||
}
|
||||
|
||||
protected function printPHP(): void
|
||||
{
|
||||
$writer = new PhpReport();
|
||||
$writer->process(self::$coverage, $this->absolutePath($this->options['coverage']));
|
||||
}
|
||||
|
||||
protected function printText(): void
|
||||
{
|
||||
$writer = $this->createTextWriter();
|
||||
file_put_contents(
|
||||
$this->absolutePath($this->options['coverage-text']),
|
||||
$writer->process(self::$coverage)
|
||||
);
|
||||
}
|
||||
|
||||
protected function printCrap4j(): void
|
||||
{
|
||||
$writer = new Crap4jReport();
|
||||
$writer->process(self::$coverage, $this->absolutePath($this->options['coverage-crap4j']));
|
||||
}
|
||||
|
||||
protected function printCobertura(): void
|
||||
{
|
||||
$writer = new CoberturaReport();
|
||||
$writer->process(self::$coverage, $this->absolutePath($this->options['coverage-cobertura']));
|
||||
}
|
||||
|
||||
protected function printPHPUnit(): void
|
||||
{
|
||||
$writer = new XmlFacadeReport(PHPUnitVersion::id());
|
||||
$writer->process(self::$coverage, $this->absolutePath($this->options['coverage-phpunit']));
|
||||
}
|
||||
|
||||
private function createHtmlFacadeWriter(): HtmlFacadeReport
|
||||
{
|
||||
$generator = ', <a href="https://codeception.com">Codeception</a> and <a href="https://phpunit.de/">PHPUnit {PHPUnitVersion::id()}</a>';
|
||||
return PHPUnitVersion::series() < 10 ?
|
||||
new HtmlFacadeReport($this->settings['low_limit'], $this->settings['high_limit'], $generator) :
|
||||
new HtmlFacadeReport($generator, null, Thresholds::from($this->settings['low_limit'], $this->settings['high_limit']));
|
||||
}
|
||||
|
||||
private function createTextWriter(): TextReport
|
||||
{
|
||||
return PHPUnitVersion::series() < 10 ?
|
||||
new TextReport($this->settings['low_limit'], $this->settings['high_limit'], $this->settings['show_uncovered'], $this->settings['show_only_summary']) :
|
||||
new TextReport(Thresholds::from($this->settings['low_limit'], $this->settings['high_limit']), $this->settings['show_uncovered'], $this->settings['show_only_summary']);
|
||||
}
|
||||
}
|
||||
81
vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/RemoteServer.php
vendored
Normal file
81
vendor/codeception/codeception/src/Codeception/Coverage/Subscriber/RemoteServer.php
vendored
Normal file
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Coverage\Subscriber;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Event\SuiteEvent;
|
||||
use Codeception\Util\FileSystem;
|
||||
use PharData;
|
||||
|
||||
use function file_put_contents;
|
||||
use function is_dir;
|
||||
use function mkdir;
|
||||
use function strtr;
|
||||
use function sys_get_temp_dir;
|
||||
use function tempnam;
|
||||
use function unlink;
|
||||
|
||||
/**
|
||||
* When collecting code coverage on remote server
|
||||
* data is retrieved over HTTP and not merged with the local code coverage results.
|
||||
*
|
||||
* Class RemoteServer
|
||||
* @package Codeception\Coverage\Subscriber
|
||||
*/
|
||||
class RemoteServer extends LocalServer
|
||||
{
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->module && $this->settings['remote'] && $this->settings['enabled'];
|
||||
}
|
||||
|
||||
public function afterSuite(SuiteEvent $event): void
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return;
|
||||
}
|
||||
$suite = strtr($event->getSuite()->getName(), ['\\' => '.']);
|
||||
|
||||
if ($this->options['coverage-xml']) {
|
||||
$this->retrieveAndPrint('clover', $suite, '.remote.coverage.xml');
|
||||
}
|
||||
if ($this->options['coverage-html']) {
|
||||
$this->retrieveToTempFileAndPrint('html', $suite, '.remote.coverage');
|
||||
}
|
||||
if ($this->options['coverage-crap4j']) {
|
||||
$this->retrieveAndPrint('crap4j', $suite, '.remote.crap4j.xml');
|
||||
}
|
||||
if ($this->options['coverage-cobertura']) {
|
||||
$this->retrieveAndPrint('cobertura', $suite, '.remote.cobertura.xml');
|
||||
}
|
||||
if ($this->options['coverage-phpunit']) {
|
||||
$this->retrieveToTempFileAndPrint('phpunit', $suite, '.remote.coverage-phpunit');
|
||||
}
|
||||
}
|
||||
|
||||
protected function retrieveAndPrint(string $type, string $suite, string $extension): void
|
||||
{
|
||||
$destFile = Configuration::outputDir() . $suite . $extension;
|
||||
file_put_contents($destFile, $this->c3Request($type));
|
||||
}
|
||||
|
||||
protected function retrieveToTempFileAndPrint(string $type, string $suite, string $extension): void
|
||||
{
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'C3') . '.tar';
|
||||
file_put_contents($tempFile, $this->c3Request($type));
|
||||
|
||||
$destDir = Configuration::outputDir() . $suite . $extension;
|
||||
if (is_dir($destDir)) {
|
||||
FileSystem::doEmptyDir($destDir);
|
||||
} else {
|
||||
mkdir($destDir, 0777, true);
|
||||
}
|
||||
|
||||
$pharData = new PharData($tempFile);
|
||||
$pharData->extractTo($destDir);
|
||||
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
129
vendor/codeception/codeception/src/Codeception/Coverage/SuiteSubscriber.php
vendored
Normal file
129
vendor/codeception/codeception/src/Codeception/Coverage/SuiteSubscriber.php
vendored
Normal file
@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Coverage;
|
||||
|
||||
use Codeception\Configuration;
|
||||
use Codeception\Coverage\Subscriber\Printer;
|
||||
use Codeception\Exception\ConfigurationException;
|
||||
use Codeception\Lib\Interfaces\Remote as RemoteInterface;
|
||||
use Codeception\Subscriber\Shared\StaticEventsTrait;
|
||||
use Exception;
|
||||
use PHPUnit\Framework\CodeCoverageException;
|
||||
use SebastianBergmann\CodeCoverage\CodeCoverage;
|
||||
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||
|
||||
use function array_keys;
|
||||
|
||||
abstract class SuiteSubscriber implements EventSubscriberInterface
|
||||
{
|
||||
use StaticEventsTrait;
|
||||
|
||||
protected array $defaultSettings = [
|
||||
'enabled' => false,
|
||||
'remote' => false,
|
||||
'local' => false,
|
||||
'xdebug_session' => 'codeception',
|
||||
'remote_config' => null,
|
||||
'show_uncovered' => false,
|
||||
'c3_url' => null,
|
||||
'work_dir' => null,
|
||||
'cookie_domain' => null,
|
||||
'path_coverage' => false,
|
||||
'strict_covers_annotation' => false,
|
||||
'ignore_deprecated_code' => false,
|
||||
'disable_code_coverage_ignore' => false,
|
||||
];
|
||||
|
||||
protected array $settings = [];
|
||||
|
||||
protected array $filters = [];
|
||||
|
||||
protected array $modules = [];
|
||||
|
||||
protected ?CodeCoverage $coverage = null;
|
||||
|
||||
protected string $logDir;
|
||||
|
||||
public static array $events = [];
|
||||
|
||||
abstract protected function isEnabled();
|
||||
|
||||
/**
|
||||
* SuiteSubscriber constructor.
|
||||
*
|
||||
* @throws ConfigurationException
|
||||
*/
|
||||
public function __construct(protected array $options = [])
|
||||
{
|
||||
$this->logDir = Configuration::outputDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function applySettings(array $settings): void
|
||||
{
|
||||
try {
|
||||
$this->coverage = PhpCodeCoverageFactory::build();
|
||||
} catch (CodeCoverageException $e) {
|
||||
throw new Exception(
|
||||
'XDebug is required to collect CodeCoverage. Please install xdebug extension and enable it in php.ini',
|
||||
$e->getCode(),
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
$this->filters = $settings;
|
||||
$this->settings = $this->defaultSettings;
|
||||
$keys = array_keys($this->defaultSettings);
|
||||
foreach ($keys as $key) {
|
||||
if (isset($settings['coverage'][$key])) {
|
||||
$this->settings[$key] = $settings['coverage'][$key];
|
||||
}
|
||||
}
|
||||
|
||||
$this->configureCoverage();
|
||||
}
|
||||
|
||||
protected function configureCoverage(): void
|
||||
{
|
||||
if ($this->settings['strict_covers_annotation']) {
|
||||
$this->coverage->enableCheckForUnintentionallyCoveredCode();
|
||||
}
|
||||
|
||||
if ($this->settings['ignore_deprecated_code']) {
|
||||
$this->coverage->ignoreDeprecatedCode();
|
||||
} else {
|
||||
$this->coverage->doNotIgnoreDeprecatedCode();
|
||||
}
|
||||
|
||||
if ($this->settings['disable_code_coverage_ignore']) {
|
||||
$this->coverage->disableAnnotationsForIgnoringCode();
|
||||
} else {
|
||||
$this->coverage->enableAnnotationsForIgnoringCode();
|
||||
}
|
||||
|
||||
if ($this->settings['show_uncovered']) {
|
||||
$this->coverage->includeUncoveredFiles();
|
||||
} else {
|
||||
$this->coverage->excludeUncoveredFiles();
|
||||
}
|
||||
}
|
||||
|
||||
protected function getServerConnectionModule(array $modules): ?RemoteInterface
|
||||
{
|
||||
foreach ($modules as $module) {
|
||||
if ($module instanceof RemoteInterface) {
|
||||
return $module;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function mergeToPrint(CodeCoverage $coverage): void
|
||||
{
|
||||
Printer::$coverage->merge($coverage);
|
||||
}
|
||||
}
|
||||
13
vendor/codeception/codeception/src/Codeception/CustomCommandInterface.php
vendored
Normal file
13
vendor/codeception/codeception/src/Codeception/CustomCommandInterface.php
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception;
|
||||
|
||||
interface CustomCommandInterface
|
||||
{
|
||||
/**
|
||||
* returns the name of the command
|
||||
*/
|
||||
public static function getCommandName(): string;
|
||||
}
|
||||
21
vendor/codeception/codeception/src/Codeception/Event/FailEvent.php
vendored
Normal file
21
vendor/codeception/codeception/src/Codeception/Event/FailEvent.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Event;
|
||||
|
||||
use Codeception\Test\Test;
|
||||
use Throwable;
|
||||
|
||||
class FailEvent extends TestEvent
|
||||
{
|
||||
public function __construct(Test $test, private Throwable $fail, ?float $time)
|
||||
{
|
||||
parent::__construct($test, $time);
|
||||
}
|
||||
|
||||
public function getFail(): Throwable
|
||||
{
|
||||
return $this->fail;
|
||||
}
|
||||
}
|
||||
20
vendor/codeception/codeception/src/Codeception/Event/PrintResultEvent.php
vendored
Normal file
20
vendor/codeception/codeception/src/Codeception/Event/PrintResultEvent.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Event;
|
||||
|
||||
use Codeception\ResultAggregator;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class PrintResultEvent extends Event
|
||||
{
|
||||
public function __construct(protected ResultAggregator $result)
|
||||
{
|
||||
}
|
||||
|
||||
public function getResult(): ResultAggregator
|
||||
{
|
||||
return $this->result;
|
||||
}
|
||||
}
|
||||
26
vendor/codeception/codeception/src/Codeception/Event/StepEvent.php
vendored
Normal file
26
vendor/codeception/codeception/src/Codeception/Event/StepEvent.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Event;
|
||||
|
||||
use Codeception\Step;
|
||||
use Codeception\TestInterface;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class StepEvent extends Event
|
||||
{
|
||||
public function __construct(protected TestInterface $test, protected Step $step)
|
||||
{
|
||||
}
|
||||
|
||||
public function getStep(): Step
|
||||
{
|
||||
return $this->step;
|
||||
}
|
||||
|
||||
public function getTest(): TestInterface
|
||||
{
|
||||
return $this->test;
|
||||
}
|
||||
}
|
||||
26
vendor/codeception/codeception/src/Codeception/Event/SuiteEvent.php
vendored
Normal file
26
vendor/codeception/codeception/src/Codeception/Event/SuiteEvent.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Event;
|
||||
|
||||
use Codeception\ResultAggregator;
|
||||
use Codeception\Suite;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class SuiteEvent extends Event
|
||||
{
|
||||
public function __construct(protected ?Suite $suite = null, protected array $settings = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function getSuite(): ?Suite
|
||||
{
|
||||
return $this->suite;
|
||||
}
|
||||
|
||||
public function getSettings(): array
|
||||
{
|
||||
return $this->settings;
|
||||
}
|
||||
}
|
||||
28
vendor/codeception/codeception/src/Codeception/Event/TestEvent.php
vendored
Normal file
28
vendor/codeception/codeception/src/Codeception/Event/TestEvent.php
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Event;
|
||||
|
||||
use Codeception\Test\Test;
|
||||
use Symfony\Contracts\EventDispatcher\Event;
|
||||
|
||||
class TestEvent extends Event
|
||||
{
|
||||
/**
|
||||
* @param float|null $time Time taken
|
||||
*/
|
||||
public function __construct(protected Test $test, protected ?float $time = 0)
|
||||
{
|
||||
}
|
||||
|
||||
public function getTime(): float
|
||||
{
|
||||
return $this->time;
|
||||
}
|
||||
|
||||
public function getTest(): Test
|
||||
{
|
||||
return $this->test;
|
||||
}
|
||||
}
|
||||
176
vendor/codeception/codeception/src/Codeception/Events.php
vendored
Normal file
176
vendor/codeception/codeception/src/Codeception/Events.php
vendored
Normal file
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception;
|
||||
|
||||
/**
|
||||
* Contains all events dispatched by Codeception.
|
||||
*
|
||||
* @author tiger-seo <tiger.seo@gmail.com>
|
||||
*/
|
||||
final class Events
|
||||
{
|
||||
/**
|
||||
* Private constructor. This class cannot be instantiated.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* The <b>MODULE_INIT</b> event occurs before modules are initialized.
|
||||
*
|
||||
* The event listener method receives a {@link \Codeception\Event\SuiteEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const MODULE_INIT = 'module.init';
|
||||
|
||||
/**
|
||||
* The <b>SUITE_INIT</b> event occurs when suite is initialized.
|
||||
* Modules are created and initialized, but Actor class is not loaded.
|
||||
*
|
||||
* The event listener method receives a {@link \Codeception\Event\SuiteEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const SUITE_INIT = 'suite.init';
|
||||
|
||||
/**
|
||||
* The <b>SUITE_BEFORE</b> event occurs before suite is executed.
|
||||
*
|
||||
* The event listener method receives a {@link \Codeception\Event\SuiteEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const SUITE_BEFORE = 'suite.before';
|
||||
|
||||
/**
|
||||
* The <b>SUITE_AFTER</b> event occurs after suite has been executed.
|
||||
*
|
||||
* The event listener method receives a {@link \Codeception\Event\SuiteEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const SUITE_AFTER = 'suite.after';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\TestEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_START = 'test.start';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\TestEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_BEFORE = 'test.before';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\StepEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const STEP_BEFORE = 'step.before';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\StepEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const STEP_AFTER = 'step.after';
|
||||
|
||||
/**
|
||||
* The <b>TEST_FAIL</b> event occurs whenever test has failed.
|
||||
*
|
||||
* The event listener method receives a {@link \Codeception\Event\FailEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_FAIL = 'test.fail';
|
||||
|
||||
/**
|
||||
* The <b>TEST_ERROR</b> event occurs whenever test got an error while being executed.
|
||||
*
|
||||
* The event listener method receives a {@link \Codeception\Event\FailEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_ERROR = 'test.error';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\TestEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_PARSED = 'test.parsed';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\FailEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_INCOMPLETE = 'test.incomplete';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\FailEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_SKIPPED = 'test.skipped';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\FailEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_WARNING = 'test.warning';
|
||||
|
||||
/**
|
||||
* The <b>TEST_USELESS</b> event occurs whenever test does not execute any assertions
|
||||
* or when it calls expectNotToPerformAssertions and then performs some assertion.
|
||||
*
|
||||
* The event listener method receives a {@link \Codeception\Event\FailEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_USELESS = 'test.useless';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\TestEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_SUCCESS = 'test.success';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\TestEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_AFTER = 'test.after';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\TestEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_END = 'test.end';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\FailEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const TEST_FAIL_PRINT = 'test.fail.print';
|
||||
|
||||
/**
|
||||
* The event listener method receives a {@link \Codeception\Event\PrintResultEvent} instance.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const RESULT_PRINT_AFTER = 'result.print.after';
|
||||
}
|
||||
93
vendor/codeception/codeception/src/Codeception/Example.php
vendored
Normal file
93
vendor/codeception/codeception/src/Codeception/Example.php
vendored
Normal file
@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception;
|
||||
|
||||
use ArrayAccess;
|
||||
use ArrayIterator;
|
||||
use Countable;
|
||||
use IteratorAggregate;
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
use Traversable;
|
||||
|
||||
class Example implements ArrayAccess, Countable, IteratorAggregate
|
||||
{
|
||||
public function __construct(protected $data)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an offset exists
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
* @param mixed $offset <p>An offset to check for.</p>
|
||||
* @return bool true on success or false on failure.
|
||||
* The return value will be casted to boolean if non-boolean was returned.
|
||||
*/
|
||||
public function offsetExists(mixed $offset): bool
|
||||
{
|
||||
return array_key_exists($offset, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayaccess.offsetget.php
|
||||
* @param mixed $offset <p>The offset to retrieve.</p>
|
||||
* @return mixed Can return all value types.
|
||||
*/
|
||||
public function offsetGet(mixed $offset): mixed
|
||||
{
|
||||
if (!$this->offsetExists($offset)) {
|
||||
throw new AssertionFailedError(sprintf("Example %s doesn't exist", $offset));
|
||||
}
|
||||
return $this->data[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayaccess.offsetset.php
|
||||
* @param mixed $offset <p>The offset to assign the value to.</p>
|
||||
* @param mixed $value <p>The value to set.</p>
|
||||
*/
|
||||
public function offsetSet(mixed $offset, mixed $value): void
|
||||
{
|
||||
$this->data[$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset
|
||||
*
|
||||
* @link https://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
* @param mixed $offset <p>The offset to unset.</p>
|
||||
*/
|
||||
public function offsetUnset(mixed $offset): void
|
||||
{
|
||||
unset($this->data[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
*
|
||||
* @link https://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* The return value is cast to an integer.
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an external iterator
|
||||
*
|
||||
* @link https://php.net/manual/en/iteratoraggregate.getiterator.php
|
||||
* @return Traversable An instance of an object implementing <b>Iterator</b> or <b>Traversable</b>
|
||||
*/
|
||||
public function getIterator(): Traversable
|
||||
{
|
||||
return new ArrayIterator($this->data);
|
||||
}
|
||||
}
|
||||
11
vendor/codeception/codeception/src/Codeception/Exception/ConditionalAssertionFailed.php
vendored
Normal file
11
vendor/codeception/codeception/src/Codeception/Exception/ConditionalAssertionFailed.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
|
||||
class ConditionalAssertionFailed extends AssertionFailedError
|
||||
{
|
||||
}
|
||||
11
vendor/codeception/codeception/src/Codeception/Exception/ConfigurationException.php
vendored
Normal file
11
vendor/codeception/codeception/src/Codeception/Exception/ConfigurationException.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ConfigurationException extends Exception
|
||||
{
|
||||
}
|
||||
11
vendor/codeception/codeception/src/Codeception/Exception/ContentNotFound.php
vendored
Normal file
11
vendor/codeception/codeception/src/Codeception/Exception/ContentNotFound.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
|
||||
class ContentNotFound extends AssertionFailedError
|
||||
{
|
||||
}
|
||||
9
vendor/codeception/codeception/src/Codeception/Exception/Deprecation.php
vendored
Normal file
9
vendor/codeception/codeception/src/Codeception/Exception/Deprecation.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
class Deprecation extends Error
|
||||
{
|
||||
}
|
||||
18
vendor/codeception/codeception/src/Codeception/Exception/Error.php
vendored
Normal file
18
vendor/codeception/codeception/src/Codeception/Exception/Error.php
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class Error extends Exception
|
||||
{
|
||||
public function __construct(string $message, int $code, string $file, int $line, ?\Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, $code, $previous);
|
||||
|
||||
$this->file = $file;
|
||||
$this->line = $line;
|
||||
}
|
||||
}
|
||||
26
vendor/codeception/codeception/src/Codeception/Exception/ExtensionException.php
vendored
Normal file
26
vendor/codeception/codeception/src/Codeception/Exception/ExtensionException.php
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
use function is_object;
|
||||
|
||||
class ExtensionException extends Exception
|
||||
{
|
||||
/**
|
||||
* ExtensionException constructor.
|
||||
*
|
||||
* @param object|string $extension
|
||||
*/
|
||||
public function __construct($extension, string $message, ?Exception $previous = null)
|
||||
{
|
||||
parent::__construct($message, 0, $previous);
|
||||
if (is_object($extension)) {
|
||||
$extension = $extension::class;
|
||||
}
|
||||
$this->message = $extension . "\n\n" . $this->message;
|
||||
}
|
||||
}
|
||||
11
vendor/codeception/codeception/src/Codeception/Exception/InjectionException.php
vendored
Normal file
11
vendor/codeception/codeception/src/Codeception/Exception/InjectionException.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InjectionException extends Exception
|
||||
{
|
||||
}
|
||||
11
vendor/codeception/codeception/src/Codeception/Exception/InvalidTestException.php
vendored
Normal file
11
vendor/codeception/codeception/src/Codeception/Exception/InvalidTestException.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class InvalidTestException extends Exception
|
||||
{
|
||||
}
|
||||
29
vendor/codeception/codeception/src/Codeception/Exception/ModuleConfigException.php
vendored
Normal file
29
vendor/codeception/codeception/src/Codeception/Exception/ModuleConfigException.php
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
use function is_object;
|
||||
use function ltrim;
|
||||
use function str_replace;
|
||||
|
||||
class ModuleConfigException extends Exception
|
||||
{
|
||||
/**
|
||||
* ModuleConfigException constructor.
|
||||
*
|
||||
* @param object|string $module
|
||||
*/
|
||||
public function __construct($module, string $message, ?Exception $previous = null)
|
||||
{
|
||||
if (is_object($module)) {
|
||||
$module = $module::class;
|
||||
}
|
||||
$module = str_replace('Codeception\Module\\', '', ltrim($module, '\\'));
|
||||
parent::__construct($message, 0, $previous);
|
||||
$this->message = $module . " module is not configured!\n \n" . $this->message;
|
||||
}
|
||||
}
|
||||
38
vendor/codeception/codeception/src/Codeception/Exception/ModuleConflictException.php
vendored
Normal file
38
vendor/codeception/codeception/src/Codeception/Exception/ModuleConflictException.php
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
use function is_object;
|
||||
use function ltrim;
|
||||
use function str_replace;
|
||||
|
||||
class ModuleConflictException extends Exception
|
||||
{
|
||||
/**
|
||||
* ModuleConflictException constructor.
|
||||
*
|
||||
* @param object|string $module
|
||||
* @param object|string $conflicted
|
||||
*/
|
||||
public function __construct($module, $conflicted, string $additional = '')
|
||||
{
|
||||
if (is_object($module)) {
|
||||
$module = $module::class;
|
||||
}
|
||||
if (is_object($conflicted)) {
|
||||
$conflicted = $conflicted::class;
|
||||
}
|
||||
$module = ltrim(str_replace('Codeception\Module\\', '', $module), '\\');
|
||||
$conflicted = ltrim(str_replace('Codeception\Module\\', '', $conflicted), '\\');
|
||||
$this->message = "{$module} module conflicts with {$conflicted}\n\n--\n"
|
||||
. "This usually happens when you enable two modules with the same actions but with different backends.\n"
|
||||
. "For instance, you can't run PhpBrowser, WebDriver, Laravel5 modules in one suite,\n"
|
||||
. "as they implement similar methods but use different drivers to execute them.\n"
|
||||
. "You can load a part of module (like: ORM) to avoid conflict.\n"
|
||||
. $additional;
|
||||
}
|
||||
}
|
||||
32
vendor/codeception/codeception/src/Codeception/Exception/ModuleException.php
vendored
Normal file
32
vendor/codeception/codeception/src/Codeception/Exception/ModuleException.php
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
use function is_object;
|
||||
use function ltrim;
|
||||
use function str_replace;
|
||||
|
||||
class ModuleException extends Exception
|
||||
{
|
||||
protected string $module;
|
||||
|
||||
/**
|
||||
* ModuleException constructor.
|
||||
*
|
||||
* @param object|string $module
|
||||
*/
|
||||
public function __construct($module, string $message)
|
||||
{
|
||||
if (is_object($module)) {
|
||||
$module = $module::class;
|
||||
}
|
||||
$module = ltrim(str_replace('Codeception\Module\\', '', $module), '\\');
|
||||
$this->module = $module;
|
||||
parent::__construct($message);
|
||||
$this->message = "{$module}: {$this->message}";
|
||||
}
|
||||
}
|
||||
29
vendor/codeception/codeception/src/Codeception/Exception/ModuleRequireException.php
vendored
Normal file
29
vendor/codeception/codeception/src/Codeception/Exception/ModuleRequireException.php
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
use function is_object;
|
||||
use function ltrim;
|
||||
use function str_replace;
|
||||
|
||||
class ModuleRequireException extends Exception
|
||||
{
|
||||
/**
|
||||
* ModuleRequireException constructor.
|
||||
*
|
||||
* @param object|string $module
|
||||
*/
|
||||
public function __construct($module, string $message)
|
||||
{
|
||||
if (is_object($module)) {
|
||||
$module = $module::class;
|
||||
}
|
||||
$module = str_replace('Codeception\\Module\\', '', ltrim($module, '\\'));
|
||||
parent::__construct($message);
|
||||
$this->message = "[{$module}] module requirements not met --\n \n" . $this->message;
|
||||
}
|
||||
}
|
||||
9
vendor/codeception/codeception/src/Codeception/Exception/Notice.php
vendored
Normal file
9
vendor/codeception/codeception/src/Codeception/Exception/Notice.php
vendored
Normal file
@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
class Notice extends Error
|
||||
{
|
||||
}
|
||||
11
vendor/codeception/codeception/src/Codeception/Exception/ParseException.php
vendored
Normal file
11
vendor/codeception/codeception/src/Codeception/Exception/ParseException.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class ParseException extends Exception
|
||||
{
|
||||
}
|
||||
16
vendor/codeception/codeception/src/Codeception/Exception/RemoteException.php
vendored
Normal file
16
vendor/codeception/codeception/src/Codeception/Exception/RemoteException.php
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class RemoteException extends Exception
|
||||
{
|
||||
public function __construct(string $message)
|
||||
{
|
||||
parent::__construct($message);
|
||||
$this->message = "Remote Application Error:\n" . $this->message;
|
||||
}
|
||||
}
|
||||
21
vendor/codeception/codeception/src/Codeception/Exception/TestParseException.php
vendored
Normal file
21
vendor/codeception/codeception/src/Codeception/Exception/TestParseException.php
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Exception;
|
||||
|
||||
class TestParseException extends Exception
|
||||
{
|
||||
public function __construct(string $fileName, ?string $errors = null, ?int $line = null)
|
||||
{
|
||||
$this->message = "Couldn't parse test '{$fileName}'";
|
||||
if ($line !== null) {
|
||||
$this->message .= " on line {$line}";
|
||||
}
|
||||
if ($errors) {
|
||||
$this->message .= PHP_EOL . $errors;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
vendor/codeception/codeception/src/Codeception/Exception/TestRuntimeException.php
vendored
Normal file
11
vendor/codeception/codeception/src/Codeception/Exception/TestRuntimeException.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
class TestRuntimeException extends RuntimeException
|
||||
{
|
||||
}
|
||||
20
vendor/codeception/codeception/src/Codeception/Exception/ThrowableWrapper.php
vendored
Normal file
20
vendor/codeception/codeception/src/Codeception/Exception/ThrowableWrapper.php
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class ThrowableWrapper extends Error
|
||||
{
|
||||
public function __construct(Throwable $throwable)
|
||||
{
|
||||
parent::__construct(
|
||||
$throwable::class . ': ' . $throwable->getMessage(),
|
||||
$throwable->getCode(),
|
||||
$throwable->getFile(),
|
||||
$throwable->getLine()
|
||||
);
|
||||
}
|
||||
}
|
||||
11
vendor/codeception/codeception/src/Codeception/Exception/UselessTestException.php
vendored
Normal file
11
vendor/codeception/codeception/src/Codeception/Exception/UselessTestException.php
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Codeception\Exception;
|
||||
|
||||
use PHPUnit\Framework\AssertionFailedError;
|
||||
|
||||
class UselessTestException extends AssertionFailedError
|
||||
{
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user