Skip to content
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

@testWith annotation #1728

Merged
merged 9 commits into from
May 28, 2015
90 changes: 76 additions & 14 deletions src/Util/Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ function trait_exists($traitname, $autoload = true)
class PHPUnit_Util_Test
{
const REGEX_DATA_PROVIDER = '/@dataProvider\s+([a-zA-Z0-9._:-\\\\x7f-\xff]+)/';
const REGEX_TEST_WITH = '/@testWith\s+/';
const REGEX_EXPECTED_EXCEPTION = '(@expectedException\s+([:.\w\\\\x7f-\xff]+)(?:[\t ]+(\S*))?(?:[\t ]+(\S*))?\s*$)m';
const REGEX_REQUIRES_VERSION = '/@requires\s+(?P<name>PHP(?:Unit)?)\s+(?P<value>[\d\.-]+(dev|(RC|alpha|beta)[\d\.])?)[ \t]*\r?$/m';
const REGEX_REQUIRES_OS = '/@requires\s+OS\s+(?P<value>.+?)[ \t]*\r?$/m';
Expand Down Expand Up @@ -346,7 +347,6 @@ private static function parseAnnotationContent($message)
* @param string $className
* @param string $methodName
* @return array|Iterator when a data provider is specified and exists
* false when a data provider is specified but does not exist
* null when no data provider is specified
* @throws PHPUnit_Framework_Exception
* @since Method available since Release 3.2.0
Expand All @@ -357,6 +357,46 @@ public static function getProvidedData($className, $methodName)
$docComment = $reflector->getDocComment();
$data = null;

if ($dataProviderData = self::getDataFromDataProviderAnnotation($docComment, $className, $methodName)) {
$data = $dataProviderData;
}

if ($testWithData = self::getDataFromTestWithAnnotation($docComment)) {
$data = $testWithData;
}

if ($data !== null) {
if (is_object($data)) {
$data = iterator_to_array($data);
}

foreach ($data as $key => $value) {
if (!is_array($value)) {
throw new PHPUnit_Framework_Exception(
sprintf(
'Data set %s is invalid.',
is_int($key) ? '#' . $key : '"' . $key . '"'
)
);
}
}
}

return $data;
}

/**
* Returns the provided data for a method.
*
* @param string $docComment
* @param string $className
* @param string $methodName
* @return array|Iterator when a data provider is specified and exists
* null when no data provider is specified
* @throws PHPUnit_Framework_Exception
*/
private static function getDataFromDataProviderAnnotation($docComment, $className, $methodName)
{
if (preg_match(self::REGEX_DATA_PROVIDER, $docComment, $matches)) {
$dataProviderMethodNameNamespace = explode('\\', $matches[1]);
$leaf = explode('::', array_pop($dataProviderMethodNameNamespace));
Expand Down Expand Up @@ -390,26 +430,48 @@ public static function getProvidedData($className, $methodName)
} else {
$data = $dataProviderMethod->invoke($object, $methodName);
}

return $data;
}
}

if ($data !== null) {
if (is_object($data)) {
$data = iterator_to_array($data);
/**
* @param string $docComment full docComment string
* @return array when @testWith annotation is defined
* null when @testWith annotation is omitted
* @throws PHPUnit_Framework_Exception when @testWith annotation is defined but cannot be parsed
*/
public static function getDataFromTestWithAnnotation($docComment)
{
$docComment = self::cleanUpMultiLineAnnotation($docComment);
if (preg_match(self::REGEX_TEST_WITH, $docComment, $matches, PREG_OFFSET_CAPTURE)) {
$offset = strlen($matches[0][0]) + $matches[0][1];
$annotationContent = substr($docComment, $offset);
$data = array();
foreach (explode("\n", $annotationContent) as $candidateRow) {
$candidateRow = trim($candidateRow);
$dataSet = json_decode($candidateRow, true);
if (json_last_error() != JSON_ERROR_NONE) {
break;
}
$data[] = $dataSet;
}

foreach ($data as $key => $value) {
if (!is_array($value)) {
throw new PHPUnit_Framework_Exception(
sprintf(
'Data set %s is invalid.',
is_int($key) ? '#' . $key : '"' . $key . '"'
)
);
}
if (!$data) {
throw new PHPUnit_Framework_Exception("The dataset for the @testWith annotation cannot be parsed.");
}

return $data;
}
}

return $data;
private static function cleanUpMultiLineAnnotation($docComment)
{
//removing initial ' * ' for docComment
$docComment = preg_replace('/' . '\n' . '\s*' . '\*' . '\s?' . '/', "\n", $docComment);
$docComment = substr($docComment, 0, -1);
$docComment = rtrim($docComment, "\n");
return $docComment;
}

/**
Expand Down
14 changes: 14 additions & 0 deletions tests/Framework/SuiteTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'BeforeAndAfterTest.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'BeforeClassAndAfterClassTest.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'TestWithTest.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'DataProviderSkippedTest.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'DataProviderIncompleteTest.php';
require_once dirname(__DIR__) . DIRECTORY_SEPARATOR . '_files' . DIRECTORY_SEPARATOR . 'InheritedTestCase.php';
Expand Down Expand Up @@ -55,6 +56,7 @@ public static function suite()
$suite->addTest(new Framework_SuiteTest('testShadowedTests'));
$suite->addTest(new Framework_SuiteTest('testBeforeClassAndAfterClassAnnotations'));
$suite->addTest(new Framework_SuiteTest('testBeforeAnnotation'));
$suite->addTest(new Framework_SuiteTest('testTestWithAnnotation'));
$suite->addTest(new Framework_SuiteTest('testSkippedTestDataProvider'));
$suite->addTest(new Framework_SuiteTest('testIncompleteTestDataProvider'));
$suite->addTest(new Framework_SuiteTest('testRequirementsBeforeClassHook'));
Expand Down Expand Up @@ -187,6 +189,18 @@ public function testBeforeAnnotation()
$this->assertEquals(2, BeforeAndAfterTest::$afterWasRun);
}

public function testTestWithAnnotation()
{
$test = new PHPUnit_Framework_TestSuite(
'TestWithTest'
);

BeforeAndAfterTest::resetProperties();
$result = $test->run();

$this->assertEquals(4, count($result->passed()));
}

public function testSkippedTestDataProvider()
{
$suite = new PHPUnit_Framework_TestSuite('DataProviderSkippedTest');
Expand Down
96 changes: 96 additions & 0 deletions tests/Util/TestTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,102 @@ public function testGetProvidedDataRegEx()
$this->assertEquals('メソッド', $matches[1]);
}

/**
* @covers PHPUnit_Util_Test::getDataFromTestWithAnnotation
*/
public function testTestWithEmptyAnnotation()
{
$result = PHPUnit_Util_Test::getDataFromTestWithAnnotation("/**\n * @anotherAnnotation\n */");
$this->assertNull($result);
}

/**
* @covers PHPUnit_Util_Test::getDataFromTestWithAnnotation
*/
public function testTestWithSimpleCase()
{
$result = PHPUnit_Util_Test::getDataFromTestWithAnnotation("/**
* @testWith [1]
*/");
$this->assertEquals(array(array(1)), $result);
}

/**
* @covers PHPUnit_Util_Test::getDataFromTestWithAnnotation
*/
public function testTestWithMultiLineMultiParameterCase()
{
$result = PHPUnit_Util_Test::getDataFromTestWithAnnotation("/**
* @testWith [1, 2]
* [3, 4]
*/");
$this->assertEquals(array(array(1, 2), array(3, 4)), $result);
}

/**
* @covers PHPUnit_Util_Test::getDataFromTestWithAnnotation
*/
public function testTestWithVariousTypes()
{
$result = PHPUnit_Util_Test::getDataFromTestWithAnnotation('/**
* @testWith ["ab"]
* [true]
* [null]
*/');
$this->assertEquals(array(array("ab"), array(true), array(null)), $result);
}

/**
* @covers PHPUnit_Util_Test::getDataFromTestWithAnnotation
*/
public function testTestWithAnnotationAfter()
{
$result = PHPUnit_Util_Test::getDataFromTestWithAnnotation("/**
* @testWith [1]
* [2]
* @annotation
*/");
$this->assertEquals(array(array(1), array(2)), $result);
}

/**
* @covers PHPUnit_Util_Test::getDataFromTestWithAnnotation
*/
public function testTestWithSimpleTextAfter()
{
$result = PHPUnit_Util_Test::getDataFromTestWithAnnotation("/**
* @testWith [1]
* [2]
* blah blah
*/");
$this->assertEquals(array(array(1), array(2)), $result);
}

/**
* @covers PHPUnit_Util_Test::getDataFromTestWithAnnotation
*/
public function testTestWithCharacterEscape()
{
$result = PHPUnit_Util_Test::getDataFromTestWithAnnotation('/**
* @testWith ["\"", "\""]
*/');
$this->assertEquals(array(array('"', '"')), $result);
}

/**
* @covers PHPUnit_Util_Test::getDataFromTestWithAnnotation
*/
public function testTestWithThrowsProperExceptionIfDatasetCannotBeParsed()
{
$this->setExpectedExceptionRegExp(
"PHPUnit_Framework_Exception",
"/^The dataset for the @testWith annotation cannot be parsed.$/"
);
PHPUnit_Util_Test::getDataFromTestWithAnnotation("/**
* @testWith [s]
*/");
}

/**
* @covers PHPUnit_Util_Test::getDependencies
* @todo Not sure what this test tests (name is misleading at least)
Expand Down
24 changes: 24 additions & 0 deletions tests/_files/TestWithTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php
class TestWithTest extends PHPUnit_Framework_TestCase
{
/**
* @testWith [0, 0, 0]
* [0, 1, 1]
* [1, 2, 3]
* [20, 22, 42]
*/
public function testAdd($a, $b, $c)
{
$this->assertEquals($c, $a + $b);
}

public static function providerMethod()
{
return array(
array(0, 0, 0),
array(0, 1, 1),
array(1, 1, 3),
array(1, 0, 1)
);
}
}