From 31995bd9d4d10bb6f065a9674dc2f742760c78cf Mon Sep 17 00:00:00 2001 From: Edward Surov Date: Mon, 29 Nov 2021 13:39:27 +0200 Subject: [PATCH] external config file (fixes #66, via #68) --- composer.json | 12 +- phpunit.report.xml | 8 +- src/AllureExtension.php | 101 ++++-- src/Internal/Config.php | 172 +++++++++ src/Internal/ConfigInterface.php | 29 ++ src/Internal/TestLifecycleFactory.php | 25 -- .../TestLifecycleFactoryInterface.php | 13 - src/Internal/TestUpdater.php | 12 +- src/Setup/ConfiguratorInterface.php | 26 -- src/Setup/DefaultConfigurator.php | 77 ---- test/unit/AllureExtensionTest.php | 163 ++------- test/unit/Internal/ConfigTest.php | 339 ++++++++++++++++++ test/unit/Internal/TestLifecycleHook.php | 11 + test/unit/Internal/TestLinkTemplate.php | 16 + test/unit/Internal/TestThreadDetector.php | 21 ++ test/unit/Setup/DefaultConfiguratorTest.php | 102 ------ test/unit/TestConfigurator.php | 68 ---- test/unit/TestConfiguratorInterface.php | 12 - 18 files changed, 712 insertions(+), 495 deletions(-) create mode 100644 src/Internal/Config.php create mode 100644 src/Internal/ConfigInterface.php delete mode 100644 src/Internal/TestLifecycleFactory.php delete mode 100644 src/Internal/TestLifecycleFactoryInterface.php delete mode 100644 src/Setup/ConfiguratorInterface.php delete mode 100644 src/Setup/DefaultConfigurator.php create mode 100644 test/unit/Internal/ConfigTest.php create mode 100644 test/unit/Internal/TestLifecycleHook.php create mode 100644 test/unit/Internal/TestLinkTemplate.php create mode 100644 test/unit/Internal/TestThreadDetector.php delete mode 100644 test/unit/Setup/DefaultConfiguratorTest.php delete mode 100644 test/unit/TestConfigurator.php delete mode 100644 test/unit/TestConfiguratorInterface.php diff --git a/composer.json b/composer.json index e9de672..2e1c184 100644 --- a/composer.json +++ b/composer.json @@ -30,15 +30,17 @@ }, "require": { "php": "^8", - "allure-framework/allure-php-commons": "2.0.0-rc3", + "allure-framework/allure-php-commons": "2.0.0-rc4", "phpunit/phpunit": "^9" }, "require-dev": { - "ext-dom": "*", - "brianium/paratest": "^6.3.1", + "brianium/paratest": "^6.3.3", "psalm/plugin-phpunit": "^0.16.1", - "squizlabs/php_codesniffer": "^3.6.0", - "vimeo/psalm": "^4.10" + "squizlabs/php_codesniffer": "^3.6.1", + "vimeo/psalm": "^4.13.1" + }, + "conflict": { + "amphp/byte-stream": "<1.5.1" }, "autoload": { "psr-4": { diff --git a/phpunit.report.xml b/phpunit.report.xml index fdb7b19..20654ab 100644 --- a/phpunit.report.xml +++ b/phpunit.report.xml @@ -18,12 +18,6 @@ - - - - - Qameta\Allure\PHPUnit\Test\Report\Hook\OnSetupHook - - + diff --git a/src/AllureExtension.php b/src/AllureExtension.php index c51a602..7ba8cbe 100644 --- a/src/AllureExtension.php +++ b/src/AllureExtension.php @@ -4,7 +4,6 @@ namespace Qameta\Allure\PHPUnit; -use LogicException; use PHPUnit\Runner\AfterIncompleteTestHook; use PHPUnit\Runner\AfterRiskyTestHook; use PHPUnit\Runner\AfterSkippedTestHook; @@ -14,15 +13,19 @@ use PHPUnit\Runner\AfterTestHook; use PHPUnit\Runner\AfterTestWarningHook; use PHPUnit\Runner\BeforeTestHook; +use Qameta\Allure\Allure; +use Qameta\Allure\Model\LinkType; use Qameta\Allure\Model\Status; -use Qameta\Allure\PHPUnit\Internal\TestLifecycleFactory; -use Qameta\Allure\PHPUnit\Internal\TestLifecycleFactoryInterface; +use Qameta\Allure\PHPUnit\Internal\Config; +use Qameta\Allure\PHPUnit\Internal\ConfigInterface; +use Qameta\Allure\PHPUnit\Internal\DefaultThreadDetector; +use Qameta\Allure\PHPUnit\Internal\TestLifecycle; use Qameta\Allure\PHPUnit\Internal\TestLifecycleInterface; -use Qameta\Allure\PHPUnit\Setup\ConfiguratorInterface; -use Qameta\Allure\PHPUnit\Setup\DefaultConfigurator; +use Qameta\Allure\PHPUnit\Internal\TestUpdater; +use RuntimeException; -use function class_exists; -use function is_a; +use function file_exists; +use function is_array; use const DIRECTORY_SEPARATOR; @@ -39,41 +42,83 @@ final class AllureExtension implements { private const DEFAULT_OUTPUT_DIRECTORY = 'build' . DIRECTORY_SEPARATOR . 'allure-results'; + private const DEFAULT_CONFIG_FILE = 'config' . DIRECTORY_SEPARATOR . 'allure.config.php'; + private TestLifecycleInterface $testLifecycle; public function __construct( - ?string $outputDirectory = null, - string|ConfiguratorInterface|null $configurator = null, - mixed ...$args, + string|array|ConfigInterface|TestLifecycleInterface|null $configOrTestLifecycle = null, ) { - if (!$configurator instanceof ConfiguratorInterface) { - $configurator = $this->createConfigurator( - $configurator ?? DefaultConfigurator::class, - ...$args, + $this->testLifecycle = $configOrTestLifecycle instanceof TestLifecycleInterface + ? $configOrTestLifecycle + : $this->createTestLifecycle($configOrTestLifecycle); + } + + private function createTestLifecycle(string|array|ConfigInterface|null $configSource): TestLifecycleInterface + { + $config = $configSource instanceof ConfigInterface + ? $configSource + : $this->loadConfig($configSource); + + $this->setupAllure($config); + + return new TestLifecycle( + Allure::getLifecycle(), + Allure::getConfig()->getResultFactory(), + Allure::getConfig()->getStatusDetector(), + $config->getThreadDetector() ?? new DefaultThreadDetector(), + AllureAdapter::getInstance(), + new TestUpdater(Allure::getConfig()->getLinkTemplates()), + ); + } + + private function setupAllure(ConfigInterface $config): void + { + Allure::setOutputDirectory($config->getOutputDirectory() ?? self::DEFAULT_OUTPUT_DIRECTORY); + + foreach ($config->getLinkTemplates() as $linkType => $linkTemplate) { + Allure::getLifecycleConfigurator()->addLinkTemplate( + LinkType::fromOptionalString($linkType), + $linkTemplate, ); } - $configurator->setupAllure($outputDirectory ?? self::DEFAULT_OUTPUT_DIRECTORY); - $this->testLifecycle = $this->createTestLifecycleInterface($configurator); + + if (!empty($config->getLifecycleHooks())) { + Allure::getLifecycleConfigurator()->addHooks(...$config->getLifecycleHooks()); + } + + $setupHook = $config->getSetupHook(); + if (isset($setupHook)) { + $setupHook(); + } } - private function createConfigurator(string $class, mixed ...$args): ConfiguratorInterface + private function loadConfig(string|array|null $configSource): ConfigInterface { - return - class_exists($class) && - is_a($class, ConfiguratorInterface::class, true) - ? new $class(...$args) - : throw new LogicException("Invalid configurator class: {$class}"); + return new Config( + is_array($configSource) + ? $configSource + : $this->loadConfigData($configSource), + ); } - private function createTestLifecycleInterface(ConfiguratorInterface $configurator): TestLifecycleInterface + private function loadConfigData(?string $configFile): array { - $testLifecycleFactory = $configurator instanceof TestLifecycleFactoryInterface - ? $configurator - : new TestLifecycleFactory(); + $fileShouldExist = isset($configFile); + $configFile ??= self::DEFAULT_CONFIG_FILE; + if (file_exists($configFile)) { + /** @psalm-var mixed $data */ + $data = require $configFile; + + return is_array($data) + ? $data + : throw new RuntimeException("Config file {$configFile} must return array"); + } elseif ($fileShouldExist) { + throw new RuntimeException("Config file {$configFile} doesn't exist"); + } - return $testLifecycleFactory->createTestLifecycle($configurator); + return []; } - public function executeBeforeTest(string $test): void { $this diff --git a/src/Internal/Config.php b/src/Internal/Config.php new file mode 100644 index 0000000..6de063a --- /dev/null +++ b/src/Internal/Config.php @@ -0,0 +1,172 @@ +data[$key])) { + return null; + } + + /** @psalm-var mixed $outputDirectory */ + $outputDirectory = $this->data[$key]; + + return is_string($outputDirectory) + ? $outputDirectory + : throw new RuntimeException("Config key \"{$key}\" should contain a string"); + } + + /** + * @return array + */ + public function getLinkTemplates(): array + { + $key = 'linkTemplates'; + $linkTemplates = []; + /** @psalm-var mixed $linkTemplateSource */ + foreach ($this->getArrayFromData($key) as $linkKey => $linkTemplateSource) { + if (!is_string($linkKey)) { + throw new RuntimeException( + "Config key \"{$key}\" should contain an array with string keys only", + ); + } + $linkTemplates[$linkKey] = $this->buildObject( + "{$key}/{$linkKey}", + $linkTemplateSource, + LinkTemplateInterface::class, + ); + } + + return $linkTemplates; + } + + private function getArrayFromData(string $key): array + { + /** @psalm-var mixed $source */ + $source = $this->data[$key] ?? []; + + return is_array($source) + ? $source + : throw new RuntimeException("Config key \"{$key}\" should contain an array"); + } + + /** + * @template T + * @param string $key + * @param mixed $source + * @param class-string $expectedClass + * @return T + * @psalm-suppress MixedMethodCall + */ + private function buildObject(string $key, mixed $source, string $expectedClass): object + { + return match (true) { + $source instanceof $expectedClass => $source, + $this->isExpectedClassName($source, $expectedClass) => new $source(), + is_callable($source) => $this->buildObject($key, $source(), $expectedClass), + default => throw new RuntimeException( + "Config key \"{$key}\" contains invalid source of {$expectedClass}", + ), + }; + } + + /** + * @template T + * @param mixed $source + * @param class-string $expectedClass + * @return bool + * @psalm-assert-if-true class-string $source + */ + private function isExpectedClassName(mixed $source, string $expectedClass): bool + { + return $this->isClassName($source) && is_a($source, $expectedClass, true); + } + + /** + * @psalm-assert-if-true class-string $source + */ + private function isClassName(mixed $source): bool + { + return is_string($source) && class_exists($source); + } + + public function getSetupHook(): ?callable + { + $key = 'setupHook'; + /** @psalm-var mixed $source */ + $source = $this->data[$key] ?? null; + + return isset($source) + ? $this->buildCallable($key, $source) + : null; + } + + /** + * @psalm-suppress MixedMethodCall + */ + private function buildCallable(string $key, mixed $source): callable + { + return match (true) { + is_callable($source) => $source, + $this->isClassName($source) => $this->buildCallable($key, new $source()), + default => throw new RuntimeException("Config key \"{$key}\" should contain a callable"), + }; + } + + public function getThreadDetector(): ?ThreadDetectorInterface + { + $key = 'threadDetector'; + /** @var mixed $threadDetector */ + $threadDetector = $this->data[$key] ?? null; + + return isset($threadDetector) + ? $this->buildObject($key, $threadDetector, ThreadDetectorInterface::class) + : null; + } + + /** + * @return list + */ + public function getLifecycleHooks(): array + { + $key = 'lifecycleHooks'; + $hooks = []; + /** @psalm-var mixed $hookSource */ + foreach ($this->getArrayFromData($key) as $index => $hookSource) { + if (!is_int($index)) { + throw new RuntimeException( + "Config key \"{$key}\" should contain an array with integer keys only", + ); + } + $hooks[] = $this->buildObject( + "{$key}/{$index}", + $hookSource, + LifecycleHookInterface::class, + ); + } + + return $hooks; + } +} diff --git a/src/Internal/ConfigInterface.php b/src/Internal/ConfigInterface.php new file mode 100644 index 0000000..10f7d7e --- /dev/null +++ b/src/Internal/ConfigInterface.php @@ -0,0 +1,29 @@ + + */ + public function getLinkTemplates(): array; + + public function getSetupHook(): ?callable; + + public function getThreadDetector(): ?ThreadDetectorInterface; + + /** + * @return list + */ + public function getLifecycleHooks(): array; +} diff --git a/src/Internal/TestLifecycleFactory.php b/src/Internal/TestLifecycleFactory.php deleted file mode 100644 index 131902f..0000000 --- a/src/Internal/TestLifecycleFactory.php +++ /dev/null @@ -1,25 +0,0 @@ -getAllureLifecycle() ?? Allure::getLifecycle(), - $configurator->getResultFactory() ?? Allure::getResultFactory(), - $configurator->getStatusDetector() ?? Allure::getStatusDetector(), - $configurator->getThreadDetector() ?? new DefaultThreadDetector(), - $configurator->getAllureAdapter() ?? AllureAdapter::getInstance(), - new TestUpdater(), - ); - } -} diff --git a/src/Internal/TestLifecycleFactoryInterface.php b/src/Internal/TestLifecycleFactoryInterface.php deleted file mode 100644 index 6c62284..0000000 --- a/src/Internal/TestLifecycleFactoryInterface.php +++ /dev/null @@ -1,13 +0,0 @@ -parseAnnotations($info); @@ -53,7 +59,7 @@ private function parseAnnotations(TestInfo $info): AttributeParser { $class = $info->getClass(); if (!isset($class)) { - return new AttributeParser([]); + return new AttributeParser([], $this->linkTemplates); } $annotations = []; @@ -73,7 +79,7 @@ private function parseAnnotations(TestInfo $info): AttributeParser $method = $info->getMethod(); if (!isset($method)) { - return new AttributeParser($annotations); + return new AttributeParser($annotations, $this->linkTemplates); } try { @@ -86,7 +92,7 @@ private function parseAnnotations(TestInfo $info): AttributeParser throw new LogicException("Annotations not loaded", 0, $e); } - return new AttributeParser($annotations); + return new AttributeParser($annotations, $this->linkTemplates); } /** diff --git a/src/Setup/ConfiguratorInterface.php b/src/Setup/ConfiguratorInterface.php deleted file mode 100644 index 1fe3b52..0000000 --- a/src/Setup/ConfiguratorInterface.php +++ /dev/null @@ -1,26 +0,0 @@ -onAllureSetup)) { - call_user_func($this->loadSetupHook($this->onAllureSetup)); - } - } - - private function loadSetupHook(string $hook): callable - { - $callable = $this->prepareHook($hook); - - return is_callable($callable) - ? $callable - : throw new LogicException("Invalid setup hook: {$hook}"); - } - - private function prepareHook(string $hook): string|object - { - if (class_exists($hook)) { - /** @psalm-suppress MixedMethodCall */ - return new $hook(); - } - - return $hook; - } - - public function getAllureLifecycle(): ?AllureLifecycleInterface - { - return null; - } - - public function getResultFactory(): ?ResultFactoryInterface - { - return null; - } - - public function getStatusDetector(): ?StatusDetectorInterface - { - return null; - } - - public function getAllureAdapter(): ?AllureAdapterInterface - { - return null; - } - - public function getThreadDetector(): ?ThreadDetectorInterface - { - return null; - } -} diff --git a/test/unit/AllureExtensionTest.php b/test/unit/AllureExtensionTest.php index 63a47e9..7a1f45a 100644 --- a/test/unit/AllureExtensionTest.php +++ b/test/unit/AllureExtensionTest.php @@ -4,18 +4,15 @@ namespace Qameta\Allure\PHPUnit\Test\Unit; -use LogicException; use PHPUnit\Framework\TestCase; use Qameta\Allure\Allure; use Qameta\Allure\Model\Status; use Qameta\Allure\PHPUnit\AllureExtension; +use Qameta\Allure\PHPUnit\Internal\ConfigInterface; use Qameta\Allure\PHPUnit\Internal\TestLifecycleInterface; -use Qameta\Allure\PHPUnit\Setup\ConfiguratorInterface; -use stdClass; +use Qameta\Allure\Setup\LifecycleBuilderInterface; use const DIRECTORY_SEPARATOR; -use const STDERR; -use const STDOUT; /** * @covers \Qameta\Allure\PHPUnit\AllureExtension @@ -26,22 +23,26 @@ class AllureExtensionTest extends TestCase public function setUp(): void { Allure::reset(); - TestConfigurator::reset(); } /** * @dataProvider providerOutputDirectory */ - public function testConstruct_GivenOutputDirectory_SetupsAllureWithMatchingDirectory( + public function testConstruct_ConfigProvidesOutputDirectory_ConstructsResultsWriterWithWithMatchingDirectory( ?string $outputDirectory, string $expectedValue, ): void { - $configurator = $this->createMock(TestConfiguratorInterface::class); - $configurator + $builder = $this->createMock(LifecycleBuilderInterface::class); + Allure::setLifecycleBuilder($builder); + $config = $this->createStub(ConfigInterface::class); + $config + ->method('getOutputDirectory') + ->willReturn($outputDirectory); + $builder ->expects(self::once()) - ->method('setupAllure') + ->method('createResultsWriter') ->with(self::identicalTo($expectedValue)); - new AllureExtension($outputDirectory, $configurator); + new AllureExtension($config); } /** @@ -56,81 +57,25 @@ public function providerOutputDirectory(): iterable } /** - * @dataProvider providerInvalidConfigurator - */ - public function testConstructor_GivenInvalidConfiguratorString_ThrowsException(string $configurator): void - { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Invalid configurator class'); - new AllureExtension(null, $configurator); - } - - /** - * @return iterable - */ - public function providerInvalidConfigurator(): iterable - { - return [ - 'Not a class' => ['a'], - 'Class not implementing configurator' => [stdClass::class], - ]; - } - - /** - * @dataProvider providerValidConfigurator + * @dataProvider providerOutputDirectory */ - public function testConstructor_GivenValidConfiguratorString_NeverThrowsException( - ?string $configurator, + public function testConstruct_ConfigDataProvidesOutputDirectory_ConstructsResultsWriterWithWithMatchingDirectory( + ?string $outputDirectory, + string $expectedValue, ): void { - $this->expectNotToPerformAssertions(); - new AllureExtension(null, $configurator); - } - - /** - * @return iterable - */ - public function providerValidConfigurator(): iterable - { - return [ - 'Null' => [null], - 'Default configurator' => [TestConfigurator::class], - ]; - } - - /** - * @dataProvider providerConfiguratorArgs - */ - public function testConstructor_GivenValidConfiguratorStringWithArgs_PassesSameArgs(array $args): void - { - new AllureExtension(null, TestConfigurator::class, ...$args); - self::assertSame($args, TestConfigurator::getArgs()); - } - - /** - * @return iterable - */ - public function providerConfiguratorArgs(): iterable - { - return [ - 'No args' => [[]], - 'Null args' => [[null, null]], - 'Integer args' => [[1, 2]], - 'Float args' => [[1.2, 3.4]], - 'String args' => [['a', 'b']], - 'Boolean args' => [[true, false]], - 'Array args' => [[['a' => 'b'], [1, 2]]], - 'Object args' => [[(object) ['a' => 'b'], (object) ['c' => 'd']]], - 'Resource args' => [[STDOUT, STDERR]], - ]; + $builder = $this->createMock(LifecycleBuilderInterface::class); + Allure::setLifecycleBuilder($builder); + $builder + ->expects(self::once()) + ->method('createResultsWriter') + ->with(self::identicalTo($expectedValue)); + new AllureExtension(['outputDirectory' => $outputDirectory]); } public function testExecuteBeforeTest_Constructed_CreatesTestAfterResettingSwitchedContext(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) ->id('switch') @@ -154,10 +99,7 @@ public function testExecuteBeforeTest_Constructed_CreatesTestAfterResettingSwitc public function testExecuteBeforeTest_Constructed_UpdatesInfoAndStartsCreatedTest(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->method('switchTo') ->willReturnSelf(); @@ -186,10 +128,7 @@ public function testExecuteBeforeTest_Constructed_UpdatesInfoAndStartsCreatedTes public function testExecuteAfterTest_Constructed_StopsTestAfterSwitchingContext(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) @@ -207,10 +146,7 @@ public function testExecuteAfterTest_Constructed_StopsTestAfterSwitchingContext( public function testExecuteAfterTest_Constructed_UpdatesRunForStoppedTestAndWritesIt(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->method('switchTo') @@ -236,10 +172,7 @@ public function testExecuteAfterTest_Constructed_UpdatesRunForStoppedTestAndWrit public function testExecuteAfterTestFailure_Constructed_SetsDetectedOrFailedStatusForSwitchedTest(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) @@ -263,10 +196,7 @@ public function testExecuteAfterTestFailure_Constructed_SetsDetectedOrFailedStat public function testExecuteAfterTestError_Constructed_SetsDetectedOrFailedStatusForSwitchedTest(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) @@ -289,10 +219,7 @@ public function testExecuteAfterTestError_Constructed_SetsDetectedOrFailedStatus public function testExecuteAfterIncompleteTest_Constructed_SetsBrokenStatusForSwitchedTest(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) @@ -311,10 +238,7 @@ public function testExecuteAfterIncompleteTest_Constructed_SetsBrokenStatusForSw public function testExecuteAfterSkippedTest_Constructed_SetsSkippedStatusForSwitchedTest(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) @@ -333,10 +257,7 @@ public function testExecuteAfterSkippedTest_Constructed_SetsSkippedStatusForSwit public function testExecuteAfterTestWarning_Constructed_SetsBrokenStatusForSwitchedTest(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) @@ -355,10 +276,7 @@ public function testExecuteAfterTestWarning_Constructed_SetsBrokenStatusForSwitc public function testExecuteAfterRiskyTest_Constructed_SetsFailedStatusForSwitchedTest(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) @@ -377,10 +295,7 @@ public function testExecuteAfterRiskyTest_Constructed_SetsFailedStatusForSwitche public function testExecuteAfterSuccessfulTest_Constructed_SetsPassedStatusForSwitchedTest(): void { $testLifecycle = $this->createMock(TestLifecycleInterface::class); - $extension = new AllureExtension( - 'a', - $this->createConfiguratorWithTestLifecycle($testLifecycle), - ); + $extension = new AllureExtension($testLifecycle); $testLifecycle ->expects(self::once()) @@ -395,14 +310,4 @@ public function testExecuteAfterSuccessfulTest_Constructed_SetsPassedStatusForSw ->with(self::identicalTo(null), self::identicalTo(Status::passed())); $extension->executeAfterSuccessfulTest('b', 1.2); } - - private function createConfiguratorWithTestLifecycle(TestLifecycleInterface $testLifecycle): ConfiguratorInterface - { - $configurator = $this->createStub(TestConfiguratorInterface::class); - $configurator - ->method('createTestLifecycle') - ->willReturn($testLifecycle); - - return $configurator; - } } diff --git a/test/unit/Internal/ConfigTest.php b/test/unit/Internal/ConfigTest.php new file mode 100644 index 0000000..cce1e70 --- /dev/null +++ b/test/unit/Internal/ConfigTest.php @@ -0,0 +1,339 @@ +getOutputDirectory()); + } + + /** + * @return iterable + */ + public function providerNoOutputDirectory(): iterable + { + return [ + 'No entry' => [[]], + 'Null entry' => [['outputDirectory' => null]], + ]; + } + + public function testGetOutputDirectory_StringInData_ReturnsSameString(): void + { + $config = new Config(['outputDirectory' => 'a']); + self::assertSame('a', $config->getOutputDirectory()); + } + + public function testGetOutputDirectory_InvalidData_ThrowsException(): void + { + $config = new Config(['outputDirectory' => 1]); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Config key "outputDirectory" should contain a string'); + $config->getOutputDirectory(); + } + + /** + * @dataProvider providerNoLinkTemplates + */ + public function testGetLinkTemplates_EmptyData_ReturnsEmptyList(array $data): void + { + $config = new Config($data); + self::assertEmpty($config->getLinkTemplates()); + } + + /** + * @return iterable + */ + public function providerNoLinkTemplates(): iterable + { + return [ + 'No entry' => [[]], + 'Null entry' => [['linkTemplates' => null]], + 'Empty entry' => [['linkTemplates' => []]], + ]; + } + + public function testGetLinkTemplates_InvalidData_ThrowsException(): void + { + $config = new Config(['linkTemplates' => 1]); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Config key "linkTemplates" should contain an array'); + $config->getLinkTemplates(); + } + + public function testGetLinkTemplates_InvalidKeyInData_ThrowsException(): void + { + $data = [ + 'linkTemplates' => [1 => $this->createStub(LinkTemplateInterface::class)], + ]; + $config = new Config($data); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Config key "linkTemplates" should contain an array with string keys only'); + $config->getLinkTemplates(); + } + + public function testGetLinkTemplates_ValidObjectsInData_ReturnsSameObjects(): void + { + $firstLink = $this->createStub(LinkTemplateInterface::class); + $secondLink = $this->createStub(LinkTemplateInterface::class); + $data = [ + 'linkTemplates' => ['tms' => $firstLink, 'issue' => $secondLink], + ]; + $config = new Config($data); + $expectedData = ['tms' => $firstLink, 'issue' => $secondLink]; + self::assertSame($expectedData, $config->getLinkTemplates()); + } + + public function testGetLinkTemplates_InvalidTypeInData_ThrowsException(): void + { + $data = [ + 'linkTemplates' => ['tms' => 1], + ]; + $config = new Config($data); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Config key "linkTemplates/tms" contains invalid source of Qameta\Allure\Setup\LinkTemplateInterface', + ); + $config->getLinkTemplates(); + } + + public function testGetLinkTemplates_CallableInDataProvidesObject_ReturnsSameObject(): void + { + $template = $this->createStub(LinkTemplateInterface::class); + $data = [ + 'linkTemplates' => ['tms' => fn (): LinkTemplateInterface => $template], + ]; + $config = new Config($data); + $expectedData = ['tms' => $template]; + self::assertSame($expectedData, $config->getLinkTemplates()); + } + + public function testGetLinkTemplates_InvalidClassNameInData_ThrowsException(): void + { + $data = [ + 'linkTemplates' => ['tms' => stdClass::class], + ]; + $config = new Config($data); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Config key "linkTemplates/tms" contains ' . + 'invalid source of Qameta\Allure\Setup\LinkTemplateInterface', + ); + $config->getLinkTemplates(); + } + + public function testGetLinkTemplates_ValidClassNameInData_ReturnsObjectOfSameClass(): void + { + $data = [ + 'linkTemplates' => ['tms' => TestLinkTemplate::class], + ]; + $config = new Config($data); + $expectedData = ['tms' => new TestLinkTemplate()]; + self::assertEquals($expectedData, $config->getLinkTemplates()); + } + + /** + * @dataProvider providerNoSetupHook + */ + public function testGetSetupHook_EmptyData_ReturnsNull(array $data): void + { + $config = new Config($data); + self::assertNull($config->getSetupHook()); + } + + /** + * @return iterable + */ + public function providerNoSetupHook(): iterable + { + return [ + 'No entry' => [[]], + 'Null entry' => [['onSetup' => null]], + ]; + } + + /** + * @dataProvider providerInvalidSetupHook + */ + public function testGetSetupHook_InvalidTypeInData_ThrowsException(array $data): void + { + $config = new Config($data); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage('Config key "setupHook" should contain a callable'); + $config->getSetupHook(); + } + + /** + * @return iterable + */ + public function providerInvalidSetupHook(): iterable + { + return [ + 'Invalid type' => [['setupHook' => 1]], + 'Non-callable class' => [['setupHook' => stdClass::class]], + ]; + } + + public function testGetSetupHook_ValidCallableInData_ReturnsSameInstance(): void + { + $hook = fn (): mixed => null; + $config = new Config(['setupHook' => $hook]); + self::assertSame($hook, $config->getSetupHook()); + } + + public function testGetSetupHook_CallableClassInData_ReturnsInstanceOfSameClass(): void + { + $config = new Config(['setupHook' => OnSetupHook::class]); + self::assertInstanceOf(OnSetupHook::class, $config->getSetupHook()); + } + + /** + * @dataProvider providerNoThreadDetector + */ + public function testGetThreadDetector_EmptyData_ReturnsNull(array $data): void + { + $config = new Config($data); + self::assertNull($config->getThreadDetector()); + } + + /** + * @return iterable + */ + public function providerNoThreadDetector(): iterable + { + return [ + 'No entry' => [[]], + 'Null entry' => [['threadDetector' => null]], + ]; + } + + public function testGetThreadDetector_InvalidTypeInData_ThrowsException(): void + { + $config = new Config(['threadDetector' => 1]); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Config key "threadDetector" contains ' . + 'invalid source of Qameta\Allure\PHPUnit\Setup\ThreadDetectorInterface', + ); + $config->getThreadDetector(); + } + + public function testGetThreadDetector_ValidObjectInData_ReturnsSameInstance(): void + { + $detector = $this->createStub(ThreadDetectorInterface::class); + $config = new Config(['threadDetector' => $detector]); + self::assertSame($detector, $config->getThreadDetector()); + } + + public function testGetThreadDetector_CallableInDataProvidesValidObject_ReturnsSameInstance(): void + { + $detector = $this->createStub(ThreadDetectorInterface::class); + $config = new Config(['threadDetector' => fn (): ThreadDetectorInterface => $detector]); + self::assertSame($detector, $config->getThreadDetector()); + } + + public function testGetThreadDetector_InvalidClassInData_ThrowsException(): void + { + $config = new Config(['threadDetector' => stdClass::class]); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Config key "threadDetector" contains ' . + 'invalid source of Qameta\Allure\PHPUnit\Setup\ThreadDetectorInterface', + ); + $config->getThreadDetector(); + } + + public function testGetThreadDetector_ValidClassInData_ReturnsInstanceOfSameClass(): void + { + $config = new Config(['threadDetector' => TestThreadDetector::class]); + self::assertInstanceOf(TestThreadDetector::class, $config->getThreadDetector()); + } + + /** + * @dataProvider providerNoLifecycleHooks + */ + public function testGetLifecycleHooks_EmptyData_ReturnsEmptyList(array $data): void + { + $config = new Config($data); + self::assertEmpty($config->getLifecycleHooks()); + } + + /** + * @return iterable + */ + public function providerNoLifecycleHooks(): iterable + { + return [ + 'No entry' => [[]], + 'Null entry' => [['lifecycleHooks' => null]], + 'Empty entry' => [['lifecycleHooks' => []]], + ]; + } + + public function testGetLifecycleHooks_InvalidIndexInData_ThrowsException(): void + { + $data = [ + 'lifecycleHooks' => [ + 'a' => $this->createStub(LifecycleHookInterface::class), + ], + ]; + $config = new Config($data); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Config key "lifecycleHooks" should contain an array with integer keys only', + ); + $config->getLifecycleHooks(); + } + + public function testGetLifecycleHooks_ValidObjectsInData_ReturnsSameObjects(): void + { + $firstHook = $this->createStub(LifecycleHookInterface::class); + $secondHook = $this->createStub(LifecycleHookInterface::class); + $config = new Config(['lifecycleHooks' => [$firstHook, $secondHook]]); + self::assertSame([$firstHook, $secondHook], $config->getLifecycleHooks()); + } + + public function testGetLifecycleHooks_ValidClassInData_ReturnsInstanceOfSameClass(): void + { + $config = new Config(['lifecycleHooks' => [TestLifecycleHook::class]]); + self::assertEquals([new TestLifecycleHook()], $config->getLifecycleHooks()); + } + + public function testGetLifecycleHooks_ValidCallableInData_ReturnsMatchingObjects(): void + { + $config = new Config(['lifecycleHooks' => [fn () => TestLifecycleHook::class]]); + self::assertEquals([new TestLifecycleHook()], $config->getLifecycleHooks()); + } + + public function testGetLifecycleHooks_InvalidTypeInData_ThrowsException(): void + { + $config = new Config(['lifecycleHooks' => [1]]); + $this->expectException(RuntimeException::class); + $this->expectExceptionMessage( + 'Config key "lifecycleHooks/0" contains ' . + 'invalid source of Qameta\Allure\Hook\LifecycleHookInterface', + ); + $config->getLifecycleHooks(); + } +} diff --git a/test/unit/Internal/TestLifecycleHook.php b/test/unit/Internal/TestLifecycleHook.php new file mode 100644 index 0000000..f232f5d --- /dev/null +++ b/test/unit/Internal/TestLifecycleHook.php @@ -0,0 +1,11 @@ +createMock(LifecycleBuilderInterface::class); - Allure::setLifecycleBuilder($builder); - - $configurator = new DefaultConfigurator(); - $configurator->setupAllure('a'); - $builder - ->expects(self::once()) - ->method('createResultsWriter') - ->with(self::identicalTo('a')); - Allure::getLifecycle(); - } - - public function testSetupAllure_ConstructedWithDynamicSetupHook_CallsSameHook(): void - { - $builder = $this->createStub(LifecycleBuilderInterface::class); - Allure::setLifecycleBuilder($builder); - - $configurator = new DefaultConfigurator(OnSetupHook::class); - $configurator->setupAllure('a'); - self::assertSame(1, OnSetupHook::getInvocationCount()); - } - - public function testSetupAllure_ConstructedWithStaticSetupHook_CallsSameHook(): void - { - $builder = $this->createStub(LifecycleBuilderInterface::class); - Allure::setLifecycleBuilder($builder); - - $configurator = new DefaultConfigurator(OnSetupHook::class . '::method'); - $configurator->setupAllure('a'); - self::assertSame(1, OnSetupHook::getInvocationCount()); - } - - public function testGetAllureLifecycle_Always_ReturnsNull(): void - { - $builder = $this->createStub(LifecycleBuilderInterface::class); - Allure::setLifecycleBuilder($builder); - - $configurator = new DefaultConfigurator(); - self::assertNull($configurator->getAllureLifecycle()); - } - - public function testGetResultFactory_Always_ReturnsNull(): void - { - $builder = $this->createStub(LifecycleBuilderInterface::class); - Allure::setLifecycleBuilder($builder); - - $configurator = new DefaultConfigurator(); - self::assertNull($configurator->getResultFactory()); - } - - public function testGetStatusDetectorFactory_Always_ReturnsNull(): void - { - $builder = $this->createStub(LifecycleBuilderInterface::class); - Allure::setLifecycleBuilder($builder); - - $configurator = new DefaultConfigurator(); - self::assertNull($configurator->getStatusDetector()); - } - - public function testGetAllureAdapter_Always_ReturnsNull(): void - { - $builder = $this->createStub(LifecycleBuilderInterface::class); - Allure::setLifecycleBuilder($builder); - - $configurator = new DefaultConfigurator(); - self::assertNull($configurator->getAllureAdapter()); - } - - public function testGetThreadDetector_Always_ReturnsNull(): void - { - $builder = $this->createStub(LifecycleBuilderInterface::class); - Allure::setLifecycleBuilder($builder); - - $configurator = new DefaultConfigurator(); - self::assertNull($configurator->getThreadDetector()); - } -} diff --git a/test/unit/TestConfigurator.php b/test/unit/TestConfigurator.php deleted file mode 100644 index 8f8ce8c..0000000 --- a/test/unit/TestConfigurator.php +++ /dev/null @@ -1,68 +0,0 @@ -