Merge pull request #22 from weirdan/traits-in-tests

Added support for test methods and providers in traits
This commit is contained in:
Bruce Weirdan 2019-03-01 20:53:36 +02:00 committed by GitHub
commit 2fcf618ba9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 143 additions and 22 deletions

View File

@ -8,6 +8,7 @@ use SimpleXMLElement;
use Psalm\Plugin\PluginEntryPointInterface;
use Psalm\Plugin\RegistrationInterface;
/** @psalm-suppress UnusedClass */
class Plugin implements PluginEntryPointInterface
{
/** @return void */

View File

@ -121,22 +121,30 @@ class TestCaseHandler implements
return null;
}
foreach ($class_storage->methods as $method_name => $method_storage) {
foreach ($class_storage->declaring_method_ids as $method_name_lc => $declaring_method_id) {
$method_name = $codebase->getCasedMethodId($class_storage->name . '::' . $method_name_lc);
$method_storage = $codebase->methods->getStorage($declaring_method_id);
list($declaring_method_class, $declaring_method_name) = explode('::', $declaring_method_id);
$declaring_class_storage = $codebase->classlike_storage_provider->get($declaring_method_class);
$declaring_class_node = $class_node;
if ($declaring_class_storage->is_trait) {
$declaring_class_node = $codebase->classlikes->getTraitNode($declaring_class_storage->name);
}
if (!$method_storage->location) {
continue;
}
$stmt_method = $class_node->getMethod($method_name);
$stmt_method = $declaring_class_node->getMethod($declaring_method_name);
if (!$stmt_method) {
throw new \RuntimeException('Failed to find ' . $method_name);
continue;
}
$specials = self::getSpecials($stmt_method);
$method_id = $class_storage->name . '::' . $method_storage->cased_name;
if (0 !== strpos($method_storage->cased_name, 'test')
if (0 !== strpos($method_name_lc, 'test')
&& !isset($specials['test'])) {
continue; // skip non-test methods
}
@ -148,23 +156,25 @@ class TestCaseHandler implements
}
foreach ($specials['dataProvider'] as $line => $provider) {
$provider_method_id = $class_storage->name . '::' . (string) $provider;
$provider_docblock_location = clone $method_storage->location;
$provider_docblock_location->setCommentLine($line);
// methodExists also can mark methods as used (weird, but handy)
if (!$codebase->methodExists($provider_method_id, $provider_docblock_location, $method_id)) {
IssueBuffer::accepts(new Issue\UndefinedMethod(
'Provider method ' . $provider_method_id . ' is not defined',
$provider_docblock_location,
$provider_method_id
));
$apparent_provider_method_name = $class_storage->name . '::' . (string) $provider;
$provider_method_id = $codebase->getDeclaringMethodId($apparent_provider_method_name);
// methodExists also can mark methods as used (weird, but handy)
if (null === $provider_method_id
|| !$codebase->methodExists($provider_method_id, $provider_docblock_location, $declaring_method_id)
) {
IssueBuffer::accepts(new Issue\UndefinedMethod(
'Provider method ' . $apparent_provider_method_name . ' is not defined',
$provider_docblock_location,
$apparent_provider_method_name
));
continue;
}
$provider_return_type = $codebase->getMethodReturnType($provider_method_id, $classStorage->name);
$provider_return_type = $codebase->getMethodReturnType($provider_method_id, $_);
assert(null !== $provider_return_type);
$provider_return_type_string = $provider_return_type->getId();
@ -228,7 +238,7 @@ class TestCaseHandler implements
int $param_offset
) use (
$codebase,
$method_id,
$method_name,
$provider_method_id,
$provider_return_type_string,
$provider_docblock_location
@ -242,7 +252,7 @@ class TestCaseHandler implements
// ok
} elseif (self::canTypeBeContainedByType($codebase, $potential_argument_type, $param_type)) {
IssueBuffer::accepts(new Issue\PossiblyInvalidArgument(
'Argument ' . ($param_offset + 1) . ' of ' . $method_id
'Argument ' . ($param_offset + 1) . ' of ' . $method_name
. ' expects ' . $param_type->getId() . ', '
. $potential_argument_type->getId() . ' provided'
. ' by ' . $provider_method_id . '():(' . $provider_return_type_string . ')',
@ -250,7 +260,7 @@ class TestCaseHandler implements
));
} elseif ($potential_argument_type->possibly_undefined && !$param->default_type) {
IssueBuffer::accepts(new Issue\InvalidArgument(
'Argument ' . ($param_offset + 1) . ' of ' . $method_id
'Argument ' . ($param_offset + 1) . ' of ' . $method_name
. ' has no default value, but possibly undefined '
. $potential_argument_type->getId() . ' provided'
. ' by ' . $provider_method_id . '():(' . $provider_return_type_string . ')',
@ -258,7 +268,7 @@ class TestCaseHandler implements
));
} else {
IssueBuffer::accepts(new Issue\InvalidArgument(
'Argument ' . ($param_offset + 1) . ' of ' . $method_id
'Argument ' . ($param_offset + 1) . ' of ' . $method_name
. ' expects ' . $param_type->getId() . ', '
. $potential_argument_type->getId() . ' provided'
. ' by ' . $provider_method_id . '():(' . $provider_return_type_string . ')',
@ -283,13 +293,13 @@ class TestCaseHandler implements
if (count($potential_argument_types) < $method_storage->required_param_count) {
IssueBuffer::accepts(new Issue\TooFewArguments(
'Too few arguments for ' . $method_id
'Too few arguments for ' . $method_name
. ' - expecting ' . $method_storage->required_param_count
. ' but saw ' . count($potential_argument_types)
. ' provided by ' . $provider_method_id . '()'
. ':(' . $provider_return_type_string . ')',
$provider_docblock_location,
$method_id
$method_name
));
}

View File

@ -747,3 +747,113 @@ Feature: TestCase
"""
When I run Psalm
Then I see no errors
Scenario: Test methods in traits are not marked as unused
Given I have the following code
"""
trait MyTestTrait {
/** @return void */
public function testSomething() {}
}
class MyTestCase extends TestCase {
use MyTestTrait;
}
"""
When I run Psalm with dead code detection
Then I see no errors
Scenario: Inherited test methods are not marked as unused
Given I have the following code
"""
abstract class IntermediateTest extends TestCase {
/** @return void */
public function testSomething() {}
}
class MyTestCase extends IntermediateTest {
}
"""
When I run Psalm with dead code detection
Then I see no errors
Scenario: Data providers in traits are not marked as unused
Given I have the following code
"""
trait MyTestTrait {
/** @return iterable<int,array{int}> */
public function provide() { return [[1]]; }
}
class MyTestCase extends TestCase {
use MyTestTrait;
/**
* @return void
* @dataProvider provide
*/
public function testSomething(int $_i) {}
}
"""
When I run Psalm with dead code detection
Then I see no errors
Scenario: Data providers in test case when test methods are in trait are not marked as unused
Given I have the following code
"""
trait MyTestTrait {
/**
* @return void
* @dataProvider provide
*/
public function testSomething(int $_i) {}
}
class MyTestCase extends TestCase {
use MyTestTrait;
/** @return iterable<int,array{int}> */
public function provide() { return [[1]]; }
}
"""
When I run Psalm with dead code detection
Then I see no errors
Scenario: Renamed imported test methods are validated
Given I have the following code
"""
trait MyTestTrait {
/**
* @return void
* @dataProvider provide
*/
public function foo(int $_i) {}
}
class MyTestCase extends TestCase {
use MyTestTrait { foo as testAnything; }
}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| UndefinedMethod | Provider method NS\MyTestCase::provide is not defined |
And I see no other errors
Scenario: Test methods and providers in trait used by a test case are validated
Given I have the following code
"""
trait MyTestTrait {
/**
* @return void
* @dataProvider provide
*/
public function testSomething(string $_p) {}
/**
* @return iterable<int,int[]>
*/
public function provide() { return [[1]]; }
}
class MyTestCase extends TestCase {
use MyTestTrait;
}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| InvalidArgument | Argument 1 of NS\MyTestTrait::testSomething expects string, int provided by NS\MyTestTrait::provide():(iterable<int, array<array-key, int>>) |
And I see no other errors