Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: WyriHaximus/php-async-test-utilities
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 6.2.0
Choose a base ref
...
head repository: WyriHaximus/php-async-test-utilities
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 7.0.0
Choose a head ref
  • 8 commits
  • 9 files changed
  • 1 contributor

Commits on Apr 23, 2023

  1. Run all tests in a fiber

    Since all tests are executed inside a fiber, there is a default timeout of `30` seconds. To lower or raise that timeout
    this package comes with a `TimeOut` attribute. It can be set on the class and method level. When set on both the method level it takes priority over the class level.
    
    ```php
    <?php
    
    declare(strict_types=1);
    
    namespace WyriHaximus\Tests\AsyncTestUtilities;
    
    use React\EventLoop\Loop;
    use WyriHaximus\AsyncTestUtilities\AsyncTestCase;
    use WyriHaximus\AsyncTestUtilities\TimeOut;
    
    use function React\Async\async;
    use function React\Async\await;
    use function React\Promise\resolve;
    use function React\Promise\Timer\sleep;
    use function time;
    
    #[TimeOut(0.3)]
    final class AsyncTestCaseTest extends AsyncTestCase
    {
        #[TimeOut(1)]
        public function testAllTestsAreRanInAFiber(): void
        {
            self::expectOutputString('ab');
    
            Loop::futureTick(async(static function (): void {
                echo 'a';
            }));
    
            await(sleep(1));
    
            echo 'b';
        }
    
        public function testExpectCallableExactly(): void
        {
            $callable = $this->expectCallableExactly(3);
    
            Loop::futureTick($callable);
            Loop::futureTick($callable);
            Loop::futureTick($callable);
        }
    
        public function testExpectCallableOnce(): void
        {
            Loop::futureTick($this->expectCallableOnce());
        }
    }
    ```
    WyriHaximus committed Apr 23, 2023
    Copy the full SHA
    5830b95 View commit details
  2. Merge pull request #194 from WyriHaximus/fun-all-tests-in-a-fiber

    Run all tests in a fiber
    WyriHaximus authored Apr 23, 2023
    Copy the full SHA
    f19172b View commit details
  3. Copy the full SHA
    161d8f5 View commit details
  4. Merge pull request #195 from WyriHaximus/drop-deprecated-await-methods

    Drop deprecated await functions
    WyriHaximus authored Apr 23, 2023
    Copy the full SHA
    18e28a0 View commit details
  5. Add Deprecations Label

    WyriHaximus authored Apr 23, 2023
    Copy the full SHA
    02d6172 View commit details
  6. Merge pull request #196 from WyriHaximus/Add-Deprecations-Label

    Add Deprecations Label
    WyriHaximus authored Apr 23, 2023
    Copy the full SHA
    4b34d28 View commit details
  7. Copy the full SHA
    6bad002 View commit details
  8. Merge pull request #197 from WyriHaximus/mark-expectCallable-methods-…

    …deprecated
    
    Mark expectCallable* methods deprecated
    WyriHaximus authored Apr 23, 2023
    Copy the full SHA
    52647a9 View commit details
Showing with 259 additions and 58 deletions.
  1. +3 −0 .editorconfig
  2. +2 −0 .github/settings.yml
  3. +56 −2 README.md
  4. +1 −0 composer.json
  5. +84 −1 composer.lock
  6. +7 −4 etc/qa/phpstan.neon
  7. +76 −36 src/AsyncTestCase.php
  8. +16 −0 src/TimeOut.php
  9. +14 −15 tests/AsyncTestCaseTest.php
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -18,3 +18,6 @@ indent_size = 2

[Makefile]
indent_style = tab

[*.neon]
indent_style = tab
2 changes: 2 additions & 0 deletions .github/settings.yml
Original file line number Diff line number Diff line change
@@ -10,6 +10,8 @@ repository:

# Labels: define labels for Issues and Pull Requests
labels:
- name: "Deprecations 👋"
color: ff7700
- name: "Dependencies 📦"
color: 0025ff
description: "Pull requests that update a dependency file"
58 changes: 56 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -17,13 +17,67 @@ composer require wyrihaximus/async-test-utilities

# Usage

Any test file can extend `WyriHaximus\AsyncTestUtilities\TestCase` and it comes with some goodies such as random namespaces and random directories to use for file storage related tests.
Any test file can extend `WyriHaximus\AsyncTestUtilities\TestCase` and it comes with some goodies such as random
namespaces and random directories to use for file storage related tests.

Since all tests are executed inside a fiber, there is a default timeout of `30` seconds. To lower or raise that timeout
this package comes with a `TimeOut` attribute. It can be set on the class and method level. When set on both the method level it takes priority over the class level.

```php
<?php

declare(strict_types=1);

namespace WyriHaximus\Tests\AsyncTestUtilities;

use React\EventLoop\Loop;
use WyriHaximus\AsyncTestUtilities\AsyncTestCase;
use WyriHaximus\AsyncTestUtilities\TimeOut;

use function React\Async\async;
use function React\Async\await;
use function React\Promise\resolve;
use function React\Promise\Timer\sleep;
use function time;

#[TimeOut(0.3)]
final class AsyncTestCaseTest extends AsyncTestCase
{
#[TimeOut(1)]
public function testAllTestsAreRanInAFiber(): void
{
self::expectOutputString('ab');

Loop::futureTick(async(static function (): void {
echo 'a';
}));

await(sleep(1));

echo 'b';
}

public function testExpectCallableExactly(): void
{
$callable = $this->expectCallableExactly(3);

Loop::futureTick($callable);
Loop::futureTick($callable);
Loop::futureTick($callable);
}

public function testExpectCallableOnce(): void
{
Loop::futureTick($this->expectCallableOnce());
}
}
```

# License

The MIT License (MIT)

Copyright (c) 2021 Cees-Jan Kiewiet
Copyright (c) 2023 Cees-Jan Kiewiet

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
"wyrihaximus/test-utilities": "^5.5.0 || ^6"
},
"require-dev": {
"react/promise-timer": "^1.9",
"wyrihaximus/iterator-or-array-to-array": "^1.2"
},
"autoload": {
85 changes: 84 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 7 additions & 4 deletions etc/qa/phpstan.neon
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
parameters:
excludes_analyse:
excludePaths:
- tests/bootstrap.php
ignoreErrors:
- '#Parameter \#1 \$name of method PHPUnit\\Framework\\TestCase::setName\(\) expects string, string\|null given.#'
- '#Trying to invoke array\{\$this\(WyriHaximus\\AsyncTestUtilities\\AsyncTestCase\), string\|null\} but it might not be a callable.#'
- '#Method WyriHaximus\\AsyncTestUtilities\\CallableStub::__invoke\(\) is not final, but since the containing class is abstract, it should be.#'
- '#Call to deprecated method await\(\) of class WyriHaximus\\AsyncTestUtilities\\AsyncTestCase#'
- '#Call to deprecated method awaitAll\(\) of class WyriHaximus\\AsyncTestUtilities\\AsyncTestCase#'
- '#Call to deprecated method awaitAny\(\) of class WyriHaximus\\AsyncTestUtilities\\AsyncTestCase#'
- '#Parameter \#1 \$name of method ReflectionClass\<\$this\(WyriHaximus\\AsyncTestUtilities\\AsyncTestCase\)\>::getMethod\(\) expects string, string\|null given.#'
- '#Call to an undefined method React\\Promise\\PromiseInterface\<mixed\>::always\(\).#'
- '#Call to deprecated method expectCallableExactly\(\)#'
- '#Call to deprecated method expectCallableOnce\(\)#'
ergebnis:
classesAllowedToBeExtended:
- WyriHaximus\AsyncTestUtilities\AsyncTestCase
112 changes: 76 additions & 36 deletions src/AsyncTestCase.php
Original file line number Diff line number Diff line change
@@ -5,63 +5,38 @@
namespace WyriHaximus\AsyncTestUtilities;

use PHPUnit\Framework\MockObject\Rule\InvokedCount;
use React\Promise\PromiseInterface;
use React\EventLoop\Loop;
use ReflectionClass;
use WyriHaximus\TestUtilities\TestCase;

use function React\Async\async;
use function React\Async\await;
use function React\Promise\all;
use function React\Promise\any;

abstract class AsyncTestCase extends TestCase
{
private const INVOKE_ARRAY = ['__invoke'];

/**
* @return mixed returns whatever the promise resolves to
*
* @psalm-suppress MissingReturnType
*
* @codingStandardsIgnoreStart
* @deprecated Use \React\Async\await directly
*/
final protected function await(PromiseInterface $promise): mixed
{
return await($promise);
}

/**
* @return array<mixed>
* @deprecated Use \React\Async\await and \React\Promise\all directly
*/
final protected function awaitAll(PromiseInterface ...$promises): array
{
/** @var array<mixed> */
return await(all($promises));
}
private ?string $realTestName = null;

/**
* @return mixed
*
* @psalm-suppress MissingReturnType
*
* @codingStandardsIgnoreStart
* @deprecated Use \React\Async\await and \React\Promise\any directly
* @deprecated With the move to fibers there is no need for these rarely used methods anymore. (Unless proven otherwise of course.)
*/
final protected function awaitAny(PromiseInterface ...$promises): mixed
{
return await(any($promises));
}

final protected function expectCallableExactly(int $amount): callable
{
return $this->getCallableMock(self::exactly($amount));
}

/**
* @deprecated With the move to fibers there is no need for these rarely used methods anymore. (Unless proven otherwise of course.)
*/
final protected function expectCallableOnce(): callable
{
return $this->getCallableMock(self::once());
}

/**
* @deprecated With the move to fibers there is no need for these rarely used methods anymore. (Unless proven otherwise of course.)
*/
final protected function expectCallableNever(): callable
{
return $this->getCallableMock(self::never());
@@ -79,4 +54,69 @@ private function getCallableMock(InvokedCount $invokedCount): callable
/** @psalm-suppress InvalidReturnStatement */
return $mock;
}

/**
* @codeCoverageIgnore Invoked before code coverage data is being collected.
*/
final public function setName(string $name): void
{
/**
* @psalm-suppress InternalMethod
*/
parent::setName($name);
$this->realTestName = $name;
}

/** @internal */
final protected function runAsyncTest(mixed ...$args): mixed
{
/**
* @psalm-suppress InternalMethod
* @psalm-suppress PossiblyNullArgument
*/
parent::setName($this->realTestName);
$timeout = 30;
$reflectionClass = new ReflectionClass($this::class);
foreach ($reflectionClass->getAttributes() as $classAttribute) {
$classTimeout = $classAttribute->newInstance();
if (! ($classTimeout instanceof TimeOut)) {
continue;
}

$timeout = $classTimeout->timeout;
}

/**
* @psalm-suppress InternalMethod
* @psalm-suppress PossiblyNullArgument
*/
foreach ($reflectionClass->getMethod($this->realTestName)->getAttributes() as $methodAttribute) {
$methodTimeout = $methodAttribute->newInstance();
if (! ($methodTimeout instanceof TimeOut)) {
continue;
}

$timeout = $methodTimeout->timeout;
}

$timeout = Loop::addTimer($timeout, static fn () => Loop::stop());

/**
* @psalm-suppress MixedArgument
* @psalm-suppress UndefinedInterfaceMethod
*/
return await(async(
fn (): mixed => ([$this, $this->realTestName])(...$args),
)()->always(static fn () => Loop::cancelTimer($timeout)));
}

final protected function runTest(): mixed
{
/**
* @psalm-suppress InternalMethod
*/
parent::setName('runAsyncTest');

return parent::runTest();
}
}
16 changes: 16 additions & 0 deletions src/TimeOut.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace WyriHaximus\AsyncTestUtilities;

use Attribute;

#[Attribute(Attribute::TARGET_METHOD | Attribute::TARGET_CLASS)]
final readonly class TimeOut
{
public function __construct(
public int|float $timeout,
) {
}
}
Loading