Skip to content

Commit

Permalink
Merge pull request sebastianbergmann#1728 from giorgiosironi/test_with
Browse files Browse the repository at this point in the history
@testwith annotation
  • Loading branch information
sebastianbergmann committed May 28, 2015
2 parents 49c2007 + 5567c59 commit a9f2f67
Show file tree
Hide file tree
Showing 4 changed files with 210 additions and 14 deletions.
90 changes: 76 additions & 14 deletions src/Util/Test.php
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
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
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
@@ -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)
);
}
}

0 comments on commit a9f2f67

Please sign in to comment.