Async/test/base/DependenciesTest.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()
);
}
}