Async/test/base/DependenciesTest.php
Abel Hoogeveen d42e7f23ef
All checks were successful
continuous-integration/drone/push Build is passing
Implemented proper dependencies.
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.
2020-06-05 15:23:21 +02:00

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()
);
}
}