Skip to content
Closed
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
9 changes: 8 additions & 1 deletion src/Framework/Attributes/DataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,16 @@
*/
private string $methodName;
private bool $validateArgumentCount;
private bool $skipWhenEmpty;

/**
* @param non-empty-string $methodName
*/
public function __construct(string $methodName, bool $validateArgumentCount = true)
public function __construct(string $methodName, bool $validateArgumentCount = true, bool $skipWhenEmpty = false)
{
$this->methodName = $methodName;
$this->validateArgumentCount = $validateArgumentCount;
$this->skipWhenEmpty = $skipWhenEmpty;
}

/**
Expand All @@ -46,4 +48,9 @@ public function validateArgumentCount(): bool
{
return $this->validateArgumentCount;
}

public function skipWhenEmpty(): bool
{
return $this->skipWhenEmpty;
}
}
9 changes: 8 additions & 1 deletion src/Framework/Attributes/DataProviderExternal.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,18 @@
*/
private string $methodName;
private bool $validateArgumentCount;
private bool $skipWhenEmpty;

/**
* @param class-string $className
* @param non-empty-string $methodName
*/
public function __construct(string $className, string $methodName, bool $validateArgumentCount = true)
public function __construct(string $className, string $methodName, bool $validateArgumentCount = true, bool $skipWhenEmpty = false)
{
$this->className = $className;
$this->methodName = $methodName;
$this->validateArgumentCount = $validateArgumentCount;
$this->skipWhenEmpty = $skipWhenEmpty;
}

/**
Expand All @@ -61,4 +63,9 @@ public function validateArgumentCount(): bool
{
return $this->validateArgumentCount;
}

public function skipWhenEmpty(): bool
{
return $this->skipWhenEmpty;
}
}
8 changes: 7 additions & 1 deletion src/Framework/TestBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public function build(ReflectionClass $theClass, string $methodName, array $grou
}
}

if ($data !== null) {
if ($data !== null && $data !== []) {
return $this->buildDataProviderTestSuite(
$methodName,
$className,
Expand All @@ -69,6 +69,12 @@ public function build(ReflectionClass $theClass, string $methodName, array $grou

$test = new $className($methodName);

if ($data === []) {
$test->setEmptyDataProviderSkipMessage(
'The data provider for this test provided no data, which is explicitly permitted',
);
}

$this->configureTestCase(
$test,
$this->shouldTestMethodBeRunInSeparateProcess($className, $methodName),
Expand Down
17 changes: 15 additions & 2 deletions src/Framework/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,9 @@ abstract class TestCase extends Assert implements Reorderable, SelfDescribing, T
/**
* @var false|resource
*/
private mixed $errorLogCapture = false;
private false|string $previousErrorLogTarget = false;
private mixed $errorLogCapture = false;
private false|string $previousErrorLogTarget = false;
private ?string $emptyDataProviderSkipMessage = null;

/**
* @param non-empty-string $name
Expand Down Expand Up @@ -500,6 +501,10 @@ final public function runBare(): void
$this->checkRequirements();
$hasMetRequirements = true;

if ($this->emptyDataProviderSkipMessage !== null) {
$this->markTestSkipped($this->emptyDataProviderSkipMessage);
}

if ($this->inIsolation) {
// @codeCoverageIgnoreStart
$this->invokeBeforeClassHookMethods($hookMethods, $emitter);
Expand Down Expand Up @@ -788,6 +793,14 @@ final public function setInIsolation(bool $inIsolation): void
$this->inIsolation = $inIsolation;
}

/**
* @internal This method is not covered by the backward compatibility promise for PHPUnit
*/
final public function setEmptyDataProviderSkipMessage(string $message): void
{
$this->emptyDataProviderSkipMessage = $message;
}

/**
* @internal This method is not covered by the backward compatibility promise for PHPUnit
*
Expand Down
9 changes: 9 additions & 0 deletions src/Metadata/Api/DataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ private function dataProvidedByMethods(string $testClassName, ReflectionMethod $

$methodsCalled = [];
$result = [];
$skipWhenEmpty = false;
$testMethodNumberOfParameters = $testMethod->getNumberOfParameters();
$testMethodIsNonVariadic = !$testMethod->isVariadic();

Expand All @@ -96,6 +97,10 @@ private function dataProvidedByMethods(string $testClassName, ReflectionMethod $
$dataProviderMethod = new Event\Code\ClassMethod($_dataProvider->className(), $_dataProvider->methodName());
$validateArgumentCount = $testMethodIsNonVariadic && $_dataProvider->validateArgumentCount();

if ($_dataProvider->skipWhenEmpty()) {
$skipWhenEmpty = true;
}

Event\Facade::emitter()->dataProviderMethodCalled(
$testMethodValueObject,
$dataProviderMethod,
Expand Down Expand Up @@ -331,6 +336,10 @@ private function dataProvidedByMethods(string $testClassName, ReflectionMethod $
);

if ($result === []) {
if ($skipWhenEmpty) {
return [];
}

throw new InvalidDataProviderException(
'Empty data set provided by data provider',
);
Expand Down
9 changes: 8 additions & 1 deletion src/Metadata/DataProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,20 @@
*/
private string $methodName;
private bool $validateArgumentCount;
private bool $skipWhenEmpty;

/**
* @param class-string $className
* @param non-empty-string $methodName
*/
protected function __construct(Level $level, string $className, string $methodName, bool $validateArgumentCount)
protected function __construct(Level $level, string $className, string $methodName, bool $validateArgumentCount, bool $skipWhenEmpty)
{
parent::__construct($level);

$this->className = $className;
$this->methodName = $methodName;
$this->validateArgumentCount = $validateArgumentCount;
$this->skipWhenEmpty = $skipWhenEmpty;
}

public function isDataProvider(): true
Expand Down Expand Up @@ -65,4 +67,9 @@ public function validateArgumentCount(): bool
{
return $this->validateArgumentCount;
}

public function skipWhenEmpty(): bool
{
return $this->skipWhenEmpty;
}
}
4 changes: 2 additions & 2 deletions src/Metadata/Metadata.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,9 @@ public static function coversNothingOnMethod(): CoversNothing
* @param class-string $className
* @param non-empty-string $methodName
*/
public static function dataProvider(string $className, string $methodName, bool $validateArgumentCount): DataProvider
public static function dataProvider(string $className, string $methodName, bool $validateArgumentCount, bool $skipWhenEmpty): DataProvider
{
return new DataProvider(Level::METHOD_LEVEL, $className, $methodName, $validateArgumentCount);
return new DataProvider(Level::METHOD_LEVEL, $className, $methodName, $validateArgumentCount, $skipWhenEmpty);
}

public static function dataProviderClosure(Closure $callable, bool $validateArgumentCount): DataProviderClosure
Expand Down
4 changes: 2 additions & 2 deletions src/Metadata/Parser/AttributeParser.php
Original file line number Diff line number Diff line change
Expand Up @@ -608,14 +608,14 @@ public function forMethod(string $className, string $methodName): MetadataCollec
case DataProvider::class:
assert($attributeInstance instanceof DataProvider);

$result[] = Metadata::dataProvider($className, $attributeInstance->methodName(), $attributeInstance->validateArgumentCount());
$result[] = Metadata::dataProvider($className, $attributeInstance->methodName(), $attributeInstance->validateArgumentCount(), $attributeInstance->skipWhenEmpty());

break;

case DataProviderExternal::class:
assert($attributeInstance instanceof DataProviderExternal);

$result[] = Metadata::dataProvider($attributeInstance->className(), $attributeInstance->methodName(), $attributeInstance->validateArgumentCount());
$result[] = Metadata::dataProvider($attributeInstance->className(), $attributeInstance->methodName(), $attributeInstance->validateArgumentCount(), $attributeInstance->skipWhenEmpty());

break;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php declare(strict_types=1);
/*
* This file is part of PHPUnit.
*
* (c) Sebastian Bergmann <sebastian@phpunit.de>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace PHPUnit\TestFixture\Event;

use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;

final class EmptyDataProviderSkipWhenEmptyTest extends TestCase
{
public static function providerMethod(): array
{
return [];
}

#[DataProvider('providerMethod', skipWhenEmpty: true)]
public function testCase(): void
{
}
}
31 changes: 31 additions & 0 deletions tests/end-to-end/event/data-provider-empty-skip-when-empty.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
The right events are emitted in the right order for a test that uses an empty data provider with skipWhenEmpty
--FILE--
<?php declare(strict_types=1);
$_SERVER['argv'][] = '--do-not-cache-result';
$_SERVER['argv'][] = '--no-configuration';
$_SERVER['argv'][] = '--debug';
$_SERVER['argv'][] = __DIR__ . '/_files/EmptyDataProviderSkipWhenEmptyTest.php';

require __DIR__ . '/../../bootstrap.php';

(new PHPUnit\TextUI\Application)->run($_SERVER['argv']);
--EXPECTF--
PHPUnit Started (PHPUnit %s using %s)
Test Runner Configured
Event Facade Sealed
Data Provider Method Called (PHPUnit\TestFixture\Event\EmptyDataProviderSkipWhenEmptyTest::providerMethod for test method PHPUnit\TestFixture\Event\EmptyDataProviderSkipWhenEmptyTest::testCase)
Data Provider Method Finished for PHPUnit\TestFixture\Event\EmptyDataProviderSkipWhenEmptyTest::testCase:
- PHPUnit\TestFixture\Event\EmptyDataProviderSkipWhenEmptyTest::providerMethod
Test Suite Loaded (1 test)
Test Runner Started
Test Suite Sorted
Test Runner Execution Started (1 test)
Test Suite Started (PHPUnit\TestFixture\Event\EmptyDataProviderSkipWhenEmptyTest, 1 test)
Test Preparation Started (PHPUnit\TestFixture\Event\EmptyDataProviderSkipWhenEmptyTest::testCase)
Test Skipped (PHPUnit\TestFixture\Event\EmptyDataProviderSkipWhenEmptyTest::testCase)
The data provider for this test provided no data, which is explicitly permitted
Test Suite Finished (PHPUnit\TestFixture\Event\EmptyDataProviderSkipWhenEmptyTest, 1 test)
Test Runner Execution Finished
Test Runner Finished
PHPUnit Finished (Shell Exit Code: 0)
2 changes: 1 addition & 1 deletion tests/unit/Metadata/MetadataCollectionTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ private function collectionWithOneOfEach(): MetadataCollection
Metadata::coversFunction(''),
Metadata::coversMethod('', ''),
Metadata::coversNothingOnClass(),
Metadata::dataProvider('', '', true),
Metadata::dataProvider('', '', true, false),
Metadata::dataProviderClosure($closure, true),
Metadata::dependsOnClass('', false, false),
Metadata::dependsOnMethod('', '', false, false),
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/Metadata/MetadataTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -1210,7 +1210,7 @@ public function testCanBeCoversTrait(): void

public function testCanBeDataProvider(): void
{
$metadata = Metadata::dataProvider(self::class, 'method', true);
$metadata = Metadata::dataProvider(self::class, 'method', true, false);

$this->assertFalse($metadata->isAfter());
$this->assertFalse($metadata->isAfterClass());
Expand Down