Release of RC1 #7

Merged
abelhooge merged 34 commits from 3-features into master 2020-06-07 13:54:20 +00:00
17 changed files with 878 additions and 76 deletions
Showing only changes of commit d42e7f23ef - Show all commits

View File

@ -56,7 +56,7 @@ use FuzeWorks\Logger;
class DependencyConstraint implements Constraint
{
public $dependencies = [];
protected $dependencies = [];
protected $delayTimes = 3;
@ -100,7 +100,6 @@ class DependencyConstraint implements Constraint
// Get dependency
$dependencyTask = $taskStorage->getTaskById($dependency);
// If the dependency task is completed, ignore it and continue to next dependency
if ($dependencyTask->getStatus() === Task::COMPLETED)
continue;
@ -146,6 +145,16 @@ class DependencyConstraint implements Constraint
return time() + $this->delayTimes;
}
/**
* Return a list of dependencies
*
* @return array
*/
public function getDependencies()
{
return $this->dependencies;
}
/**
* Load the tasks library, so that dependencies can get scanned later
*

View File

@ -38,6 +38,15 @@ namespace FuzeWorks\Async;
interface Handler
{
/**
* Gets invoked upon being added to the Task
*
* @param Task $task
* @return mixed
* @throws TasksException
*/
public function init(Task $task);
/**
* Retrieve the parent handler that will first handle this task, before this child Handler
*
@ -55,9 +64,9 @@ interface Handler
/**
* Import the parent output into the child
*
* @param mixed $input
* @param string $input
*/
public function setParentInput($input): void;
public function setParentInput(string $input): void;
/**
* The handler method used to handle this task.
@ -73,9 +82,9 @@ interface Handler
/**
* Any output generated by primaryHandler should be returned here.
*
* @return mixed
* @return string
*/
public function getOutput();
public function getOutput(): string;
/**
* The handler method used after the primaryHandler if so requested
@ -90,8 +99,8 @@ interface Handler
/**
* Any output generated by postHandler should be returned here
*
* @return mixed
* @return string
*/
public function getPostOutput();
public function getPostOutput(): string;
}

View File

@ -0,0 +1,250 @@
<?php
/**
* FuzeWorks Async Library
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2020 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2020, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
namespace FuzeWorks\Async\Handler;
use FuzeWorks\Async\Constraint;
use FuzeWorks\Async\Constraint\DependencyConstraint;
use FuzeWorks\Async\Handler;
use FuzeWorks\Async\Task;
use FuzeWorks\Async\Tasks;
use FuzeWorks\Async\TasksException;
use FuzeWorks\Exception\FactoryException;
use FuzeWorks\Exception\LibraryException;
use FuzeWorks\Factory;
use FuzeWorks\Libraries;
class DependentTaskHandler implements Handler
{
/**
* @var Handler
*/
protected $parentHandler;
/**
* @var string
*/
protected $output;
/**
* @var array
*/
private $dependencyList;
/**
* @var int
*/
private $delayTimes;
/**
* DependentTaskHandler constructor.
*
* To add dependencies, the following array should be supplied:
* $dependencyList: array(string 'taskId', string 'taskId', string 'taskId')
*
* @param array $dependencyList
* @param int $delayTimes Time that a task should be delayed before retrying
*/
public function __construct(array $dependencyList = [], int $delayTimes = 3)
{
$this->dependencyList = $dependencyList;
$this->delayTimes = $delayTimes;
}
/**
* @inheritDoc
*/
public function init(Task $task)
{
if (!empty($this->dependencyList))
$task->addConstraint(new DependencyConstraint($this->dependencyList, $this->delayTimes));
}
/**
* @inheritDoc
*/
public function primaryHandler(Task $task): bool
{
// First find all the dependencies
try {
$dependencies = $this->fetchDependencies($task);
$this->output = json_encode($dependencies);
return true;
} catch (TasksException $e) {
$this->output = 'Failed to fetch dependencies. TasksException: ' . $e->getMessage();
return false;
}
}
/**
* @inheritDoc
*/
public function getOutput(): string
{
return $this->output;
}
/**
* @inheritDoc
*/
public function postHandler(Task $task)
{
// First find all the dependencies
try {
$dependencies = $this->fetchDependencies($task);
$this->output = json_encode($dependencies);
return true;
} catch (TasksException $e) {
$this->output = 'Failed to fetch dependencies. TasksException: ' . $e->getMessage();
return false;
}
}
/**
* @inheritDoc
*/
public function getPostOutput(): string
{
return $this->output;
}
/**
* @inheritDoc
*/
public function getParentHandler(): ?Handler
{
return $this->parentHandler;
}
/**
* @inheritDoc
*/
public function setParentHandler(Handler $parentHandler): void
{
$this->parentHandler = $parentHandler;
}
/**
* @inheritDoc
*/
public function setParentInput(string $input): void
{
// Parent output gets set at this handler's output.
// Only if this class has something to intervene it will override the parent output
// Which should be always... but alas.
$this->output = $input;
}
/**
* @param Task $task
* @return array
* @throws TasksException
*/
protected function fetchDependencies(Task $task): array
{
// When it receives the task, all dependencies should already be handled
// the primary handler will therefore connect with the DependencyConstraint and fetch dependencies
$constraints = $task->getConstraints();
// First prepare a list of dependencies
$dependencies = [];
$dependencyConstraints = [];
foreach ($constraints as $constraint) {
if ($constraint instanceof Constraint)
$dependencyConstraints[] = $constraint;
}
// If no dependencies found, throw exception
if (empty($dependencyConstraints))
return $dependencies;
// Afterwards build a list of dependencies
/** @var DependencyConstraint $constraint */
foreach ($dependencyConstraints as $constraint) {
foreach ($constraint->getDependencies() as $dependency) {
if (!isset($dependencies[$dependency]))
$dependencies[$dependency] = [];
}
}
// Now that all dependencies are determined, fetch all the output
$tasks = $this->loadTasksLib();
$taskStorage = $tasks->getTaskStorage();
foreach ($dependencies as $dependency => $data)
{
// Fetch the task
try {
$dependencyTask = $taskStorage->getTaskById($dependency);
// Then fetch all output
$dependencies[$dependency]['status'] = $dependencyTask->getStatus();
$dependencies[$dependency]['output'] = $dependencyTask->getOutput();
$dependencies[$dependency]['errors'] = $dependencyTask->getErrors();
$dependencies[$dependency]['post'] = $dependencyTask->getPostOutput();
$dependencies[$dependency]['postErrors'] = $dependencyTask->getPostErrors();
} catch (TasksException $e) {
$dependencies[$dependency]['status'] = Task::FAILED;
$dependencies[$dependency]['output'] = null;
$dependencies[$dependency]['errors'] = 'Task not found.';
$dependencies[$dependency]['post'] = null;
$dependencies[$dependency]['postErrors'] = null;
}
}
return $dependencies;
}
/**
* Load the tasks library, so that dependencies can get scanned later
*
* @return Tasks
* @throws TasksException
*/
private function loadTasksLib(): Tasks
{
try {
/** @var Libraries $libraries */
$libraries = Factory::getInstance('libraries');
/** @var Tasks $tasks */
$tasks = $libraries->get('async');
return $tasks;
} catch (FactoryException | LibraryException $e) {
throw new TasksException("Could not constrain task. Async library could not be loaded.");
}
}
}

View File

@ -117,9 +117,8 @@ class ParallelSuperVisor implements SuperVisor
// RUNNING: check if task is still running. If not, set result based on output
elseif ($task->getStatus() === Task::RUNNING)
{
// @todo Find a way to use the latest output
$isRunning = $this->executor->getTaskRunning($task);
$output = $this->taskStorage->readTaskOutput($task, 1);
$output = $this->taskStorage->readTaskOutput($task);
$hasOutput = !is_null($output);
// If nothing is found, the process has crashed and status PFAILED should be set
@ -204,9 +203,8 @@ class ParallelSuperVisor implements SuperVisor
// POST: when a task is currently running in it's postHandler
elseif ($task->getStatus() === Task::POST)
{
// @todo Find a way to use the latest output
$isRunning = $this->executor->getTaskRunning($task);
$output = $this->taskStorage->readPostOutput($task, 1);
$output = $this->taskStorage->readPostOutput($task);
$hasOutput = !is_null($output);
// If a task is not running and has no output, an error has occurred

View File

@ -209,6 +209,9 @@ class Task
throw new TasksException("Could not create Task. Provided arguments are not serializable.");
$this->arguments = $args;
// Init the handler
$this->handler->init($this);
}
/**

View File

@ -237,16 +237,19 @@ class DummyTaskStorage implements TaskStorage
*/
public function readTaskOutput(Task $task, int $attempt = 0): ?array
{
if ($attempt !== 0)
if (!isset($this->taskOutput[$task->getId()]['task']))
return null;
if ($attempt === 0)
$attempt = count($this->taskOutput[$task->getId()]['task']);
if ($attempt === -1)
return $this->taskOutput[$task->getId()]['task'];
else
{
if (isset($this->taskOutput[$task->getId()]['task'][$attempt]))
return $this->taskOutput[$task->getId()]['task'][$attempt];
}
else
{
if (isset($this->taskOutput[$task->getId()]['task']))
return $this->taskOutput[$task->getId()]['task'];
}
return null;
}
@ -256,16 +259,19 @@ class DummyTaskStorage implements TaskStorage
*/
public function readPostOutput(Task $task, int $attempt = 0): ?array
{
if ($attempt !== 0)
if (!isset($this->taskOutput[$task->getId()]['post']))
return null;
if ($attempt === 0)
$attempt = count($this->taskOutput[$task->getId()]['post']);
if ($attempt === -1)
return $this->taskOutput[$task->getId()]['post'];
else
{
if (isset($this->taskOutput[$task->getId()]['post'][$attempt]))
return $this->taskOutput[$task->getId()]['post'][$attempt];
}
else
{
if (isset($this->taskOutput[$task->getId()]['post']))
return $this->taskOutput[$task->getId()]['post'];
}
return null;
}

View File

@ -271,19 +271,12 @@ class RedisTaskStorage implements TaskStorage
// First get the task ID
$taskId = $task->getId();
if ($attempt !== 0)
{
// Check if this output exists
if (!$this->conn->hExists($this->key_prefix . $taskId, 'output' . $attempt))
return null;
// If a nothing in particular is requested, fetch the latest and select that as the attempt
if ($attempt === 0)
$attempt = $this->conn->hGet($this->key_prefix . $taskId, 'taskOutputAttempts');
// Load and convert the data
$data = $this->conn->hGet($this->key_prefix . $taskId, 'output' . $attempt);
$data = unserialize($data);
return ['output' => $data['output'], 'errors' => $data['errors'], 'statusCode' => $data['statusCode']];
}
else
// If -1 is requested, fetch all
if ($attempt === -1)
{
// Get amount of attempts
$totalAttempts = $this->conn->hGet($this->key_prefix . $taskId, 'taskOutputAttempts');
@ -303,9 +296,22 @@ class RedisTaskStorage implements TaskStorage
if (!empty($output))
return $output;
return null;
}
// If a specific one is requested, fetch that one
else
{
// Check if this output exists
if (!$this->conn->hExists($this->key_prefix . $taskId, 'output' . $attempt))
return null;
// Load and convert the data
$data = $this->conn->hGet($this->key_prefix . $taskId, 'output' . $attempt);
$data = unserialize($data);
return ['output' => $data['output'], 'errors' => $data['errors'], 'statusCode' => $data['statusCode']];
}
return null;
}
/**
@ -316,19 +322,12 @@ class RedisTaskStorage implements TaskStorage
// First get the task ID
$taskId = $task->getId();
if ($attempt !== 0)
{
// Check if this output exists
if (!$this->conn->hExists($this->key_prefix . $taskId, 'postOutput' . $attempt))
return null;
// If a nothing in particular is requested, fetch the latest and select that as the attempt
if ($attempt === 0)
$attempt = $this->conn->hGet($this->key_prefix . $taskId, 'taskPostAttempts');
// Load and convert the data
$data = $this->conn->hGet($this->key_prefix . $taskId, 'postOutput' . $attempt);
$data = unserialize($data);
return ['output' => $data['output'], 'errors' => $data['errors'], 'statusCode' => $data['statusCode']];
}
else
// If -1 is requested, fetch all
if ($attempt === -1)
{
// Get amount of attempts
$totalAttempts = $this->conn->hGet($this->key_prefix . $taskId, 'taskPostAttempts');
@ -348,9 +347,22 @@ class RedisTaskStorage implements TaskStorage
if (!empty($output))
return $output;
return null;
}
// If a specific one is requested, fetch that one
else
{
// Check if this output exists
if (!$this->conn->hExists($this->key_prefix . $taskId, 'postOutput' . $attempt))
return null;
// Load and convert the data
$data = $this->conn->hGet($this->key_prefix . $taskId, 'postOutput' . $attempt);
$data = unserialize($data);
return ['output' => $data['output'], 'errors' => $data['errors'], 'statusCode' => $data['statusCode']];
}
return null;
}
/**

View File

@ -94,11 +94,10 @@ class Tasks implements iLibrary
}
/**
* @param string $bootstrapFile
* @return SuperVisor
* @throws TasksException
*/
public function getSuperVisor(string $bootstrapFile): SuperVisor
public function getSuperVisor(): SuperVisor
{
if (isset($this->supervisor))
return $this->supervisor;
@ -116,7 +115,7 @@ class Tasks implements iLibrary
$parameters = isset($cfg[$type]['parameters']) && is_array($cfg[$type]['parameters']) ? $cfg[$type]['parameters'] : [];
// Then add the TaskStorage and Executor to the parameters
array_unshift($parameters, $this->getTaskStorage(), $this->getExecutor($bootstrapFile));
array_unshift($parameters, $this->getTaskStorage(), $this->getExecutor());
// If the type does not exist, throw an exception
if (!class_exists($class, true))
@ -183,11 +182,10 @@ class Tasks implements iLibrary
/**
* Fetch the Executor based on the configured type
*
* @param string $bootstrapFile
* @return Executor
* @throws TasksException
*/
protected function getExecutor(string $bootstrapFile): Executor
protected function getExecutor(): Executor
{
if (isset($this->executor))
return $this->executor;
@ -209,7 +207,7 @@ class Tasks implements iLibrary
throw new TasksException("Could not get Executor. Type of '$class' not found.");
// And load the Executor and test if everything is in order
$object = new $class($bootstrapFile, $parameters);
$object = new $class($parameters);
if (!$object instanceof Executor)
throw new TasksException("Could not get Executor. Type '$class' is not instanceof Executor.");

View File

@ -0,0 +1,453 @@
<?php
/**
* FuzeWorks Async Library
*
* The FuzeWorks PHP FrameWork
*
* Copyright (C) 2013-2020 TechFuze
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @author TechFuze
* @copyright Copyright (c) 2013 - 2020, TechFuze. (http://techfuze.net)
* @license https://opensource.org/licenses/MIT MIT License
*
* @link http://techfuze.net/fuzeworks
* @since Version 1.0.0
*
* @version Version 1.0.0
*/
use FuzeWorks\Async\Constraint\DependencyConstraint;
use FuzeWorks\Async\Handler;
use FuzeWorks\Async\Handler\DependentTaskHandler;
use FuzeWorks\Async\SuperVisor;
use FuzeWorks\Async\Task;
use FuzeWorks\Async\Tasks;
use FuzeWorks\Async\TaskStorage;
use FuzeWorks\Events;
use FuzeWorks\Factory;
use Mock\Handlers\ArgumentedHandler;
use Mock\Handlers\EmptyHandler;
use PHPUnit\Framework\TestCase;
class DependenciesTest extends TestCase
{
/**
* @var Tasks
*/
private $tasks;
/**
* @var TaskStorage
*/
private $taskStorage;
public function setUp(): void
{
// Add TaskStorage
/** @var Tasks $tasks */
$this->tasks = Factory::getInstance('libraries')->get('async');
$this->taskStorage = $this->tasks->getTaskStorage();
$this->taskStorage->reset();
// Reset events
Events::$listeners = [];
}
/* ---------------------------------- Test the dependency constraint ------------------ */
public function testHasConstrainedDeps()
{
// Create the dependent tasks
$depTask1 = new Task('depTask1', new EmptyHandler());
$depTask2 = new Task('depTask2', new EmptyHandler());
// Write those dependencies to TaskStorage
$this->taskStorage->addTask($depTask1);
$this->taskStorage->addTask($depTask2);
// Create the constraint
$constraint = new DependencyConstraint(['depTask1', 'depTask2']);
// And a dummyTask to accompany
$dummyTask = new Task('dependentTask', new EmptyHandler());
$dummyTask->addConstraint($constraint);
// Test that the constraint is the same
$this->assertSame([$constraint], $dummyTask->getConstraints());
// And test the intervention
$this->assertTrue($constraint->intervene($dummyTask));
$this->assertEquals(Task::DELAYED, $constraint->blockCode());
$this->assertEquals(time() + 3, $constraint->delayTime());
}
/**
* @depends testHasConstrainedDeps
*/
public function testDelayTimes()
{
// Create the dependent tasks
$depTask1 = new Task('depTask1', new EmptyHandler());
$depTask2 = new Task('depTask2', new EmptyHandler());
// Write those dependencies to TaskStorage
$this->taskStorage->addTask($depTask1);
$this->taskStorage->addTask($depTask2);
// Create some useless dummy task
$dummyTask = new Task('dependentTask', new EmptyHandler());
// Create the constraints
// Default time
$constraintDef = new DependencyConstraint(['depTask1', 'depTask2']);
// Modified time (30)
$constraintMod1 = new DependencyConstraint(['depTask1', 'depTask2'], 30);
// And another (60)
$constraintMod2 = new DependencyConstraint(['depTask1', 'depTask2'], 60);
// And intervene all of them
$this->assertTrue($constraintDef->intervene($dummyTask));
$this->assertTrue($constraintMod1->intervene($dummyTask));
$this->assertTrue($constraintMod2->intervene($dummyTask));
// And check the results
$this->assertEquals(Task::DELAYED, $constraintDef->blockCode());
$this->assertEquals(Task::DELAYED, $constraintMod1->blockCode());
$this->assertEquals(Task::DELAYED, $constraintMod2->blockCode());
$this->assertEquals(time() + 3, $constraintDef->delayTime());
$this->assertEquals(time() + 30, $constraintMod1->delayTime());
$this->assertEquals(time() + 60, $constraintMod2->delayTime());
}
public function testHasFailedDeps()
{
// Create the dependent tasks
$depTask1 = new Task('depTask1', new EmptyHandler());
$depTask2 = new Task('depTask2', new EmptyHandler());
// And set the first as completed, and second as failed
$depTask1->setStatus(Task::COMPLETED);
$depTask2->setStatus(Task::CANCELLED);
// Write those dependencies to TaskStorage
$this->taskStorage->addTask($depTask1);
$this->taskStorage->addTask($depTask2);
// Create the constraint
$constraint = new DependencyConstraint(['depTask1', 'depTask2']);
// And a dummyTask to accompany
$dummyTask = new Task('dependentTask', new EmptyHandler());
$dummyTask->addConstraint($constraint);
// Test that the constraint is the same
$this->assertSame([$constraint], $dummyTask->getConstraints());
// And test the intervention
$this->assertTrue($constraint->intervene($dummyTask));
$this->assertEquals(Task::CANCELLED, $constraint->blockCode());
$this->assertEquals('Task cancelled due to failed dependency.', $dummyTask->getErrors());
}
public function testHasCompletedDeps()
{
// Create the dependent tasks
$depTask1 = new Task('depTask1', new EmptyHandler());
$depTask2 = new Task('depTask2', new EmptyHandler());
// And set the first as completed, and second as failed
$depTask1->setStatus(Task::COMPLETED);
$depTask2->setStatus(Task::COMPLETED);
// Write those dependencies to TaskStorage
$this->taskStorage->addTask($depTask1);
$this->taskStorage->addTask($depTask2);
// Create the constraint
$constraint = new DependencyConstraint(['depTask1', 'depTask2']);
// And a dummyTask to accompany
$dummyTask = new Task('dependentTask', new EmptyHandler());
$dummyTask->addConstraint($constraint);
// Test that the constraint is the same
$this->assertSame([$constraint], $dummyTask->getConstraints());
// And test the intervention
$this->assertFalse($constraint->intervene($dummyTask));
}
public function testGetDependencies()
{
$constraint = new DependencyConstraint(['someTask1', 'someTask2']);
$this->assertEquals(['someTask1', 'someTask2'], $constraint->getDependencies());
}
/* ---------------------------------- Test the dependent task handler ----------------- */
public function testAddedDependencies()
{
$handler = new DependentTaskHandler(['someTask1', 'someTask2']);
$dummyTask = new Task('someTask', $handler);
// Check that the constraints match expectations
/** @var DependencyConstraint[] $constraints */
$constraints = $dummyTask->getConstraints();
$this->assertInstanceOf(DependencyConstraint::class, $constraints[0]);
// And that the dependencies match
$this->assertEquals(['someTask1', 'someTask2'], $constraints[0]->getDependencies());
}
public function testPassingOutput()
{
// Create the dependent tasks
$depTask1 = new Task('someTask', new EmptyHandler());
$depTask2 = new Task('someTask2', new EmptyHandler());
// Give the dependencies some output
$depTask1->setOutput('First Output', '');
$depTask2->setOutput('Second Output', '');
// Write those to TaskStorage
$this->taskStorage->addTask($depTask1);
$this->taskStorage->addTask($depTask2);
// Create the task
$handler = new DependentTaskHandler(['someTask', 'someTask2']);
// Create a dummy Task
$dummyTask = new Task('someTask3', $handler);
// Assert that all is well
$this->assertTrue($handler->primaryHandler($dummyTask));
// And test the handler's output
$this->assertEquals(json_encode([
'someTask' => [
'status' => Task::PENDING,
'output' => 'First Output',
'errors' => '',
'post' => null,
'postErrors' => null
],
'someTask2' => [
'status' => Task::PENDING,
'output' => 'Second Output',
'errors' => '',
'post' => null,
'postErrors' => null
]
]), $handler->getOutput());
// And test the post handler
$this->assertTrue($handler->postHandler($dummyTask));
$this->assertEquals(json_encode([
'someTask' => [
'status' => Task::PENDING,
'output' => 'First Output',
'errors' => '',
'post' => null,
'postErrors' => null
],
'someTask2' => [
'status' => Task::PENDING,
'output' => 'Second Output',
'errors' => '',
'post' => null,
'postErrors' => null
]
]), $handler->getPostOutput());
}
/**
* @depends testPassingOutput
*/
public function testMissingDependency()
{
// Create the task
$handler = new DependentTaskHandler(['someTask']);
// Create a dummy Task
$dummyTask = new Task('someTask2', $handler);
// Assert that all is well
$this->assertTrue($handler->primaryHandler($dummyTask));
// And test the handler's output
$this->assertEquals(json_encode([
'someTask' => [
'status' => Task::FAILED,
'output' => null,
'errors' => 'Task not found.',
'post' => null,
'postErrors' => null
],
]), $handler->getOutput());
// And test the post handler
$this->assertTrue($handler->postHandler($dummyTask));
$this->assertEquals(json_encode([
'someTask' => [
'status' => Task::FAILED,
'output' => null,
'errors' => 'Task not found.',
'post' => null,
'postErrors' => null
],
]), $handler->getPostOutput());
}
/**
* @depends testPassingOutput
*/
public function testNoDepedencies()
{
// Create the task
$handler = new DependentTaskHandler([]);
// Create a dummy Task
$dummyTask = new Task('someTask', $handler);
// Assert that all is well
$this->assertTrue($handler->primaryHandler($dummyTask));
$this->assertEquals(json_encode([]), $handler->getOutput());
// And test the post handler
$this->assertTrue($handler->postHandler($dummyTask));
$this->assertEquals(json_encode([]), $handler->getPostOutput());
}
public function testParentHandler()
{
// Test pass output
$handler = new DependentTaskHandler([]);
$handler->setParentInput('Passed Input');
$this->assertEquals('Passed Input', $handler->getOutput());
// Test passing a handler
$handler = new DependentTaskHandler([]);
$parentHandler = $this->createMock(Handler::class);
$handler->setParentHandler($parentHandler);
$this->assertSame($parentHandler, $handler->getParentHandler());
}
public function testPassDependencyOutput()
{
// Build all systems for this test
$superVisor = $this->tasks->getSuperVisor();
// Create the dependency
$dependency = new Task('dependency', new ArgumentedHandler(0, 'Prepared Output'));
// Write the task to TaskStorage
$this->taskStorage->addTask($dependency);
// Now create the dependent task
$dependent = new Task('dependent', new DependentTaskHandler(['dependency'], 2));
// And write that task to TaskStorage
$this->taskStorage->addTask($dependent);
// Now we make the SuperVisor cycle, to start the dependency and set the dependent to WAIT
$this->assertEquals(SuperVisor::RUNNING, $superVisor->cycle());
// Assert that everything is running
$this->assertEquals(Task::RUNNING,
$this->taskStorage->getTaskById($dependency->getId())->getStatus()
);
$this->assertEquals(Task::DELAYED,
$this->taskStorage->getTaskById($dependent->getId())->getStatus()
);
// Give the task some time to finish
usleep(500000);
// And re-run the SuperVisor
$this->assertEquals(SuperVisor::RUNNING, $superVisor->cycle());
// Now check the tasks again. Dependency should be finished and have output,
// whereas dependent should still be delayed
$this->assertEquals(Task::SUCCESS,
$this->taskStorage->getTaskById($dependency->getId())->getStatus()
);
$this->assertEquals(Task::DELAYED,
$this->taskStorage->getTaskById($dependent->getId())->getStatus()
);
// Cycle again and see the dependency be completed, and dependent still delayed
$this->assertEquals(SuperVisor::RUNNING, $superVisor->cycle());
$this->assertEquals(Task::COMPLETED,
$this->taskStorage->getTaskById($dependency->getId())->getStatus()
);
$this->assertEquals(Task::DELAYED,
$this->taskStorage->getTaskById($dependent->getId())->getStatus()
);
// Also check that output is correct
$this->assertEquals('Prepared Output',
$this->taskStorage->getTaskById($dependency->getId())->getOutput()
);
// Now wait long enough for the delay to be finished
usleep(2500000);
// Now cycle again, and expect the task to be pending
$this->assertEquals(SuperVisor::RUNNING, $superVisor->cycle());
$this->assertEquals(Task::PENDING,
$this->taskStorage->getTaskById($dependent->getId())->getStatus()
);
// Cycle again and expect it to be running
$this->assertEquals(SuperVisor::RUNNING, $superVisor->cycle());
$this->assertEquals(Task::RUNNING,
$this->taskStorage->getTaskById($dependent->getId())->getStatus()
);
// Give the task some time to finish
usleep(500000);
// And cycle again and expect the task to have succeeded
$this->assertEquals(SuperVisor::RUNNING, $superVisor->cycle());
$this->assertEquals(Task::SUCCESS,
$this->taskStorage->getTaskById($dependent->getId())->getStatus()
);
// Cycle again and expect the task to have completed, and test its output
$this->assertEquals(SuperVisor::FINISHED, $superVisor->cycle());
$this->assertEquals(Task::COMPLETED,
$this->taskStorage->getTaskById($dependent->getId())->getStatus()
);
$this->assertEquals(
json_encode(['dependency' => [
'status' => Task::COMPLETED,
'output' => 'Prepared Output',
'errors' => '',
'post' => null,
'postErrors' => null
]]),
$this->taskStorage->getTaskById($dependent->getId())->getOutput()
);
}
}

View File

@ -338,11 +338,14 @@ class TaskStorageTest extends TestCase
$this->assertTrue($this->taskStorage->writeTaskOutput($dummyTask, 'output2', 'errors2', 0));
// Then try to read all the output
$output = $this->taskStorage->readTaskOutput($dummyTask);
$output = $this->taskStorage->readTaskOutput($dummyTask, -1);
$this->assertEquals([
['output' => 'output1', 'errors' => 'errors1', 'statusCode' => 0],
['output' => 'output2', 'errors' => 'errors2', 'statusCode' => 0]
], $output);
// Then try and read the latest
$this->assertEquals(['output' => 'output2', 'errors' => 'errors2', 'statusCode' => 0], $this->taskStorage->readTaskOutput($dummyTask));
}
/**
@ -392,11 +395,16 @@ class TaskStorageTest extends TestCase
// Attempt to load the default output
$output = $this->taskStorage->readTaskOutput($dummyTask);
$this->assertEquals('output2', $output['output']);
$this->assertEquals('errors2', $output['errors']);
$this->assertEquals(102, $output['statusCode']);
// And to load all
$this->assertEquals([
['output' => 'output0', 'errors' => 'errors0', 'statusCode' => 100],
['output' => 'output1', 'errors' => 'errors1', 'statusCode' => 101],
['output' => 'output2', 'errors' => 'errors2', 'statusCode' => 102]
], $output);
], $this->taskStorage->readTaskOutput($dummyTask, -1));
}
/**
@ -446,11 +454,13 @@ class TaskStorageTest extends TestCase
$this->assertTrue($this->taskStorage->writePostOutput($dummyTask, 'output2', 'errors2', 0));
// Then try to read all the output
$output = $this->taskStorage->readPostOutput($dummyTask);
$output = $this->taskStorage->readPostOutput($dummyTask, -1);
$this->assertEquals([
['output' => 'output1', 'errors' => 'errors1', 'statusCode' => 0],
['output' => 'output2', 'errors' => 'errors2', 'statusCode' => 0]
], $output);
$this->assertEquals(['output' => 'output2', 'errors' => 'errors2', 'statusCode' => 0], $this->taskStorage->readPostOutput($dummyTask));
}
/**
@ -488,11 +498,16 @@ class TaskStorageTest extends TestCase
// Attempt to load the default output
$output = $this->taskStorage->readPostOutput($dummyTask);
$this->assertEquals('output2', $output['output']);
$this->assertEquals('errors2', $output['errors']);
$this->assertEquals(102, $output['statusCode']);
// And to load all
$this->assertEquals([
['output' => 'output0', 'errors' => 'errors0', 'statusCode' => 100],
['output' => 'output1', 'errors' => 'errors1', 'statusCode' => 101],
['output' => 'output2', 'errors' => 'errors2', 'statusCode' => 102]
], $output);
], $this->taskStorage->readPostOutput($dummyTask, -1));
}
/**

View File

@ -35,6 +35,7 @@
*/
use FuzeWorks\Async\Constraint;
use FuzeWorks\Async\Handler;
use FuzeWorks\Async\Task;
use FuzeWorks\Async\TasksException;
use Mock\Handlers\EmptyHandler;
@ -111,6 +112,19 @@ class TaskTest extends TestCase
$this->assertEquals([$stub], $dummyTask->getConstraints());
}
/**
* @depends testBaseVariables
*/
public function testInitHandler()
{
$mockHandler = $this->createMock(Handler::class);
$mockHandler->expects($this->once())->method('init')
->with($this->callback(function($subject){return $subject instanceof Task;}));
// Then create a class
new Task('testInitHandler', $mockHandler);
}
/**
* @depends testBaseVariables
*/

View File

@ -83,8 +83,9 @@ return array(
'ShellExecutor' => [
'parameters' => [
'workerFile' => Core::getEnv('EXECUTOR_SHELL_WORKER',
dirname(__FILE__) . DS . 'bin' . DS . 'worker'),
'bootstrapFile' => Core::getEnv('EXECUTOR_SHELL_BOOTSTRAP', 'unknown')
dirname(__FILE__, 2) . DS . 'bin' . DS . 'worker'),
'bootstrapFile' => Core::getEnv('EXECUTOR_SHELL_BOOTSTRAP',
dirname(__FILE__) . DS . 'bootstrap.php')
]
]
]

View File

@ -37,6 +37,7 @@
namespace Mock\Handlers;
use FuzeWorks\Async\Handler;
use FuzeWorks\Async\Task;
use FuzeWorks\Async\TasksException;
class ArgumentedHandler implements Handler
{
@ -50,6 +51,13 @@ class ArgumentedHandler implements Handler
$this->output = $output;
}
/**
* @inheritDoc
*/
public function init(Task $task)
{
}
/**
* @inheritDoc
*/
@ -62,7 +70,7 @@ class ArgumentedHandler implements Handler
/**
* @inheritDoc
*/
public function getOutput()
public function getOutput(): string
{
return $this->output;
}
@ -79,7 +87,7 @@ class ArgumentedHandler implements Handler
/**
* @inheritDoc
*/
public function getPostOutput()
public function getPostOutput(): string
{
return $this->output;
}
@ -95,7 +103,7 @@ class ArgumentedHandler implements Handler
/**
* @inheritDoc
*/
public function setParentInput($input): void
public function setParentInput(string $input): void
{
}

View File

@ -41,6 +41,13 @@ use FuzeWorks\Async\Task;
class EmptyHandler implements Handler
{
/**
* @inheritDoc
*/
public function init(Task $task)
{
}
/**
* @inheritDoc
*/
@ -52,7 +59,7 @@ class EmptyHandler implements Handler
/**
* @inheritDoc
*/
public function getOutput()
public function getOutput(): string
{
}
@ -66,7 +73,7 @@ class EmptyHandler implements Handler
/**
* @inheritDoc
*/
public function getPostOutput()
public function getPostOutput(): string
{
}
@ -81,7 +88,7 @@ class EmptyHandler implements Handler
/**
* @inheritDoc
*/
public function setParentInput($input): void
public function setParentInput(string $input): void
{
}

View File

@ -41,6 +41,13 @@ use FuzeWorks\Async\Task;
class TestStartAndReadTasksHandler implements Handler
{
/**
* @inheritDoc
*/
public function init(Task $task)
{
}
/**
* @inheritDoc
*/
@ -53,7 +60,7 @@ class TestStartAndReadTasksHandler implements Handler
/**
* @inheritDoc
*/
public function getOutput()
public function getOutput(): string
{
return "Valid Output";
}
@ -69,7 +76,7 @@ class TestStartAndReadTasksHandler implements Handler
/**
* @inheritDoc
*/
public function getPostOutput()
public function getPostOutput(): string
{
}
@ -85,7 +92,7 @@ class TestStartAndReadTasksHandler implements Handler
/**
* @inheritDoc
*/
public function setParentInput($input): void
public function setParentInput(string $input): void
{
}

View File

@ -41,6 +41,13 @@ use FuzeWorks\Async\Task;
class TestStopTaskHandler implements Handler
{
/**
* @inheritDoc
*/
public function init(Task $task)
{
}
/**
* @inheritDoc
*/
@ -53,7 +60,7 @@ class TestStopTaskHandler implements Handler
/**
* @inheritDoc
*/
public function getOutput()
public function getOutput(): string
{
return "Valid Output";
}
@ -68,7 +75,7 @@ class TestStopTaskHandler implements Handler
/**
* @inheritDoc
*/
public function getPostOutput()
public function getPostOutput(): string
{
}
@ -83,7 +90,7 @@ class TestStopTaskHandler implements Handler
/**
* @inheritDoc
*/
public function setParentInput($input): void
public function setParentInput(string $input): void
{
}

View File

@ -45,6 +45,11 @@ use FuzeWorks\Events;
use Mock\Handlers\ArgumentedHandler;
use PHPUnit\Framework\TestCase;
/**
* Class ParallelSuperVisorTest
*
* @todo Add test that latest output is added to Task, and not just 'any' output
*/
class ParallelSuperVisorTest extends TestCase
{