Skip to content

Commit

Permalink
external config file (fixes #66, via #68)
Browse files Browse the repository at this point in the history
  • Loading branch information
remorhaz authored Nov 29, 2021
1 parent 88bccf5 commit 31995bd
Show file tree
Hide file tree
Showing 18 changed files with 712 additions and 495 deletions.
12 changes: 7 additions & 5 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
8 changes: 1 addition & 7 deletions phpunit.report.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,6 @@
</testsuite>
</testsuites>
<extensions>
<extension class="Qameta\Allure\PHPUnit\AllureExtension">
<arguments>
<null/><!-- default output directory -->
<null/><!-- default configurator -->
<string>Qameta\Allure\PHPUnit\Test\Report\Hook\OnSetupHook</string>
</arguments>
</extension>
<extension class="Qameta\Allure\PHPUnit\AllureExtension" />
</extensions>
</phpunit>
101 changes: 73 additions & 28 deletions src/AllureExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

namespace Qameta\Allure\PHPUnit;

use LogicException;
use PHPUnit\Runner\AfterIncompleteTestHook;
use PHPUnit\Runner\AfterRiskyTestHook;
use PHPUnit\Runner\AfterSkippedTestHook;
Expand All @@ -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;

Expand All @@ -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
Expand Down
172 changes: 172 additions & 0 deletions src/Internal/Config.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
<?php

declare(strict_types=1);

namespace Qameta\Allure\PHPUnit\Internal;

use Qameta\Allure\Hook\LifecycleHookInterface;
use Qameta\Allure\PHPUnit\Setup\ThreadDetectorInterface;
use Qameta\Allure\Setup\LinkTemplateInterface;
use RuntimeException;

use function class_exists;
use function is_a;
use function is_array;
use function is_callable;
use function is_int;
use function is_string;

final class Config implements ConfigInterface
{
public function __construct(
private array $data,
) {
}

public function getOutputDirectory(): ?string
{
$key = 'outputDirectory';
if (!isset($this->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<string, LinkTemplateInterface>
*/
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<T> $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<T> $expectedClass
* @return bool
* @psalm-assert-if-true class-string<T> $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<LifecycleHookInterface>
*/
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;
}
}
29 changes: 29 additions & 0 deletions src/Internal/ConfigInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php

declare(strict_types=1);

namespace Qameta\Allure\PHPUnit\Internal;

use Qameta\Allure\Hook\LifecycleHookInterface;
use Qameta\Allure\PHPUnit\Setup\ThreadDetectorInterface;
use Qameta\Allure\Setup\LinkTemplateInterface;

interface ConfigInterface
{

public function getOutputDirectory(): ?string;

/**
* @return array<string, LinkTemplateInterface>
*/
public function getLinkTemplates(): array;

public function getSetupHook(): ?callable;

public function getThreadDetector(): ?ThreadDetectorInterface;

/**
* @return list<LifecycleHookInterface>
*/
public function getLifecycleHooks(): array;
}
Loading

0 comments on commit 31995bd

Please sign in to comment.