Skip to content

Keep information which data provider provided current data set #5992

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
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
5 changes: 4 additions & 1 deletion src/Framework/Exception/Exception.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,14 @@
*/
class Exception extends RuntimeException implements \PHPUnit\Exception
{
public ?string $method;

/**
* @var list<array{file: string, line: int, function: string}>
*/
protected array $serializableTrace;

public function __construct(string $message = '', int|string $code = 0, ?Throwable $previous = null)
public function __construct(string $message = '', int|string $code = 0, ?Throwable $previous = null, ?string $method = null)
{
/**
* @see https://github.com/sebastianbergmann/phpunit/issues/5965
Expand All @@ -68,6 +70,7 @@ public function __construct(string $message = '', int|string $code = 0, ?Throwab
foreach (array_keys($this->serializableTrace) as $key) {
unset($this->serializableTrace[$key]['args']);
}
$this->method = $method;
}

public function __sleep(): array
Expand Down
28 changes: 16 additions & 12 deletions src/Framework/TestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use function array_merge;
use function assert;
use function reset;
use PHPUnit\Metadata\Api\DataProvider;
use PHPUnit\Metadata\Api\Groups;
use PHPUnit\Metadata\Api\Requirements;
Expand Down Expand Up @@ -47,7 +48,7 @@ public function build(ReflectionClass $theClass, string $methodName, array $grou
$data = (new DataProvider)->providedData($className, $methodName);
}

if ($data !== null) {
if ($data !== null && reset($data) !== null) {
return $this->buildDataProviderTestSuite(
$methodName,
$className,
Expand Down Expand Up @@ -91,20 +92,23 @@ private function buildDataProviderTestSuite(string $methodName, string $classNam
(new Groups)->groups($className, $methodName),
);

foreach ($data as $_dataName => $_data) {
$_test = new $className($methodName);
foreach ($data as $providerName => $providedData) {
foreach ($providedData as $_dataName => $_data) {
$_test = new $className($methodName);

$_test->setData($_dataName, $_data);
$_test->setData($_dataName, $_data);

$this->configureTestCase(
$_test,
$runTestInSeparateProcess,
$preserveGlobalState,
$runClassInSeparateProcess,
$backupSettings,
);
$this->configureTestCase(
$_test,
$runTestInSeparateProcess,
$preserveGlobalState,
$runClassInSeparateProcess,
$backupSettings,
);

$dataProviderTestSuite->addTest($_test, $groups);
$_test->setProviderName($providerName);
$dataProviderTestSuite->addTest($_test, $groups);
}
}

return $dataProviderTestSuite;
Expand Down
19 changes: 19 additions & 0 deletions src/Framework/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@
*/
abstract class TestCase extends Assert implements Reorderable, SelfDescribing, Test
{
/**
* @var non-empty-string
*/
protected string $providerName;
private ?bool $backupGlobals = null;

/**
Expand Down Expand Up @@ -375,6 +379,21 @@ final public function setGroups(array $groups): void
$this->groups = $groups;
}

public function getProviderName(): string
{
return $this->providerName;
}

/**
* @return $this
*/
public function setProviderName(string $providerName): self
{
$this->providerName = $providerName;

return $this;
}

/**
* @internal This method is not covered by the backward compatibility promise for PHPUnit
*/
Expand Down
3 changes: 2 additions & 1 deletion src/Framework/TestSuite.php
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,8 @@ protected function addTestMethod(ReflectionClass $class, ReflectionMethod $metho
Event\TestData\TestDataCollection::fromArray([]),
),
sprintf(
"The data provider specified for %s::%s is invalid\n%s",
"The data provider %s specified for %s::%s is invalid\n%s",
$e->method,
$className,
$methodName,
$this->exceptionToString($e),
Expand Down
162 changes: 87 additions & 75 deletions src/Metadata/Api/DataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@
use function get_debug_type;
use function is_array;
use function is_int;
use function is_iterable;
use function is_string;
use function key;
use function reset;
use function sprintf;
use PHPUnit\Event;
use PHPUnit\Event\Code\ClassMethod;
use PHPUnit\Event\Code\TestMethod;
use PHPUnit\Framework\InvalidDataProviderException;
use PHPUnit\Metadata\DataProvider as DataProviderMetadata;
Expand Down Expand Up @@ -54,75 +58,82 @@ public function providedData(string $className, string $methodName): ?array
if ($dataProvider->isNotEmpty()) {
$data = $this->dataProvidedByMethods($className, $methodName, $dataProvider);
} else {
$data = $this->dataProvidedByMetadata($testWith);
$data = ['testWith' => $this->dataProvidedByMetadata($testWith)];
}

if ($data === []) {
if ($data === [] || reset($data) === []) {
throw new InvalidDataProviderException(
'Empty data set provided by data provider',
method: key($data),
);
}

$method = new ReflectionMethod($className, $methodName);
$testMethodNumberOfParameters = $method->getNumberOfParameters();
$testMethodIsNonVariadic = !$method->isVariadic();

foreach ($data as $key => $value) {
if (!is_array($value)) {
throw new InvalidDataProviderException(
sprintf(
'Data set %s is invalid, expected array but got %s',
$this->formatKey($key),
get_debug_type($value),
),
);
}
foreach ($data as $providerMethodName => $providedData) {
foreach ($providedData as $key => $value) {
if (!is_array($value)) {
throw new InvalidDataProviderException(
sprintf(
'Data set %s is invalid, expected array but got %s',
$this->formatKey($key),
get_debug_type($value),
),
method: $providerMethodName,
);
}

if ($testMethodIsNonVariadic && $testMethodNumberOfParameters < count($value)) {
Event\Facade::emitter()->testTriggeredPhpunitWarning(
new TestMethod(
$className,
$methodName,
$method->getFileName(),
$method->getStartLine(),
Event\Code\TestDoxBuilder::fromClassNameAndMethodName(
if ($testMethodIsNonVariadic && $testMethodNumberOfParameters < count($value)) {
Event\Facade::emitter()->testTriggeredPhpunitWarning(
new TestMethod(
$className,
$methodName,
$method->getFileName(),
$method->getStartLine(),
Event\Code\TestDoxBuilder::fromClassNameAndMethodName(
$className,
$methodName,
),
MetadataCollection::fromArray([]),
Event\TestData\TestDataCollection::fromArray([]),
),
MetadataCollection::fromArray([]),
Event\TestData\TestDataCollection::fromArray([]),
),
sprintf(
'Data set %s has more arguments (%d) than the test method accepts (%d)',
$this->formatKey($key),
count($value),
$testMethodNumberOfParameters,
),
);
sprintf(
'Data set %s has more arguments (%d) than the test method accepts (%d)',
$this->formatKey($key),
count($value),
$testMethodNumberOfParameters,
),
);
}
}
}

return $data;
}

/**
* @param class-string $className
* @param non-empty-string $methodName
* @param class-string $testClassName Name of class with test
* @param non-empty-string $testMethodName Name of method containing test
*
* @throws InvalidDataProviderException
*
* @return array<array<mixed>>
*/
private function dataProvidedByMethods(string $className, string $methodName, MetadataCollection $dataProvider): array
private function dataProvidedByMethods(string $testClassName, string $testMethodName, MetadataCollection $dataProvider): array
{
$testMethod = new Event\Code\ClassMethod($className, $methodName);
$testMethod = new ClassMethod($testClassName, $testMethodName);
$methodsCalled = [];
$result = [];
$return = [];
$caseNames = [];

foreach ($dataProvider as $_dataProvider) {
assert($_dataProvider instanceof DataProviderMetadata);

$dataProviderMethod = new Event\Code\ClassMethod($_dataProvider->className(), $_dataProvider->methodName());
$providerClassName = $_dataProvider->className();
$providerMethodName = $_dataProvider->methodName();
$dataProviderMethod = new ClassMethod($providerClassName, $providerMethodName);

Event\Facade::emitter()->dataProviderMethodCalled(
$testMethod,
Expand All @@ -135,91 +146,92 @@ private function dataProvidedByMethods(string $className, string $methodName, Me
$method = new ReflectionMethod($_dataProvider->className(), $_dataProvider->methodName());

if (!$method->isPublic()) {
throw new InvalidDataProviderException(
sprintf(
'Data Provider method %s::%s() is not public',
$_dataProvider->className(),
$_dataProvider->methodName(),
),
);
$this->throwInvalid('is not public', $_dataProvider);
}

if (!$method->isStatic()) {
throw new InvalidDataProviderException(
sprintf(
'Data Provider method %s::%s() is not static',
$_dataProvider->className(),
$_dataProvider->methodName(),
),
);
$this->throwInvalid('is not static', $_dataProvider);
}

if ($method->getNumberOfParameters() > 0) {
throw new InvalidDataProviderException(
sprintf(
'Data Provider method %s::%s() expects an argument',
$_dataProvider->className(),
$_dataProvider->methodName(),
),
);
$this->throwInvalid('expects an argument', $_dataProvider);
}

$className = $_dataProvider->className();
$methodName = $_dataProvider->methodName();

/** @phpstan-ignore staticMethod.dynamicName */
$data = $className::$methodName();
$data = $providerClassName::$providerMethodName();

if (!is_iterable($data)) {
$this->throwInvalid('does not provide iterable type', $_dataProvider);
}
} catch (Throwable $e) {
Event\Facade::emitter()->dataProviderMethodFinished(
$testMethod,
...$methodsCalled,
);
$this->finishMethods($testMethod, $methodsCalled);

throw new InvalidDataProviderException(
$e->getMessage(),
$e->getCode(),
$e,
$providerMethodName,
);
}

$result = [];

foreach ($data as $key => $value) {
if (is_int($key)) {
$result[] = $value;
} elseif (is_string($key)) {
if (array_key_exists($key, $result)) {
Event\Facade::emitter()->dataProviderMethodFinished(
$testMethod,
...$methodsCalled,
);
if (isset($caseNames[$key])) {
$this->finishMethods($testMethod, $methodsCalled);

throw new InvalidDataProviderException(
sprintf(
'The key "%s" has already been defined by a previous data provider',
$key,
),
method: $providerMethodName,
);
}

$result[$key] = $value;
$caseNames[$key] = 1;
$result[$key] = $value;
} else {
// @codeCoverageIgnoreStart
throw new InvalidDataProviderException(
sprintf(
'The key must be an integer or a string, %s given',
get_debug_type($key),
),
method: $providerMethodName,
);
// @codeCoverageIgnoreEnd
}
}
$return[$providerMethodName] = $result;
}
$this->finishMethods($testMethod, $methodsCalled);

return $return;
}

private function throwInvalid(string $message, DataProviderMetadata $dataProvider): never
{
throw new InvalidDataProviderException(
sprintf(
'Data Provider method %s::%s() ',
$dataProvider->className(),
$dataProvider->methodName(),
) . $message,
);
}

/**
* @param ClassMethod[] $methodsCalled
*/
private function finishMethods(ClassMethod $method, array $methodsCalled): void
{
Event\Facade::emitter()->dataProviderMethodFinished(
$testMethod,
$method,
...$methodsCalled,
);

return $result;
}

/**
Expand Down
Loading
Loading