Implemented proper dependencies.
continuous-integration/drone/push Build is passing Details

Dependencies can now pass output to each other using the DependentTaskHandler.
Also fixed some general problems in the Tasks class, for instance the Executor not starting correctly because of problematic parameters.
Also, SuperVisor now sets the output of a Task using the last output of the task, and not the first.
This commit is contained in:
Abel Hoogeveen 2020-06-05 15:23:21 +02:00
parent fc83a931ae
commit d42e7f23ef
No known key found for this signature in database
GPG Key ID: 96C2234920BF4292
17 changed files with 878 additions and 76 deletions

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
{