From 6ec947b82b019b24b00be7b9ed715be7199165c9 Mon Sep 17 00:00:00 2001 From: Brown Date: Fri, 6 Dec 2019 14:58:18 -0500 Subject: [PATCH] Move some tests into special fodler --- tests/AssertTest.php | 595 -------------- .../ConditionalTest.php} | 737 ++++++++++++++---- tests/TypeReconciliation/ReconcilerTest.php | 165 ++++ .../RedundantConditionTest.php | 8 +- tests/{ => TypeReconciliation}/ScopeTest.php | 8 +- tests/{ => TypeReconciliation}/TypeTest.php | 8 +- 6 files changed, 760 insertions(+), 761 deletions(-) delete mode 100644 tests/AssertTest.php rename tests/{TypeReconciliationTest.php => TypeReconciliation/ConditionalTest.php} (72%) create mode 100644 tests/TypeReconciliation/ReconcilerTest.php rename tests/{ => TypeReconciliation}/RedundantConditionTest.php (99%) rename tests/{ => TypeReconciliation}/ScopeTest.php (98%) rename tests/{ => TypeReconciliation}/TypeTest.php (99%) diff --git a/tests/AssertTest.php b/tests/AssertTest.php deleted file mode 100644 index 01bc62ddc..000000000 --- a/tests/AssertTest.php +++ /dev/null @@ -1,595 +0,0 @@ -,error_levels?:string[]}> - */ - public function providerValidCodeParse() - { - return [ - 'assertArrayReturnTypeNarrowed' => [ - ' [ - ' [ - ' [ - 'format("Y-m-d"); - } - }', - ], - 'assertCheckOnNonZeroArrayOffset' => [ - ' [ - ' $arr - */ - function uriToPath(array $arr) : string { - if (!isset($arr["a"]) || $arr["b"] !== "foo") { - throw new \InvalidArgumentException("bad"); - } - - return (string) $arr["c"]; - }', - ], - 'combineAfterLoopAssert' => [ - ' [ - ' [ - ' [ - ' [ - ' [ - ' [ - ' 0; - }', - ], - 'assertHasArrayAccess' => [ - ' [ - '> $array - * @return array - */ - function getBar(array $array) : array { - if (isset($array[\'foo\'][\'bar\'])) { - return $array[\'foo\']; - } - - return []; - }', - ], - 'assertHasArrayAccessOnSimpleXMLElement' => [ - 'bar)) {} - }', - ], - 'assertArrayOffsetToTraversable' => [ - ' [ - ' [ - ' [ - ' $arr - * @return non-empty-array - */ - function foo(array $arr) : array { - if (isset($arr["a"])) { - return $arr; - } - - return ["b" => 1]; - }' - ], - 'setArrayConstantOffset' => [ - ' [ - ' $arr - */ - function foo(A $a, array $arr): void { - if (!isset($arr[$a->id])) { - $arr[$a->id] = new B(); - } - $arr[$a->id]->foo(); - }' - ], - 'assertAfterNotEmptyArrayCheck' => [ - ' [ - 'c[$s]) && empty($this->c[$t])) {} - } - }' - ], - 'assertNotEmptyTwiceOnStaticPropertyArray' => [ - ' [ - ' [ - ' [ - ' [ - 'arr[0])) { - return $this->arr[0]; - } - - $this->arr[0] = new stdClass; - return $this->arr[0]; - } - }' - ], - 'assertArrayKeyExistsRefinesType' => [ - ' */ - public const DAYS = [ - 1 => "mon", - 2 => "tue", - 3 => "wed", - 4 => "thu", - 5 => "fri", - 6 => "sat", - 7 => "sun", - ]; - - /** @param key-of $dayNum*/ - private static function doGetDayName(int $dayNum): string { - return self::DAYS[$dayNum]; - } - - /** @throws LogicException */ - public static function getDayName(int $dayNum): string { - if (! array_key_exists($dayNum, self::DAYS)) { - throw new \LogicException(); - } - return self::doGetDayName($dayNum); - } - }' - ], - 'assertPropertiesOfElseStatement' => [ - 'a === "foo") { - } elseif ($obj->b === "bar") { - } else if ($obj->b === "baz") {} - - if ($obj->b === "baz") {} - }' - ], - 'assertPropertiesOfElseifStatement' => [ - 'a === "foo") { - } elseif ($obj->b === "bar") { - } elseif ($obj->b === "baz") {} - - if ($obj->b === "baz") {} - }' - ], - 'assertArrayWithOffset' => [ - ' [ - ' [ - ' [ - 'foo();', - [ - '$a' => 'bool', - ] - ], - 'SKIPPED-assertVarRedefinedInOpWithOr' => [ - 'foo();', - [ - '$a' => 'bool', - ] - ], - 'SKIPPED-assertVarRedefinedInIfWithAnd' => [ - ' [ - ' [ - ' [ - 'foo(); - } - }' - ], - 'assertOnArrayThings' => [ - '> */ - $a = null; - - if (isset($a["b"]) || isset($a["c"])) { - $all_params = ($a["b"] ?? []) + ($a["c"] ?? []); - }' - ] - ]; - } -} diff --git a/tests/TypeReconciliationTest.php b/tests/TypeReconciliation/ConditionalTest.php similarity index 72% rename from tests/TypeReconciliationTest.php rename to tests/TypeReconciliation/ConditionalTest.php index 7e2e57b88..49dccc89f 100644 --- a/tests/TypeReconciliationTest.php +++ b/tests/TypeReconciliation/ConditionalTest.php @@ -1,5 +1,5 @@ file_analyzer = new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php'); - $this->file_analyzer->context = new Context(); - $this->statements_analyzer = new StatementsAnalyzer( - $this->file_analyzer, - new \Psalm\Internal\Provider\NodeDataProvider() - ); - } - - /** - * @dataProvider providerTestReconcilation - * - * @param string $expected - * @param string $type - * @param string $string - * - * @return void - */ - public function testReconcilation($expected, $type, $string) - { - $reconciled = \Psalm\Internal\Type\AssertionReconciler::reconcile( - $type, - Type::parseString($string), - null, - $this->statements_analyzer, - false, - [] - ); - - $this->assertSame( - $expected, - $reconciled->getId() - ); - - if (is_array($reconciled->getTypes())) { - $this->assertContainsOnlyInstancesOf('Psalm\Type\Atomic', $reconciled->getTypes()); - } - } - - /** - * @dataProvider providerTestTypeIsContainedBy - * - * @param string $input - * @param string $container - * - * @return void - */ - public function testTypeIsContainedBy($input, $container) - { - $this->assertTrue( - TypeAnalyzer::isContainedBy( - $this->project_analyzer->getCodebase(), - Type::parseString($input), - Type::parseString($container) - ) - ); - } - - /** - * @return array - */ - public function providerTestReconcilation() - { - return [ - 'notNullWithObject' => ['MyObject', '!null', 'MyObject'], - 'notNullWithObjectPipeNull' => ['MyObject', '!null', 'MyObject|null'], - 'notNullWithMyObjectPipeFalse' => ['MyObject|false', '!null', 'MyObject|false'], - 'notNullWithMixed' => ['mixed', '!null', 'mixed'], - - 'notEmptyWithMyObject' => ['MyObject', '!falsy', 'MyObject'], - 'notEmptyWithMyObjectPipeNull' => ['MyObject', '!falsy', 'MyObject|null'], - 'notEmptyWithMyObjectPipeFalse' => ['MyObject', '!falsy', 'MyObject|false'], - 'notEmptyWithMixed' => ['non-empty-mixed', '!falsy', 'mixed'], - // @todo in the future this should also work - //'notEmptyWithMyObjectFalseTrue' => ['MyObject|true', '!falsy', 'MyObject|bool'], - - 'nullWithMyObjectPipeNull' => ['null', 'null', 'MyObject|null'], - 'nullWithMixed' => ['null', 'null', 'mixed'], - - 'falsyWithMyObject' => ['mixed', 'falsy', 'MyObject'], - 'falsyWithMyObjectPipeFalse' => ['false', 'falsy', 'MyObject|false'], - 'falsyWithMyObjectPipeBool' => ['false', 'falsy', 'MyObject|bool'], - 'falsyWithMixed' => ['empty-mixed', 'falsy', 'mixed'], - 'falsyWithBool' => ['false', 'falsy', 'bool'], - 'falsyWithStringOrNull' => ['null|string()|string(0)', 'falsy', 'string|null'], - 'falsyWithScalarOrNull' => ['empty-scalar', 'falsy', 'scalar'], - - 'notMyObjectWithMyObjectPipeBool' => ['bool', '!MyObject', 'MyObject|bool'], - 'notMyObjectWithMyObjectPipeNull' => ['null', '!MyObject', 'MyObject|null'], - 'notMyObjectWithMyObjectAPipeMyObjectB' => ['MyObjectB', '!MyObjectA', 'MyObjectA|MyObjectB'], - - 'myObjectWithMyObjectPipeBool' => ['MyObject', 'MyObject', 'MyObject|bool'], - 'myObjectWithMyObjectAPipeMyObjectB' => ['MyObjectA', 'MyObjectA', 'MyObjectA|MyObjectB'], - - 'array' => ['array', 'array', 'array|null'], - - '2dArray' => ['array>', 'array', 'array>|null'], - - 'numeric' => ['numeric-string', 'numeric', 'string'], - - 'nullableClassString' => ['null', 'falsy', '?class-string'], - 'mixedOrNullNotFalsy' => ['non-empty-mixed', '!falsy', 'mixed|null'], - 'mixedOrNullFalsy' => ['empty-mixed|null', 'falsy', 'mixed|null'], - 'nullableClassStringFalsy' => ['null', 'falsy', 'class-string|null'], - 'nullableClassStringEqualsNull' => ['null', '=null', 'class-string|null'], - 'nullableClassStringTruthy' => ['class-string', '!falsy', 'class-string|null'], - 'iterableToArray' => ['array', 'array', 'iterable'], - 'iterableToTraversable' => ['Traversable', 'Traversable', 'iterable'], - 'callableToCallableArray' => ['callable-array{0: object|string, 1: string}', 'array', 'callable'], - 'callableOrArrayToCallableArray' => ['array|callable-array{0: object|string, 1: string}', 'array', 'callable|array'], - 'traversableToIntersection' => ['Countable&Traversable', 'Traversable', 'Countable'], - 'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', '!array', 'iterable'], - 'iterableWithParamsToTraversableWithParams' => ['Traversable', '!array', 'iterable'], - ]; - } - - /** - * @return array - */ - public function providerTestTypeIsContainedBy() - { - return [ - 'arrayContainsWithArrayOfStrings' => ['array', 'array'], - 'arrayContainsWithArrayOfExceptions' => ['array', 'array'], - - 'unionContainsWithstring' => ['string', 'string|false'], - 'unionContainsWithFalse' => ['false', 'string|false'], - 'objectLikeTypeWithPossiblyUndefinedToGeneric' => [ - 'array{0: array{a: string}, 1: array{c: string, e: string}}', - 'array>', - ], - 'objectLikeTypeWithPossiblyUndefinedToEmpty' => [ - 'array', - 'array{a?: string, b?: string}', - ], - ]; - } + use \Psalm\Tests\Traits\InvalidCodeAnalysisTestTrait; + use \Psalm\Tests\Traits\ValidCodeAnalysisTestTrait; /** * @return iterable,error_levels?:string[]}> @@ -1521,6 +1371,585 @@ class TypeReconciliationTest extends TestCase if (isset($a[$b[0]->id])) {} }', ], + 'assertArrayReturnTypeNarrowed' => [ + ' [ + ' [ + ' [ + 'format("Y-m-d"); + } + }', + ], + 'assertCheckOnNonZeroArrayOffset' => [ + ' [ + ' $arr + */ + function uriToPath(array $arr) : string { + if (!isset($arr["a"]) || $arr["b"] !== "foo") { + throw new \InvalidArgumentException("bad"); + } + + return (string) $arr["c"]; + }', + ], + 'combineAfterLoopAssert' => [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' [ + ' 0; + }', + ], + 'assertHasArrayAccess' => [ + ' [ + '> $array + * @return array + */ + function getBar(array $array) : array { + if (isset($array[\'foo\'][\'bar\'])) { + return $array[\'foo\']; + } + + return []; + }', + ], + 'assertHasArrayAccessOnSimpleXMLElement' => [ + 'bar)) {} + }', + ], + 'assertArrayOffsetToTraversable' => [ + ' [ + ' [ + ' [ + ' $arr + * @return non-empty-array + */ + function foo(array $arr) : array { + if (isset($arr["a"])) { + return $arr; + } + + return ["b" => 1]; + }' + ], + 'setArrayConstantOffset' => [ + ' [ + ' $arr + */ + function foo(A $a, array $arr): void { + if (!isset($arr[$a->id])) { + $arr[$a->id] = new B(); + } + $arr[$a->id]->foo(); + }' + ], + 'assertAfterNotEmptyArrayCheck' => [ + ' [ + 'c[$s]) && empty($this->c[$t])) {} + } + }' + ], + 'assertNotEmptyTwiceOnStaticPropertyArray' => [ + ' [ + ' [ + ' [ + ' [ + 'arr[0])) { + return $this->arr[0]; + } + + $this->arr[0] = new stdClass; + return $this->arr[0]; + } + }' + ], + 'assertArrayKeyExistsRefinesType' => [ + ' */ + public const DAYS = [ + 1 => "mon", + 2 => "tue", + 3 => "wed", + 4 => "thu", + 5 => "fri", + 6 => "sat", + 7 => "sun", + ]; + + /** @param key-of $dayNum*/ + private static function doGetDayName(int $dayNum): string { + return self::DAYS[$dayNum]; + } + + /** @throws LogicException */ + public static function getDayName(int $dayNum): string { + if (! array_key_exists($dayNum, self::DAYS)) { + throw new \LogicException(); + } + return self::doGetDayName($dayNum); + } + }' + ], + 'assertPropertiesOfElseStatement' => [ + 'a === "foo") { + } elseif ($obj->b === "bar") { + } else if ($obj->b === "baz") {} + + if ($obj->b === "baz") {} + }' + ], + 'assertPropertiesOfElseifStatement' => [ + 'a === "foo") { + } elseif ($obj->b === "bar") { + } elseif ($obj->b === "baz") {} + + if ($obj->b === "baz") {} + }' + ], + 'assertArrayWithOffset' => [ + ' [ + ' [ + ' [ + 'foo();', + [ + '$a' => 'bool', + ] + ], + 'SKIPPED-assertVarRedefinedInOpWithOr' => [ + 'foo();', + [ + '$a' => 'bool', + ] + ], + 'SKIPPED-assertVarRedefinedInIfWithAnd' => [ + ' [ + ' [ + ' [ + 'foo(); + } + }' + ], + 'assertOnArrayThings' => [ + '> */ + $a = null; + + if (isset($a["b"]) || isset($a["c"])) { + $all_params = ($a["b"] ?? []) + ($a["c"] ?? []); + }' + ] ]; } diff --git a/tests/TypeReconciliation/ReconcilerTest.php b/tests/TypeReconciliation/ReconcilerTest.php new file mode 100644 index 000000000..bdcd9cb9f --- /dev/null +++ b/tests/TypeReconciliation/ReconcilerTest.php @@ -0,0 +1,165 @@ +file_analyzer = new FileAnalyzer($this->project_analyzer, 'somefile.php', 'somefile.php'); + $this->file_analyzer->context = new Context(); + $this->statements_analyzer = new StatementsAnalyzer( + $this->file_analyzer, + new \Psalm\Internal\Provider\NodeDataProvider() + ); + } + + /** + * @dataProvider providerTestReconcilation + * + * @param string $expected + * @param string $type + * @param string $string + * + * @return void + */ + public function testReconcilation($expected, $type, $string) + { + $reconciled = \Psalm\Internal\Type\AssertionReconciler::reconcile( + $type, + Type::parseString($string), + null, + $this->statements_analyzer, + false, + [] + ); + + $this->assertSame( + $expected, + $reconciled->getId() + ); + + if (is_array($reconciled->getTypes())) { + $this->assertContainsOnlyInstancesOf('Psalm\Type\Atomic', $reconciled->getTypes()); + } + } + + /** + * @dataProvider providerTestTypeIsContainedBy + * + * @param string $input + * @param string $container + * + * @return void + */ + public function testTypeIsContainedBy($input, $container) + { + $this->assertTrue( + TypeAnalyzer::isContainedBy( + $this->project_analyzer->getCodebase(), + Type::parseString($input), + Type::parseString($container) + ) + ); + } + + /** + * @return array + */ + public function providerTestReconcilation() + { + return [ + 'notNullWithObject' => ['MyObject', '!null', 'MyObject'], + 'notNullWithObjectPipeNull' => ['MyObject', '!null', 'MyObject|null'], + 'notNullWithMyObjectPipeFalse' => ['MyObject|false', '!null', 'MyObject|false'], + 'notNullWithMixed' => ['mixed', '!null', 'mixed'], + + 'notEmptyWithMyObject' => ['MyObject', '!falsy', 'MyObject'], + 'notEmptyWithMyObjectPipeNull' => ['MyObject', '!falsy', 'MyObject|null'], + 'notEmptyWithMyObjectPipeFalse' => ['MyObject', '!falsy', 'MyObject|false'], + 'notEmptyWithMixed' => ['non-empty-mixed', '!falsy', 'mixed'], + // @todo in the future this should also work + //'notEmptyWithMyObjectFalseTrue' => ['MyObject|true', '!falsy', 'MyObject|bool'], + + 'nullWithMyObjectPipeNull' => ['null', 'null', 'MyObject|null'], + 'nullWithMixed' => ['null', 'null', 'mixed'], + + 'falsyWithMyObject' => ['mixed', 'falsy', 'MyObject'], + 'falsyWithMyObjectPipeFalse' => ['false', 'falsy', 'MyObject|false'], + 'falsyWithMyObjectPipeBool' => ['false', 'falsy', 'MyObject|bool'], + 'falsyWithMixed' => ['empty-mixed', 'falsy', 'mixed'], + 'falsyWithBool' => ['false', 'falsy', 'bool'], + 'falsyWithStringOrNull' => ['null|string()|string(0)', 'falsy', 'string|null'], + 'falsyWithScalarOrNull' => ['empty-scalar', 'falsy', 'scalar'], + + 'notMyObjectWithMyObjectPipeBool' => ['bool', '!MyObject', 'MyObject|bool'], + 'notMyObjectWithMyObjectPipeNull' => ['null', '!MyObject', 'MyObject|null'], + 'notMyObjectWithMyObjectAPipeMyObjectB' => ['MyObjectB', '!MyObjectA', 'MyObjectA|MyObjectB'], + + 'myObjectWithMyObjectPipeBool' => ['MyObject', 'MyObject', 'MyObject|bool'], + 'myObjectWithMyObjectAPipeMyObjectB' => ['MyObjectA', 'MyObjectA', 'MyObjectA|MyObjectB'], + + 'array' => ['array', 'array', 'array|null'], + + '2dArray' => ['array>', 'array', 'array>|null'], + + 'numeric' => ['numeric-string', 'numeric', 'string'], + + 'nullableClassString' => ['null', 'falsy', '?class-string'], + 'mixedOrNullNotFalsy' => ['non-empty-mixed', '!falsy', 'mixed|null'], + 'mixedOrNullFalsy' => ['empty-mixed|null', 'falsy', 'mixed|null'], + 'nullableClassStringFalsy' => ['null', 'falsy', 'class-string|null'], + 'nullableClassStringEqualsNull' => ['null', '=null', 'class-string|null'], + 'nullableClassStringTruthy' => ['class-string', '!falsy', 'class-string|null'], + 'iterableToArray' => ['array', 'array', 'iterable'], + 'iterableToTraversable' => ['Traversable', 'Traversable', 'iterable'], + 'callableToCallableArray' => ['callable-array{0: object|string, 1: string}', 'array', 'callable'], + 'callableOrArrayToCallableArray' => ['array|callable-array{0: object|string, 1: string}', 'array', 'callable|array'], + 'traversableToIntersection' => ['Countable&Traversable', 'Traversable', 'Countable'], + 'iterableWithoutParamsToTraversableWithoutParams' => ['Traversable', '!array', 'iterable'], + 'iterableWithParamsToTraversableWithParams' => ['Traversable', '!array', 'iterable'], + ]; + } + + /** + * @return array + */ + public function providerTestTypeIsContainedBy() + { + return [ + 'arrayContainsWithArrayOfStrings' => ['array', 'array'], + 'arrayContainsWithArrayOfExceptions' => ['array', 'array'], + + 'unionContainsWithstring' => ['string', 'string|false'], + 'unionContainsWithFalse' => ['false', 'string|false'], + 'objectLikeTypeWithPossiblyUndefinedToGeneric' => [ + 'array{0: array{a: string}, 1: array{c: string, e: string}}', + 'array>', + ], + 'objectLikeTypeWithPossiblyUndefinedToEmpty' => [ + 'array', + 'array{a?: string, b?: string}', + ], + ]; + } +} diff --git a/tests/RedundantConditionTest.php b/tests/TypeReconciliation/RedundantConditionTest.php similarity index 99% rename from tests/RedundantConditionTest.php rename to tests/TypeReconciliation/RedundantConditionTest.php index 6b78a6706..d03d9deae 100644 --- a/tests/RedundantConditionTest.php +++ b/tests/TypeReconciliation/RedundantConditionTest.php @@ -1,12 +1,12 @@ ,error_levels?:string[]}> diff --git a/tests/ScopeTest.php b/tests/TypeReconciliation/ScopeTest.php similarity index 98% rename from tests/ScopeTest.php rename to tests/TypeReconciliation/ScopeTest.php index 16e04db9a..f3c9eb353 100644 --- a/tests/ScopeTest.php +++ b/tests/TypeReconciliation/ScopeTest.php @@ -1,12 +1,12 @@ ,error_levels?:string[]}> diff --git a/tests/TypeTest.php b/tests/TypeReconciliation/TypeTest.php similarity index 99% rename from tests/TypeTest.php rename to tests/TypeReconciliation/TypeTest.php index 08afa51eb..7b9a883ed 100644 --- a/tests/TypeTest.php +++ b/tests/TypeReconciliation/TypeTest.php @@ -1,12 +1,12 @@ ,error_levels?:string[]}>