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

Mock readonly classes on PHP 82 #1319

Draft
wants to merge 26 commits into
base: 1.6.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a9159f6
Create readonly-class-abstract.php
ghostwriter Aug 3, 2023
bedd995
Create readonly-class-final.php
ghostwriter Aug 3, 2023
778e48e
Create readonly-class.php
ghostwriter Aug 3, 2023
584168a
Update TargetClassInterface.php
ghostwriter Aug 3, 2023
adb6c95
Update UndefinedTargetClass.php
ghostwriter Aug 3, 2023
50deeea
Update DefinedTargetClass.php
ghostwriter Aug 3, 2023
a7e8c96
Update Php82LanguageFeaturesTest.php
ghostwriter Aug 3, 2023
a10c0b9
Update TargetClassInterface.php
ghostwriter Aug 3, 2023
956f350
Update UndefinedTargetClass.php
ghostwriter Aug 3, 2023
1e07623
Update ClassNamePass.php
ghostwriter Aug 3, 2023
ee64a57
Update .gitattributes
ghostwriter Aug 3, 2023
119c5ae
Delete readonly-class-abstract.php
ghostwriter Aug 3, 2023
a04cf8f
Delete readonly-class-final.php
ghostwriter Aug 3, 2023
6f40af3
Delete readonly-class.php
ghostwriter Aug 3, 2023
abf2279
Create User.php
ghostwriter Aug 3, 2023
ebb17bd
Create User.php
ghostwriter Aug 3, 2023
39a371c
Create ReadOnlyClass.php
ghostwriter Aug 3, 2023
82464a0
Create ReadOnlyClassAbstract.php
ghostwriter Aug 3, 2023
999cc5d
Create ReadonlyClassesMustNotUseAllowDynamicPropertiesAttribute.php
ghostwriter Aug 3, 2023
3cded1b
Create ReadonlyClassesMustNotUseDynamicProperties.php
ghostwriter Aug 3, 2023
0964492
Create ReadonlyClassesMustOnlyContainTypedProperties.php
ghostwriter Aug 3, 2023
a801519
Create ReadOnlyClassFinal.php
ghostwriter Aug 3, 2023
74f578d
Update Php82LanguageFeaturesTest.php
ghostwriter Aug 3, 2023
b714997
readonly subclass must also be declared readonly
ghostwriter Aug 4, 2023
a8f4dbf
Update Bootstrap.php
ghostwriter Aug 4, 2023
a0a71d3
Update tests.yml
ghostwriter Aug 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Expand Up @@ -2,6 +2,7 @@

/.github export-ignore
/docker export-ignore
/fixtures export-ignore
/tests export-ignore
.editorconfig export-ignore
.gitattributes export-ignore
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Expand Up @@ -14,6 +14,7 @@ jobs:
runs-on: ubuntu-latest

strategy:
fail-fast: false
matrix:
php: ['7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
dependencies: ['lowest','highest','locked']
Expand Down
8 changes: 8 additions & 0 deletions fixtures/PHP81/User.php
@@ -0,0 +1,8 @@
<?php

namespace Mockery\Tests\Fixtures\PHP81;

class User {
public readonly int $uid;
public readonly string $username;
}
8 changes: 8 additions & 0 deletions fixtures/PHP82/ReadOnlyClass.php
@@ -0,0 +1,8 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82;

readonly class ReadOnlyClass
{
public string $myValue;
}
7 changes: 7 additions & 0 deletions fixtures/PHP82/ReadOnlyClassAbstract.php
@@ -0,0 +1,7 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82;

abstract readonly class ReadOnlyClassAbstract
{
}
7 changes: 7 additions & 0 deletions fixtures/PHP82/ReadOnlyClassFinal.php
@@ -0,0 +1,7 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82;

final readonly class ReadOnlyClassFinal
{
}
@@ -0,0 +1,12 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82;

/**
* @see https://php.watch/versions/8.2/readonly-classes#AllowDynamicProperties
*/
#[AllowDynamicProperties]
readonly class ReadonlyClassesMustNotUseAllowDynamicPropertiesAttribute
{
public string $test;
}
11 changes: 11 additions & 0 deletions fixtures/PHP82/ReadonlyClassesMustNotUseDynamicProperties.php
@@ -0,0 +1,11 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82;

/**
* @see https://php.watch/versions/8.2/readonly-classes#dynamic-properties
*/
readonly class ReadonlyClassesMustNotUseDynamicProperties
{
public string $test;
}
11 changes: 11 additions & 0 deletions fixtures/PHP82/ReadonlyClassesMustOnlyContainTypedProperties.php
@@ -0,0 +1,11 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82;

/**
* @see https://php.watch/versions/8.2/readonly-classes#typed-only
*/
readonly class ReadonlyClassesMustOnlyContainTypedProperties
{
public $test;

Check failure on line 10 in fixtures/PHP82/ReadonlyClassesMustOnlyContainTypedProperties.php

View workflow job for this annotation

GitHub Actions / PHPUnit on PHP 8.2 with lowest Dependencies

Readonly property Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustOnlyContainTypedProperties::$test must have type

Check failure on line 10 in fixtures/PHP82/ReadonlyClassesMustOnlyContainTypedProperties.php

View workflow job for this annotation

GitHub Actions / PHPUnit on PHP 8.2 with highest Dependencies

Readonly property Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustOnlyContainTypedProperties::$test must have type

Check failure on line 10 in fixtures/PHP82/ReadonlyClassesMustOnlyContainTypedProperties.php

View workflow job for this annotation

GitHub Actions / PHPUnit on PHP 8.2 with locked Dependencies

Readonly property Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustOnlyContainTypedProperties::$test must have type

Check failure on line 10 in fixtures/PHP82/ReadonlyClassesMustOnlyContainTypedProperties.php

View workflow job for this annotation

GitHub Actions / PHPUnit on PHP 8.3 with lowest Dependencies

Readonly property Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustOnlyContainTypedProperties::$test must have type

Check failure on line 10 in fixtures/PHP82/ReadonlyClassesMustOnlyContainTypedProperties.php

View workflow job for this annotation

GitHub Actions / PHPUnit on PHP 8.3 with highest Dependencies

Readonly property Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustOnlyContainTypedProperties::$test must have type

Check failure on line 10 in fixtures/PHP82/ReadonlyClassesMustOnlyContainTypedProperties.php

View workflow job for this annotation

GitHub Actions / PHPUnit on PHP 8.3 with locked Dependencies

Readonly property Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustOnlyContainTypedProperties::$test must have type
}
@@ -0,0 +1,10 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82\Subclass;

/**
* @see https://php.watch/versions/8.2/readonly-classes#subclass-readonly
*/
readonly class ReadonlySubclassMustAlsoBeDeclaredReadonlyChildInvalid extends ReadonlySubclassMustAlsoBeDeclaredReadonlyParentInvalid
{
}
@@ -0,0 +1,10 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82\Subclass;

/**
* @see https://php.watch/versions/8.2/readonly-classes#subclass-readonly
*/
readonly class ReadonlySubclassMustAlsoBeDeclaredReadonlyChildValid extends ReadonlySubclassMustAlsoBeDeclaredReadonlyParentValid
{
}
@@ -0,0 +1,10 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82\Subclass;

/**
* @see https://php.watch/versions/8.2/readonly-classes#subclass-readonly
*/
readonly class ReadonlySubclassMustAlsoBeDeclaredReadonlyParentInvalid
{
}
@@ -0,0 +1,10 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82\Subclass;

/**
* @see https://php.watch/versions/8.2/readonly-classes#subclass-readonly
*/
readonly class ReadonlySubclassMustAlsoBeDeclaredReadonlyParentValid
{
}
8 changes: 8 additions & 0 deletions fixtures/PHP82/User.php
@@ -0,0 +1,8 @@
<?php

namespace Mockery\Tests\Fixtures\PHP82;

readonly class User {
public int $uid;
public string $username;
}
9 changes: 9 additions & 0 deletions library/Mockery/Generator/DefinedTargetClass.php
Expand Up @@ -124,4 +124,13 @@ public function hasInternalAncestor()

return false;
}

public function isReadOnly(): bool
{
if (PHP_VERSION_ID >= 80200) {
return $this->rfc->isReadOnly();
}

return false;
}
}
Expand Up @@ -28,6 +28,23 @@ public function apply($code, MockConfiguration $config)
$code
);

$target = $config->getTargetClass();
if ($target && $target->isReadOnly()) {
// dd($config, $namespace, $className);
$code = str_replace(
'class Mock',
'readonly class Mock',
$code
);

// remove \AllowDynamicProperties trait
$code = str_replace(
'#[\AllowDynamicProperties]',
'',
$code
);
}

$code = str_replace(
'class Mock',
'class ' . $className,
Expand Down
5 changes: 5 additions & 0 deletions library/Mockery/Generator/TargetClassInterface.php
Expand Up @@ -102,4 +102,9 @@ public function implementsInterface($interface);
* @return boolean
*/
public function hasInternalAncestor();

/**
* Returns whether the targetClass is readonly.
*/
public function isReadOnly(): bool;
}
5 changes: 5 additions & 0 deletions library/Mockery/Generator/UndefinedTargetClass.php
Expand Up @@ -88,4 +88,9 @@ public function __toString()
{
return $this->name;
}

public function isReadOnly(): bool
{
return false;
}
}
19 changes: 9 additions & 10 deletions tests/Bootstrap.php
Expand Up @@ -35,15 +35,15 @@ function isAbsolutePath($path)
$root = realpath(dirname(dirname(__FILE__)));
$composerVendorDirectory = getenv('COMPOSER_VENDOR_DIR') ?: 'vendor';

if (! isAbsolutePath($composerVendorDirectory)) {
if (!isAbsolutePath($composerVendorDirectory)) {
$composerVendorDirectory = $root . DIRECTORY_SEPARATOR . $composerVendorDirectory;
}

/**
* Check that composer installation was done
*/
$autoloadPath = $composerVendorDirectory . DIRECTORY_SEPARATOR . 'autoload.php';
if (! file_exists($autoloadPath)) {
if (!file_exists($autoloadPath)) {
throw new Exception(
'Please run "php composer.phar install" in root directory '
. 'to setup unit test dependencies before running the tests'
Expand All @@ -67,17 +67,16 @@ function isAbsolutePath($path)
*/
unset($root, $autoloadPath, $hamcrestPath, $composerVendorDirectory);

$dev = false;

if ($dev) {
$mocksDirectory = __DIR__ . '/_mocks/';
if (! file_exists($mocksDirectory)) {
mkdir($mocksDirectory, 0777, true);
}
$mocksDirectory = __DIR__ . '/_mocks/';
if (!file_exists($mocksDirectory)) {
mkdir($mocksDirectory, 0777, true);
}

Mockery::setLoader(new Mockery\Loader\RequireLoader($mocksDirectory));
Mockery::setLoader(new Mockery\Loader\RequireLoader($mocksDirectory));

function vdd()
if (!function_exists('dd')) {
function dd(): void
{
var_dump(func_get_args());

Expand Down
58 changes: 56 additions & 2 deletions tests/Unit/PHP82/Php82LanguageFeaturesTest.php
Expand Up @@ -5,7 +5,18 @@
use Generator;
use Mockery\Adapter\Phpunit\MockeryTestCase;
use Mockery\Reflector;
use Mockery\Tests\Fixtures\PHP82\Subclass\ReadonlySubclassMustAlsoBeDeclaredReadonlyChildInvalid;
use Mockery\Tests\Fixtures\PHP82\Subclass\ReadonlySubclassMustAlsoBeDeclaredReadonlyChildValid;
use Mockery\Tests\Fixtures\PHP82\ReadOnlyClass;
use Mockery\Tests\Fixtures\PHP82\ReadOnlyClassAbstract;
use Mockery\Tests\Fixtures\PHP82\ReadOnlyClassFinal;
use Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustNotUseAllowDynamicPropertiesAttribute;
use Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustNotUseDynamicProperties;
use Mockery\Tests\Fixtures\PHP82\ReadonlyClassesMustOnlyContainTypedProperties;
use Mockery\Tests\Fixtures\PHP82\User;
use ReflectionClass;
use ReflectionType;
use stdClass;

/**
* @requires PHP 8.2.0-dev
Expand All @@ -18,15 +29,15 @@ class Php82LanguageFeaturesTest extends MockeryTestCase
*/
public function testMockParameterDisjunctiveNormalFormTypes(string $fullyQualifiedClassName): void
{
$expectedReflectionClass = new \ReflectionClass($fullyQualifiedClassName);
$expectedReflectionClass = new ReflectionClass($fullyQualifiedClassName);
$expectedMethod = $expectedReflectionClass->getMethods()[0];
$expectedType = $expectedMethod
->getParameters()[0]
->getType();

$mock = mock($fullyQualifiedClassName);

$reflectionClass = new \ReflectionClass($mock);
$reflectionClass = new ReflectionClass($mock);
$type = $reflectionClass->getMethod($expectedMethod->getName())
->getParameters()[0]
->getType();
Expand Down Expand Up @@ -119,6 +130,49 @@ public function testTypeHintIIterableStdClassString(): void
Reflector::getTypeHint($refParam)
);
}

public function testMockReadOnlyClassAbstract(): void
{
self::assertSame(ReadOnlyClassAbstract::class, mock(ReadOnlyClassAbstract::class));
}

public function testMockReadOnlyClassFinal(): void
{
self::assertSame(ReadOnlyClassFinal::class, mock(ReadOnlyClassFinal::class));
}

public function testMockReadOnlyClass(): void
{
self::assertSame(ReadOnlyClass::class, mock(ReadOnlyClass::class));
}

public function testMockUser(): void
{
self::assertSame(User::class, mock(User::class));
}
public function testReadonlySubclassMustAlsoBeDeclaredReadonlyInvalid(): void
{
self::assertSame(ReadonlySubclassMustAlsoBeDeclaredReadonlyChildInvalid::class, mock(ReadonlySubclassMustAlsoBeDeclaredReadonlyChildInvalid::class));
}
public function testReadonlySubclassMustAlsoBeDeclaredReadonlyValid(): void
{
self::assertSame(ReadonlySubclassMustAlsoBeDeclaredReadonlyChildValid::class, mock(ReadonlySubclassMustAlsoBeDeclaredReadonlyChildValid::class));
}

public function testMockReadonlyClassesMustNotUseAllowDynamicPropertiesAttribute(): void
{
self::assertSame(ReadonlyClassesMustNotUseAllowDynamicPropertiesAttribute::class, mock(ReadonlyClassesMustNotUseAllowDynamicPropertiesAttribute::class));
}

public function testMockReadonlyClassesMustNotUseDynamicProperties(): void
{
self::assertSame(ReadonlyClassesMustNotUseDynamicProperties::class, mock(ReadonlyClassesMustNotUseDynamicProperties::class));
}

public function testMockReadonlyClassesMustOnlyContainTypedProperties(): void
{
self::assertSame(ReadonlyClassesMustOnlyContainTypedProperties::class, mock(ReadonlyClassesMustOnlyContainTypedProperties::class));
}
}

class IterableObject
Expand Down