454 lines
16 KiB
PHP
454 lines
16 KiB
PHP
|
<?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()
|
||
|
);
|
||
|
}
|
||
|
}
|