Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"php": "^8",
"ext-json": "*",
"codeception/codeception": "^5.0.3",
"allure-framework/allure-php-commons": "^2.3.1"
"allure-framework/allure-php-commons": "^2.4.0"
},
"require-dev": {
"psalm/plugin-phpunit": "^0.19.0 || ^0.20.1",
Expand Down
13 changes: 7 additions & 6 deletions src/AllureCodeception.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,13 @@ public function stepAfter(StepEvent $stepEvent): void
private function getTestLifecycle(): TestLifecycleInterface
{
return $this->testLifecycle ??= new TestLifecycle(
Allure::getLifecycle(),
Allure::getConfig()->getResultFactory(),
Allure::getConfig()->getStatusDetector(),
$this->getThreadDetector(),
Allure::getConfig()->getLinkTemplates(),
$_ENV,
rootDir: $this->getRootDir(),
lifecycle: Allure::getLifecycle(),
resultFactory: Allure::getConfig()->getResultFactory(),
statusDetector: Allure::getConfig()->getStatusDetector(),
threadDetector: $this->getThreadDetector(),
linkTemplates: Allure::getConfig()->getLinkTemplates(),
env: $_ENV,
);
}
}
14 changes: 14 additions & 0 deletions src/Internal/CeptInfoBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,21 +6,35 @@

use Codeception\Test\Cept;

use function array_pop;
use function realpath;

final class CeptInfoBuilder implements TestInfoBuilderInterface
{
public function __construct(
private string $rootDir,
private Cept $test,
) {
}

#[\Override]
public function build(?string $host, ?string $thread): TestInfo
{
// May contain .. if the config is not in a parent directory.
$filePath = realpath($this->test->getFileName());
$titlePath = $filePath !== false
? ModelFunctions::getTitlePathByFile($this->rootDir, $filePath)
: [];

// A cept file is a single test, so we're removing the file node from titlePath.
array_pop($titlePath);

return new TestInfo(
originalTest: $this->test,
signature: $this->test->getSignature(),
class: $this->test->getName(),
method: $this->test->getName(),
titlePath: $titlePath,
host: $host,
thread: $thread,
);
Expand Down
6 changes: 5 additions & 1 deletion src/Internal/CestInfoBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ public function __construct(
#[\Override]
public function build(?string $host, ?string $thread): TestInfo
{
$class = $this->test->getTestInstance()::class;
$titlePath = ModelFunctions::getTitlePathByClass($class);

return new TestInfo(
originalTest: $this->test,
signature: $this->test->getSignature(),
class: $this->test->getTestInstance()::class,
class: $class,
method: $this->test->getTestMethod(),
titlePath: $titlePath,
dataLabel: $this->getDataLabel(),
host: $host,
thread: $thread,
Expand Down
26 changes: 25 additions & 1 deletion src/Internal/GherkinInfoBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,47 @@

use Codeception\Test\Gherkin;

use function is_string;
use function array_pop;
use function realpath;

final class GherkinInfoBuilder implements TestInfoBuilderInterface
{
public function __construct(
private string $rootDir,
private Gherkin $test,
) {
}

#[\Override]
public function build(?string $host, ?string $thread): TestInfo
{
// May contain .. if the config is not in a parent directory.
$filePath = realpath($this->test->getFileName());

/**
* @var list<string>
*/
$titlePath = $filePath !== false
? ModelFunctions::getTitlePathByFile($this->rootDir, $filePath)
: [];

$featureName = $this->test->getFeature();
if ($featureName) {
// Prefer a more human-readable feature name instead of the filename.
if ($titlePath) {
array_pop($titlePath);
$titlePath[] = $featureName;
} else {
$titlePath = [$featureName];
}
}

return new TestInfo(
originalTest: $this->test,
signature: $this->test->getSignature(),
class: $this->test->getFeature(),
method: $this->test->getScenarioTitle(),
titlePath: $titlePath,
host: $host,
thread: $thread,
);
Expand Down
85 changes: 85 additions & 0 deletions src/Internal/ModelFunctions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?php

declare(strict_types=1);

namespace Qameta\Allure\Codeception\Internal;

use function array_filter;
use function array_shift;
use function explode;
use function getcwd;
use function is_string;
use function rtrim;

use const DIRECTORY_SEPARATOR;

/**
* @internal
*/
final class ModelFunctions
{
/**
* @return list<string>
*/
public static function getTitlePathByFile(string $base, string $path, bool $final = false): array
{
if (!$path) {
return [];
}

$baseParts = explode(DIRECTORY_SEPARATOR, rtrim($base, DIRECTORY_SEPARATOR));
$pathParts = explode(DIRECTORY_SEPARATOR, $path);

if (!$base || $baseParts[0] !== $pathParts[0]) {
// The base is not provided or is on another disk (on Windows)
// or is not an absolute path.
// Fallback to CWD if not yet in fallback mode
if (!$final) {
$cwd = getcwd();
if ($cwd !== false) {
return self::getTitlePathByFile($cwd, $path, true);
}
}

// CWD didn't work too. Turn the absolute path into titlePath.
// Add leading '/' node on Linux/MAC to avoid confusion with
// well-formed titlePath values of other tests.
return $pathParts[0]
? $pathParts
: [DIRECTORY_SEPARATOR, ...$pathParts];
}

do {
// Skipping identical parts of both paths.
array_shift($baseParts);
array_shift($pathParts);
} while ($baseParts && $pathParts && $baseParts[0] === $pathParts[0]);

if (!$pathParts) {
// If the path contains less parts than the base (is a parent of the base)
// or is equal to the base, return empty titlePath.
return [];
}

// At this point we have three cases:
// - $pathParts is empty: the path contains less parts than the base (is a
// parent of the base) or is equal to the base, return empty titlePath.
// - $baseParts is empty: the path is inside the base.
// The titlePath consists entirely of the remaining path parts.
// - $baseParts is not empty: the path is not in the base but they share a
// common ancestor. We consider this ancestor the proper root directory
// and return the remaining parts of the path as titlePath.
// Essentially, all three cases are treated identically.
return $pathParts;
}

/**
* @return list<string>
*/
public static function getTitlePathByClass(?string $class): array
{
return is_string($class)
? [...array_filter(explode("\\", $class))]
: [];
}
}
12 changes: 12 additions & 0 deletions src/Internal/TestInfo.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@

final class TestInfo
{
/**
* @param list<string> $titlePath
*/
public function __construct(
private object $originalTest,
private string $signature,
private ?string $class = null,
private ?string $method = null,
private array $titlePath = [],
private ?string $dataLabel = null,
private ?string $host = null,
private ?string $thread = null,
Expand All @@ -37,6 +41,14 @@ public function getMethod(): ?string
return $this->method;
}

/**
* @return list<string>
*/
public function getTitlePath(): array
{
return $this->titlePath;
}

public function getDataLabel(): ?string
{
return $this->dataLabel;
Expand Down
11 changes: 7 additions & 4 deletions src/Internal/TestLifecycle.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ final class TestLifecycle implements TestLifecycleInterface
private WeakMap $stepStarts;

public function __construct(
private string $rootDir,
private AllureLifecycleInterface $lifecycle,
private ResultFactoryInterface $resultFactory,
private StatusDetectorInterface $statusDetector,
Expand Down Expand Up @@ -115,8 +116,8 @@ private function getTestInfoBuilder(object $test): TestInfoBuilderInterface
{
return match (true) {
$test instanceof Cest => new CestInfoBuilder($test),
$test instanceof Gherkin => new GherkinInfoBuilder($test),
$test instanceof Cept => new CeptInfoBuilder($test),
$test instanceof Gherkin => new GherkinInfoBuilder($this->rootDir, $test),
$test instanceof Cept => new CeptInfoBuilder($this->rootDir, $test),
$test instanceof TestCaseWrapper => new UnitInfoBuilder($test),
default => new UnknownInfoBuilder($test),
};
Expand All @@ -142,16 +143,18 @@ public function create(): self
#[\Override]
public function updateTest(): self
{
$test = $this->getCurrentTest();
$provider = new ModelProviderChain(
new EnvProvider($this->env),
...SuiteProvider::createForChain($this->getCurrentSuite(), $this->linkTemplates),
...TestInfoProvider::createForChain($this->getCurrentTest()),
...$this->createModelProvidersForTest($this->getCurrentTest()->getOriginalTest()),
...TestInfoProvider::createForChain($test),
...$this->createModelProvidersForTest($test->getOriginalTest()),
);
$this->lifecycle->updateTest(
fn (TestResult $t) => $t
->setName($provider->getDisplayName())
->setFullName($provider->getFullName())
->setTitlePath(...$test->getTitlePath())
->setDescription($provider->getDescription())
->setDescriptionHtml($provider->getDescriptionHtml())
->addLinks(...$provider->getLinks())
Expand Down
1 change: 1 addition & 0 deletions src/Internal/UnitInfoBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public function build(?string $host, ?string $thread): TestInfo
signature: $this->test->getSignature(),
class: $class ?? null,
method: $this->test->getMetadata()->getName(),
titlePath: ModelFunctions::getTitlePathByClass($class),
dataLabel: $dataLabel,
host: $host,
thread: $thread,
Expand Down
Loading
Loading