Skip to content

Commit

Permalink
added serialization helper and restructured tests
Browse files Browse the repository at this point in the history
  • Loading branch information
matthi4s committed Oct 26, 2023
1 parent 9f34ecc commit b8872f4
Show file tree
Hide file tree
Showing 44 changed files with 486 additions and 59 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ jobs:

name: Run tests on PHP v${{ matrix.php-version }}

env:
TASKMASTER_TEST_TIME_FACTOR: 100

steps:
- name: Checkout code
uses: actions/checkout@v3
Expand Down
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,25 @@ class SynchronizedFieldTask extends \Aternos\Taskmaster\Task\Task

The result of this task is `6` because the `counter` property is synchronized and increased on both sides.

### Serialization in other classes
The [`OnParent`](src/Task/OnParent.php), [`OnChild`](src/Task/OnChild.php) and [`OnBoth`](src/Task/OnBoth.php)
attributes are only available in your [`Task`](src/Task/Task.php) class. If other objects are serialized but
contain properties that should not be serialized, you can use the
[`SerializationTrait`](src/Communication/Serialization/SerializationTrait.php) in your class
and then add the [`Serializable`](src/Communication/Serialization/Serializable.php) or [`NotSerializable`](src/Communication/Serialization/NotSerializable.php)
attributes to your properties.

You can use the [`Serializable`](src/Communication/Serialization/Serializable.php) attribute to mark properties that should be serialized.
When using only the [`Serializable`](src/Communication/Serialization/Serializable.php) attribute, all properties that are not marked with the
[`Serializable`](src/Communication/Serialization/Serializable.php) attribute will be ignored.

You can use the [`NotSerializable`](src/Communication/Serialization/NotSerializable.php) attribute to mark properties that should not be serialized.
When using only the [`NotSerializable`](src/Communication/Serialization/NotSerializable.php) attribute, all properties that are not marked with the
[`NotSerializable`](src/Communication/Serialization/NotSerializable.php) attribute will be serialized.

When using both attributes, all properties **must** be marked with either the [`Serializable`](src/Communication/Serialization/Serializable.php)
or [`NotSerializable`](src/Communication/Serialization/NotSerializable.php) attribute, otherwise an exception will be thrown.

### Handling the result

The `Task::handleResult()` function is called when the task returns a value. It can be used to handle
Expand Down
7 changes: 5 additions & 2 deletions phpunit.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
colors="true"
testdox="true">
<testsuites>
<testsuite name="environment">
<directory>test/Environment/</directory>
<testsuite name="integration">
<directory>test/Integration/</directory>
</testsuite>
<testsuite name="unit">
<directory>test/Unit/</directory>
</testsuite>
</testsuites>
<source>
Expand Down
24 changes: 24 additions & 0 deletions src/Communication/Serialization/NotSerializable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Aternos\Taskmaster\Communication\Serialization;

use Attribute;

/**
* Attribute NotSerializable
*
* This attribute is used to mark properties that should not be serialized.
* When using only the #[NotSerializable] attribute, all properties that are not marked with the
* #[NotSerializable] attribute will be serialized.
*
* When using both attributes, all properties MUST be marked with either the #[Serializable] or #[NotSerializable].
*
* You can use the {@link SerializationTrait} to implement serialization with these attributes.
*
* @package Aternos\Taskmaster\Communication\Serialization
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
class NotSerializable
{

}
24 changes: 24 additions & 0 deletions src/Communication/Serialization/Serializable.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

namespace Aternos\Taskmaster\Communication\Serialization;

use Attribute;

/**
* Attribute Serializable
*
* This attribute is used to mark properties that should be serialized.
* When using only the #[Serializable] attribute, all properties that are not marked with the
* #[Serializable] attribute will be ignored.
*
* When using both attributes, all properties MUST be marked with either the #[Serializable] or #[NotSerializable].
*
* You can use the {@link SerializationTrait} to implement serialization with these attributes.
*
* @package Aternos\Taskmaster\Communication\Serialization
*/
#[Attribute(Attribute::TARGET_PROPERTY)]
class Serializable
{

}
73 changes: 73 additions & 0 deletions src/Communication/Serialization/SerializationTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Aternos\Taskmaster\Communication\Serialization;

use ReflectionClass;

/**
* Trait SerializationTrait
*
* This trait is used to serialize objects using the #[Serializable] and #[NotSerializable] attributes.
*
* You can use the #[Serializable] attribute to mark properties that should be serialized.
* When using only the #[Serializable] attribute, all properties that are not marked with the
* #[Serializable] attribute will be ignored.
*
* You can use the #[NotSerializable] attribute to mark properties that should not be serialized.
* When using only the #[NotSerializable] attribute, all properties that are not marked with the
* #[NotSerializable] attribute will be serialized.
*
* When using both attributes, all properties MUST be marked with either the #[Serializable] or #[NotSerializable]
* attribute, otherwise an exception will be thrown.
*
* @package Aternos\Taskmaster\Communication\Serialization
*/
trait SerializationTrait
{
public function __serialize(): array
{
$unknown = [];
$serializable = [];
$hasNotSerializable = false;
$hasSerializable = false;
$hasUnknown = false;
foreach ((new ReflectionClass($this))->getProperties() as $property) {
if ($property->isStatic()) {
continue;
}

if ($property->getAttributes(NotSerializable::class)) {
$hasNotSerializable = true;
continue;
}

if ($property->getAttributes(Serializable::class)) {
$hasSerializable = true;
if ($property->isInitialized($this)) {
$serializable[$property->getName()] = $property->getValue($this);
}
continue;
}

$hasUnknown = true;
if ($property->isInitialized($this)) {
$unknown[$property->getName()] = $property->getValue($this);
}
}

if ($hasNotSerializable) {
if (!$hasSerializable) {
return $unknown;
}
if ($hasUnknown) {
throw new \LogicException("Found unknown properties (" . implode(", ", array_keys($unknown)) . ") on object using both, #[Serializable] and #[NotSerializable] attributes.");
}
return $serializable;
}

if ($hasSerializable) {
return $serializable;
}
return $unknown;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Exception\PhpError;
use Aternos\Taskmaster\Exception\TaskTimeoutException;
use Aternos\Taskmaster\Taskmaster;
use Aternos\Taskmaster\Test\Task\InterruptableSleepTask;
use Aternos\Taskmaster\Test\Task\SleepTask;
use Aternos\Taskmaster\Test\Task\WarningTask;
use Aternos\Taskmaster\Test\Util\Task\EmptyTask;
use Aternos\Taskmaster\Test\Util\Task\InterruptableSleepTask;
use Aternos\Taskmaster\Test\Util\Task\SleepTask;
use Aternos\Taskmaster\Test\Util\Task\WarningTask;
use Aternos\Taskmaster\Worker\WorkerInterface;

abstract class AsyncWorkerTestCase extends WorkerTestCase
Expand All @@ -22,12 +23,13 @@ protected function createTaskmaster(): void

public function testMultipleTasksRunAtTheSameTime(): void
{
$time = 20_000 * $this->getTimeFactor();
$start = microtime(true);
$this->addTasks(new SleepTask(500000), 3);
$this->addTasks(new SleepTask($time), 3);
$this->taskmaster->wait();
$end = microtime(true);
$time = ($end - $start) * 1000;
$this->assertLessThan(1499, $time);
$time = ($end - $start) * 1_000_000;
$this->assertLessThan($time * 3 - 1, $time);
}

public function testHandleWarning(): void
Expand All @@ -46,8 +48,8 @@ public function testHandleWarning(): void

public function testDefaultTimeout(): void
{
$this->taskmaster->setDefaultTaskTimeout(0.005);
$tasks = $this->addTasks(new InterruptableSleepTask(10000), 3);
$this->taskmaster->setDefaultTaskTimeout(0.005 * $this->getTimeFactor());
$tasks = $this->addTasks(new InterruptableSleepTask(10_000 * $this->getTimeFactor()), 3);
$this->taskmaster->wait();
foreach ($tasks as $task) {
$this->assertInstanceOf(TaskTimeoutException::class, $task->getError());
Expand All @@ -56,15 +58,14 @@ public function testDefaultTimeout(): void

public function testRecoverAfterTimeout(): void
{
$this->taskmaster->setDefaultTaskTimeout(0.05);
$this->addTasks(new InterruptableSleepTask(100000), 3);
$this->addTasks(new InterruptableSleepTask(1000), 3);
$this->taskmaster->setDefaultTaskTimeout(0.005 * $this->getTimeFactor());
$this->addTasks(new InterruptableSleepTask(10_000 * $this->getTimeFactor()), 3);
$this->addTasks(new EmptyTask(), 3);

$counter = 0;
foreach ($this->taskmaster->waitAndHandleTasks() as $task) {
$counter++;
$this->assertInstanceOf(InterruptableSleepTask::class, $task);
if ($task->microseconds === 100000) {
if ($task instanceof InterruptableSleepTask) {
$this->assertInstanceOf(TaskTimeoutException::class, $task->getError());
} else {
$this->assertNull($task->getError());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Exception\PhpFatalErrorException;
use Aternos\Taskmaster\Exception\WorkerFailedException;
use Aternos\Taskmaster\Test\Task\AdditionTask;
use Aternos\Taskmaster\Test\Task\EmptyTask;
use Aternos\Taskmaster\Test\Task\ErrorTask;
use Aternos\Taskmaster\Test\Task\ExitTask;
use Aternos\Taskmaster\Test\Util\Task\AdditionTask;
use Aternos\Taskmaster\Test\Util\Task\ErrorTask;
use Aternos\Taskmaster\Test\Util\Task\ExitTask;

abstract class ExitableAsyncWorkerTestCase extends AsyncWorkerTestCase
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Environment\Fork\ForkWorker;
use Aternos\Taskmaster\Worker\WorkerInterface;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Environment\Process\ProcessWorker;
use Aternos\Taskmaster\Worker\WorkerInterface;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Proxy\ProcessProxy;
use Aternos\Taskmaster\Worker\WorkerInterface;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Proxy\ProcessProxy;
use Aternos\Taskmaster\Worker\WorkerInterface;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Proxy\ProcessProxy;
use Aternos\Taskmaster\Worker\WorkerInterface;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Exception\WorkerFailedException;
use Aternos\Taskmaster\Task\TaskInterface;
use Aternos\Taskmaster\Taskmaster;
use Aternos\Taskmaster\Test\Task\SleepStatusTask;
use Aternos\Taskmaster\Test\Util\Task\SleepStatusTask;

trait ProxiedWorkerTestTrait
{
Expand All @@ -21,7 +21,7 @@ abstract protected function addTasks(TaskInterface $task, int $amount): array;
public function testTasksFailOnProxyDeath(): void
{
/** @var SleepStatusTask $tasks */
$tasks = $this->addTasks(new SleepStatusTask(100000), 3);
$tasks = $this->addTasks(new SleepStatusTask(100_000 * $this->getTimeFactor()), 3);
do {
$this->taskmaster->update();
$runningTasks = 0;
Expand All @@ -43,7 +43,7 @@ public function testTasksFailOnProxyDeath(): void
public function testProxyRestartsAfterFail(): void
{
/** @var SleepStatusTask $tasks */
$tasks = $this->addTasks(new SleepStatusTask(100000), 6);
$tasks = $this->addTasks(new SleepStatusTask(100_000 * $this->getTimeFactor()), 6);
do {
$this->taskmaster->update();
$runningTasks = 0;
Expand All @@ -70,4 +70,6 @@ public function testProxyRestartsAfterFail(): void
abstract public static function assertNull(mixed $actual, string $message = ''): void;

abstract public static function assertInstanceOf(string $expected, mixed $actual, string $message = ''): void;

abstract protected function getTimeFactor(): int;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Environment\Sync\SyncWorker;
use Aternos\Taskmaster\Taskmaster;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?php

namespace Aternos\Taskmaster\Test\Environment;
namespace Aternos\Taskmaster\Test\Integration;

use Aternos\Taskmaster\Environment\Thread\ThreadWorker;
use Aternos\Taskmaster\Worker\WorkerInterface;
Expand Down
Loading

0 comments on commit b8872f4

Please sign in to comment.