Skip to content

Commit 31995bd

Browse files
authored
external config file (fixes allure-framework#66, via allure-framework#68)
1 parent 88bccf5 commit 31995bd

18 files changed

+712
-495
lines changed

composer.json

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,17 @@
3030
},
3131
"require": {
3232
"php": "^8",
33-
"allure-framework/allure-php-commons": "2.0.0-rc3",
33+
"allure-framework/allure-php-commons": "2.0.0-rc4",
3434
"phpunit/phpunit": "^9"
3535
},
3636
"require-dev": {
37-
"ext-dom": "*",
38-
"brianium/paratest": "^6.3.1",
37+
"brianium/paratest": "^6.3.3",
3938
"psalm/plugin-phpunit": "^0.16.1",
40-
"squizlabs/php_codesniffer": "^3.6.0",
41-
"vimeo/psalm": "^4.10"
39+
"squizlabs/php_codesniffer": "^3.6.1",
40+
"vimeo/psalm": "^4.13.1"
41+
},
42+
"conflict": {
43+
"amphp/byte-stream": "<1.5.1"
4244
},
4345
"autoload": {
4446
"psr-4": {

phpunit.report.xml

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,6 @@
1818
</testsuite>
1919
</testsuites>
2020
<extensions>
21-
<extension class="Qameta\Allure\PHPUnit\AllureExtension">
22-
<arguments>
23-
<null/><!-- default output directory -->
24-
<null/><!-- default configurator -->
25-
<string>Qameta\Allure\PHPUnit\Test\Report\Hook\OnSetupHook</string>
26-
</arguments>
27-
</extension>
21+
<extension class="Qameta\Allure\PHPUnit\AllureExtension" />
2822
</extensions>
2923
</phpunit>

src/AllureExtension.php

Lines changed: 73 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55
namespace Qameta\Allure\PHPUnit;
66

7-
use LogicException;
87
use PHPUnit\Runner\AfterIncompleteTestHook;
98
use PHPUnit\Runner\AfterRiskyTestHook;
109
use PHPUnit\Runner\AfterSkippedTestHook;
@@ -14,15 +13,19 @@
1413
use PHPUnit\Runner\AfterTestHook;
1514
use PHPUnit\Runner\AfterTestWarningHook;
1615
use PHPUnit\Runner\BeforeTestHook;
16+
use Qameta\Allure\Allure;
17+
use Qameta\Allure\Model\LinkType;
1718
use Qameta\Allure\Model\Status;
18-
use Qameta\Allure\PHPUnit\Internal\TestLifecycleFactory;
19-
use Qameta\Allure\PHPUnit\Internal\TestLifecycleFactoryInterface;
19+
use Qameta\Allure\PHPUnit\Internal\Config;
20+
use Qameta\Allure\PHPUnit\Internal\ConfigInterface;
21+
use Qameta\Allure\PHPUnit\Internal\DefaultThreadDetector;
22+
use Qameta\Allure\PHPUnit\Internal\TestLifecycle;
2023
use Qameta\Allure\PHPUnit\Internal\TestLifecycleInterface;
21-
use Qameta\Allure\PHPUnit\Setup\ConfiguratorInterface;
22-
use Qameta\Allure\PHPUnit\Setup\DefaultConfigurator;
24+
use Qameta\Allure\PHPUnit\Internal\TestUpdater;
25+
use RuntimeException;
2326

24-
use function class_exists;
25-
use function is_a;
27+
use function file_exists;
28+
use function is_array;
2629

2730
use const DIRECTORY_SEPARATOR;
2831

@@ -39,41 +42,83 @@ final class AllureExtension implements
3942
{
4043
private const DEFAULT_OUTPUT_DIRECTORY = 'build' . DIRECTORY_SEPARATOR . 'allure-results';
4144

45+
private const DEFAULT_CONFIG_FILE = 'config' . DIRECTORY_SEPARATOR . 'allure.config.php';
46+
4247
private TestLifecycleInterface $testLifecycle;
4348

4449
public function __construct(
45-
?string $outputDirectory = null,
46-
string|ConfiguratorInterface|null $configurator = null,
47-
mixed ...$args,
50+
string|array|ConfigInterface|TestLifecycleInterface|null $configOrTestLifecycle = null,
4851
) {
49-
if (!$configurator instanceof ConfiguratorInterface) {
50-
$configurator = $this->createConfigurator(
51-
$configurator ?? DefaultConfigurator::class,
52-
...$args,
52+
$this->testLifecycle = $configOrTestLifecycle instanceof TestLifecycleInterface
53+
? $configOrTestLifecycle
54+
: $this->createTestLifecycle($configOrTestLifecycle);
55+
}
56+
57+
private function createTestLifecycle(string|array|ConfigInterface|null $configSource): TestLifecycleInterface
58+
{
59+
$config = $configSource instanceof ConfigInterface
60+
? $configSource
61+
: $this->loadConfig($configSource);
62+
63+
$this->setupAllure($config);
64+
65+
return new TestLifecycle(
66+
Allure::getLifecycle(),
67+
Allure::getConfig()->getResultFactory(),
68+
Allure::getConfig()->getStatusDetector(),
69+
$config->getThreadDetector() ?? new DefaultThreadDetector(),
70+
AllureAdapter::getInstance(),
71+
new TestUpdater(Allure::getConfig()->getLinkTemplates()),
72+
);
73+
}
74+
75+
private function setupAllure(ConfigInterface $config): void
76+
{
77+
Allure::setOutputDirectory($config->getOutputDirectory() ?? self::DEFAULT_OUTPUT_DIRECTORY);
78+
79+
foreach ($config->getLinkTemplates() as $linkType => $linkTemplate) {
80+
Allure::getLifecycleConfigurator()->addLinkTemplate(
81+
LinkType::fromOptionalString($linkType),
82+
$linkTemplate,
5383
);
5484
}
55-
$configurator->setupAllure($outputDirectory ?? self::DEFAULT_OUTPUT_DIRECTORY);
56-
$this->testLifecycle = $this->createTestLifecycleInterface($configurator);
85+
86+
if (!empty($config->getLifecycleHooks())) {
87+
Allure::getLifecycleConfigurator()->addHooks(...$config->getLifecycleHooks());
88+
}
89+
90+
$setupHook = $config->getSetupHook();
91+
if (isset($setupHook)) {
92+
$setupHook();
93+
}
5794
}
5895

59-
private function createConfigurator(string $class, mixed ...$args): ConfiguratorInterface
96+
private function loadConfig(string|array|null $configSource): ConfigInterface
6097
{
61-
return
62-
class_exists($class) &&
63-
is_a($class, ConfiguratorInterface::class, true)
64-
? new $class(...$args)
65-
: throw new LogicException("Invalid configurator class: {$class}");
98+
return new Config(
99+
is_array($configSource)
100+
? $configSource
101+
: $this->loadConfigData($configSource),
102+
);
66103
}
67104

68-
private function createTestLifecycleInterface(ConfiguratorInterface $configurator): TestLifecycleInterface
105+
private function loadConfigData(?string $configFile): array
69106
{
70-
$testLifecycleFactory = $configurator instanceof TestLifecycleFactoryInterface
71-
? $configurator
72-
: new TestLifecycleFactory();
107+
$fileShouldExist = isset($configFile);
108+
$configFile ??= self::DEFAULT_CONFIG_FILE;
109+
if (file_exists($configFile)) {
110+
/** @psalm-var mixed $data */
111+
$data = require $configFile;
112+
113+
return is_array($data)
114+
? $data
115+
: throw new RuntimeException("Config file {$configFile} must return array");
116+
} elseif ($fileShouldExist) {
117+
throw new RuntimeException("Config file {$configFile} doesn't exist");
118+
}
73119

74-
return $testLifecycleFactory->createTestLifecycle($configurator);
120+
return [];
75121
}
76-
77122
public function executeBeforeTest(string $test): void
78123
{
79124
$this

src/Internal/Config.php

Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Qameta\Allure\PHPUnit\Internal;
6+
7+
use Qameta\Allure\Hook\LifecycleHookInterface;
8+
use Qameta\Allure\PHPUnit\Setup\ThreadDetectorInterface;
9+
use Qameta\Allure\Setup\LinkTemplateInterface;
10+
use RuntimeException;
11+
12+
use function class_exists;
13+
use function is_a;
14+
use function is_array;
15+
use function is_callable;
16+
use function is_int;
17+
use function is_string;
18+
19+
final class Config implements ConfigInterface
20+
{
21+
public function __construct(
22+
private array $data,
23+
) {
24+
}
25+
26+
public function getOutputDirectory(): ?string
27+
{
28+
$key = 'outputDirectory';
29+
if (!isset($this->data[$key])) {
30+
return null;
31+
}
32+
33+
/** @psalm-var mixed $outputDirectory */
34+
$outputDirectory = $this->data[$key];
35+
36+
return is_string($outputDirectory)
37+
? $outputDirectory
38+
: throw new RuntimeException("Config key \"{$key}\" should contain a string");
39+
}
40+
41+
/**
42+
* @return array<string, LinkTemplateInterface>
43+
*/
44+
public function getLinkTemplates(): array
45+
{
46+
$key = 'linkTemplates';
47+
$linkTemplates = [];
48+
/** @psalm-var mixed $linkTemplateSource */
49+
foreach ($this->getArrayFromData($key) as $linkKey => $linkTemplateSource) {
50+
if (!is_string($linkKey)) {
51+
throw new RuntimeException(
52+
"Config key \"{$key}\" should contain an array with string keys only",
53+
);
54+
}
55+
$linkTemplates[$linkKey] = $this->buildObject(
56+
"{$key}/{$linkKey}",
57+
$linkTemplateSource,
58+
LinkTemplateInterface::class,
59+
);
60+
}
61+
62+
return $linkTemplates;
63+
}
64+
65+
private function getArrayFromData(string $key): array
66+
{
67+
/** @psalm-var mixed $source */
68+
$source = $this->data[$key] ?? [];
69+
70+
return is_array($source)
71+
? $source
72+
: throw new RuntimeException("Config key \"{$key}\" should contain an array");
73+
}
74+
75+
/**
76+
* @template T
77+
* @param string $key
78+
* @param mixed $source
79+
* @param class-string<T> $expectedClass
80+
* @return T
81+
* @psalm-suppress MixedMethodCall
82+
*/
83+
private function buildObject(string $key, mixed $source, string $expectedClass): object
84+
{
85+
return match (true) {
86+
$source instanceof $expectedClass => $source,
87+
$this->isExpectedClassName($source, $expectedClass) => new $source(),
88+
is_callable($source) => $this->buildObject($key, $source(), $expectedClass),
89+
default => throw new RuntimeException(
90+
"Config key \"{$key}\" contains invalid source of {$expectedClass}",
91+
),
92+
};
93+
}
94+
95+
/**
96+
* @template T
97+
* @param mixed $source
98+
* @param class-string<T> $expectedClass
99+
* @return bool
100+
* @psalm-assert-if-true class-string<T> $source
101+
*/
102+
private function isExpectedClassName(mixed $source, string $expectedClass): bool
103+
{
104+
return $this->isClassName($source) && is_a($source, $expectedClass, true);
105+
}
106+
107+
/**
108+
* @psalm-assert-if-true class-string $source
109+
*/
110+
private function isClassName(mixed $source): bool
111+
{
112+
return is_string($source) && class_exists($source);
113+
}
114+
115+
public function getSetupHook(): ?callable
116+
{
117+
$key = 'setupHook';
118+
/** @psalm-var mixed $source */
119+
$source = $this->data[$key] ?? null;
120+
121+
return isset($source)
122+
? $this->buildCallable($key, $source)
123+
: null;
124+
}
125+
126+
/**
127+
* @psalm-suppress MixedMethodCall
128+
*/
129+
private function buildCallable(string $key, mixed $source): callable
130+
{
131+
return match (true) {
132+
is_callable($source) => $source,
133+
$this->isClassName($source) => $this->buildCallable($key, new $source()),
134+
default => throw new RuntimeException("Config key \"{$key}\" should contain a callable"),
135+
};
136+
}
137+
138+
public function getThreadDetector(): ?ThreadDetectorInterface
139+
{
140+
$key = 'threadDetector';
141+
/** @var mixed $threadDetector */
142+
$threadDetector = $this->data[$key] ?? null;
143+
144+
return isset($threadDetector)
145+
? $this->buildObject($key, $threadDetector, ThreadDetectorInterface::class)
146+
: null;
147+
}
148+
149+
/**
150+
* @return list<LifecycleHookInterface>
151+
*/
152+
public function getLifecycleHooks(): array
153+
{
154+
$key = 'lifecycleHooks';
155+
$hooks = [];
156+
/** @psalm-var mixed $hookSource */
157+
foreach ($this->getArrayFromData($key) as $index => $hookSource) {
158+
if (!is_int($index)) {
159+
throw new RuntimeException(
160+
"Config key \"{$key}\" should contain an array with integer keys only",
161+
);
162+
}
163+
$hooks[] = $this->buildObject(
164+
"{$key}/{$index}",
165+
$hookSource,
166+
LifecycleHookInterface::class,
167+
);
168+
}
169+
170+
return $hooks;
171+
}
172+
}

src/Internal/ConfigInterface.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Qameta\Allure\PHPUnit\Internal;
6+
7+
use Qameta\Allure\Hook\LifecycleHookInterface;
8+
use Qameta\Allure\PHPUnit\Setup\ThreadDetectorInterface;
9+
use Qameta\Allure\Setup\LinkTemplateInterface;
10+
11+
interface ConfigInterface
12+
{
13+
14+
public function getOutputDirectory(): ?string;
15+
16+
/**
17+
* @return array<string, LinkTemplateInterface>
18+
*/
19+
public function getLinkTemplates(): array;
20+
21+
public function getSetupHook(): ?callable;
22+
23+
public function getThreadDetector(): ?ThreadDetectorInterface;
24+
25+
/**
26+
* @return list<LifecycleHookInterface>
27+
*/
28+
public function getLifecycleHooks(): array;
29+
}

0 commit comments

Comments
 (0)