Merge pull request #10 from weirdan/7.5-features

Added PHPUnit 7.5 assertions
This commit is contained in:
Bruce Weirdan 2019-02-11 09:24:18 +02:00 committed by GitHub
commit 74215dc546
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 476 additions and 2 deletions

View File

@ -9,6 +9,7 @@ install:
- if [[ "$DEPS" = 'high' ]]; then travis_retry composer $DEFAULT_COMPOSER_FLAGS update; fi
- if [[ "$DEPS" = 'low' ]]; then travis_retry composer $DEFAULT_COMPOSER_FLAGS --prefer-lowest --prefer-stable update; fi
- if [[ "$DEPS" = 'stable' ]]; then travis_retry composer $DEFAULT_COMPOSER_FLAGS --prefer-stable update; fi
- ./vendor/bin/psalm --version
script: composer check

View File

@ -1,6 +1,9 @@
<?php
namespace Psalm\PhpUnitPlugin;
use Composer\Semver\Comparator;
use Composer\Semver\VersionParser;
use Muglug\PackageVersions\Versions;
use SimpleXMLElement;
use Psalm\Plugin\PluginEntryPointInterface;
use Psalm\Plugin\RegistrationInterface;
@ -11,9 +14,24 @@ class Plugin implements PluginEntryPointInterface
public function __invoke(RegistrationInterface $psalm, SimpleXMLElement $config = null)
{
$psalm->addStubFile(__DIR__ . '/stubs/Assert.php');
if ($this->packageVersionIs('phpunit/phpunit', '>=', '7.5')) {
$psalm->addStubFile(__DIR__ . '/stubs/Assert_75.php');
}
$psalm->addStubFile(__DIR__ . '/stubs/TestCase.php');
$psalm->addStubFile(__DIR__ . '/stubs/MockBuilder.php');
$psalm->addStubFile(__DIR__ . '/stubs/InvocationMocker.php');
$psalm->addStubFile(__DIR__ . '/stubs/Prophecy.php');
}
private function packageVersionIs(string $package, string $op, string $ref): bool
{
$currentVersion = (string) Versions::getShortVersion($package);
$parser = new VersionParser();
$currentVersion = $parser->normalize($currentVersion);
$ref = $parser->normalize($ref);
return Comparator::compare($currentVersion, $op, $ref);
}
}

View File

@ -11,7 +11,9 @@
],
"require": {
"phpunit/phpunit": "^5.0 || ^6.0 || ^7.0",
"vimeo/psalm": "^3.0 || dev-master"
"vimeo/psalm": "^3.0 || dev-master",
"composer/semver": "^1.4",
"muglug/package-versions-56": "^1.2"
},
"require-dev": {
"squizlabs/php_codesniffer": "^3.3.1",

138
stubs/Assert_75.php Normal file
View File

@ -0,0 +1,138 @@
<?php // phpcs:ignoreFile
namespace PHPUnit\Framework;
abstract class Assert
{
/**
* @param mixed $actual
* @psalm-assert array $actual
*/
public static function assertIsArray($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert bool $actual
*/
public static function assertIsBool($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert float $actual
*/
public static function assertIsFloat($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert int $actual
*/
public static function assertIsInt($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert numeric $actual
*/
public static function assertIsNumeric($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert object $actual
*/
public static function assertIsObject($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert resource $actual
*/
public static function assertIsResource($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert string $actual
*/
public static function assertIsString($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert scalar $actual
*/
public static function assertIsScalar($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert callable $actual
*/
public static function assertIsCallable($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert iterable $actual
*/
public static function assertIsIterable($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !array $actual
*/
public static function assertIsNotArray($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !bool $actual
*/
public static function assertIsNotBool($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !float $actual
*/
public static function assertIsNotFloat($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !int $actual
*/
public static function assertIsNotInt($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !numeric $actual
*/
public static function assertIsNotNumeric($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !object $actual
*/
public static function assertIsNotObject($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !resource $actual
*/
public static function assertIsNotResource($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !string $actual
*/
public static function assertIsNotString($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !scalar $actual
*/
public static function assertIsNotScalar($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !callable $actual
*/
public static function assertIsNotCallable($actual, string $message = ''): void {}
/**
* @param mixed $actual
* @psalm-assert !iterable $actual
*/
public static function assertIsNotIterable($actual, string $message = ''): void {}
}

View File

@ -1,10 +1,46 @@
<?php
namespace Psalm\PhpUnitPlugin\Tests\Helper;
use Codeception\Exception\Skip;
use Codeception\Exception\TestRuntimeException;
use Composer\Semver\Comparator;
use Composer\Semver\VersionParser;
use Muglug\PackageVersions\Versions;
// here you can define custom actions
// all public methods declared in helper class will be available in $I
class Acceptance extends \Codeception\Module
{
/** @var array<string,string */
const VERSION_OPERATORS = [
'newer than' => '>',
'older than' => '<',
];
/**
* @Given /I have PHPUnit (newer than|older than) "([0-9.]+)" \(because of "([^"]+)"\)/
*/
public function havePHPUnitOfACertainVersionRangeBecauseOf(string $operator, string $version, string $reason): void
{
if (!isset(self::VERSION_OPERATORS[$operator])) {
throw new TestRuntimeException("Unknown operator: $operator");
}
$op = (string) self::VERSION_OPERATORS[$operator];
$currentVersion = (string) Versions::getShortVersion('phpunit/phpunit');
$this->debug(sprintf("Current version: %s", $currentVersion));
$parser = new VersionParser();
$currentVersion = $parser->normalize($currentVersion);
$version = $parser->normalize($version);
$result = Comparator::compare($currentVersion, $op, $version);
$this->debug("Comparing $currentVersion $op $version => $result");
if (!$result) {
throw new Skip("This scenario requires PHPUnit $op $version because of $reason");
}
}
}

View File

@ -12,7 +12,7 @@ Feature: Assert
"""
Scenario: Asserting instanceof
Scenario: Assert::assertInstanceOf()
Given I have the following code
"""
function f(): \Exception {

View File

@ -0,0 +1,279 @@
Feature: Assert (PHPUnit 7.5+)
In order to use PHPUnit safely
As a Psalm user
I need Psalm to typecheck asserts
Background:
Given I have the following code preamble
"""
<?php
namespace NS;
use PHPUnit\Framework\Assert;
/**
* @return mixed
* @psalm-suppress InvalidReturnType
*/
function mixed() {}
"""
And I have PHPUnit newer than "7.4.99999" (because of "new features in 7.5")
Scenario: Assert::assertIsArray()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$a = mixed();
Assert::assertIsArray($a);
array_pop($a);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsBool()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$b = mixed();
Assert::assertIsBool($b);
microtime($b);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsFloat()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$f = mixed();
Assert::assertIsFloat($f);
atan($f);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsInt()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$i = mixed();
Assert::assertIsInt($i);
substr('foo', $i);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNumeric()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$n = mixed();
Assert::assertIsNumeric($n);
$n + $n;
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsObject()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$o = mixed();
Assert::assertIsObject($o);
$o->foo = 1;
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsResource()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$r = mixed();
Assert::assertIsResource($r);
get_resource_type($r);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsString()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$s = mixed();
Assert::assertIsString($s);
strlen($s);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsScalar()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$s = mixed();
Assert::assertIsScalar($s); // int|string|float|bool
// all of the following should cause errors
if (is_array($s)) {}
if (is_resource($s)) {}
if (is_object($s)) {}
if (is_null($s)) {}
"""
When I run Psalm
Then I see these errors
| Type | Message |
| DocblockTypeContradiction | Cannot resolve types for $s - docblock-defined type scalar does not contain array<%, mixed> |
| DocblockTypeContradiction | Cannot resolve types for $s - docblock-defined type scalar does not contain resource |
| DocblockTypeContradiction | Found a contradiction with a docblock-defined type when evaluating $s and trying to reconcile type 'scalar' to object |
| DocblockTypeContradiction | Cannot resolve types for $s - docblock-defined type scalar does not contain null |
Scenario: Assert::assertIsCallable()
Given I have the following code
"""
/** @psalm-suppress MixedAssignment */
$s = mixed();
Assert::assertIsCallable($s);
\Closure::fromCallable($s);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsIterable()
Given I have the following code
"""
/** @return iterable */
function () {
/** @psalm-suppress MixedAssignment */
$s = mixed();
Assert::assertIsIterable($s);
return $s;
};
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotArray()
Given I have the following code
"""
$i = rand(0, 1) ? 1 : [1];
Assert::assertIsNotArray($i);
substr("foo", $i);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotBool()
Given I have the following code
"""
$i = rand(0, 1) ? 1 : true;
Assert::assertIsNotBool($i);
substr("foo", $i);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotFloat()
Given I have the following code
"""
$i = rand(0, 1) ? 1 : 0.1;
Assert::assertIsNotFloat($i);
substr("foo", $i);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotInt()
Given I have the following code
"""
$a = rand(0, 1) ? 1 : [1];
Assert::assertIsNotInt($a);
array_pop($a);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotNumeric()
Given I have the following code
"""
/** @return numeric|array */
function f() { return rand(0,1) ? 1 : [1]; }
$a = f();
Assert::assertIsNotNumeric($a);
array_pop($a);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotObject()
Given I have the following code
"""
$a = rand(0, 1) ? ((object)[]) : [1];
Assert::assertIsNotObject($a);
array_pop($a);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotResource()
Given I have the following code
"""
$a = rand(0, 1) ? STDIN : [1];
Assert::assertIsNotResource($a);
array_pop($a);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotString()
Given I have the following code
"""
$a = rand(0, 1) ? "foo" : [1];
Assert::assertIsNotString($a);
array_pop($a);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotScalar()
Given I have the following code
"""
$a = rand(0, 1) ? "foo" : [1];
Assert::assertIsNotScalar($a);
array_pop($a);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotCallable()
Given I have the following code
"""
/** @return callable|float */
function f() { return rand(0,1) ? 'f' : 1.1; }
$a = f();
Assert::assertIsNotCallable($a);
atan($a);
"""
When I run Psalm
Then I see no errors
Scenario: Assert::assertIsNotIterable()
Given I have the following code
"""
/** @var string|iterable $s */
$s = rand(0, 1) ? "foo" : [1];
Assert::assertIsNotIterable($s);
strlen($s);
"""
When I run Psalm
Then I see no errors