From 1ab2d4fcc422f09df8fb5c3ae31d92e39be09650 Mon Sep 17 00:00:00 2001 From: Matthieu Napoli <matthieu@mnapoli.fr> Date: Sat, 3 Jan 2015 12:24:53 +1300 Subject: [PATCH] Moved Piwik\TaskScheduler to Piwik\Scheduler\Scheduler --- CHANGELOG.md | 4 +- core/ScheduledTask.php | 3 + core/Scheduler/Scheduler.php | 180 ++++++++++ core/Scheduler/Task.php | 2 +- core/Scheduler/TaskLoader.php | 40 +++ core/TaskScheduler.php | 123 +------ .../PHPUnit/Unit/Scheduler/SchedulerTest.php | 198 +++++++++++ .../PHPUnit/Unit/Scheduler/TimetableTest.php | 154 ++++++++ tests/PHPUnit/Unit/TaskSchedulerTest.php | 330 ------------------ 9 files changed, 594 insertions(+), 440 deletions(-) create mode 100644 core/Scheduler/Scheduler.php create mode 100644 core/Scheduler/TaskLoader.php create mode 100644 tests/PHPUnit/Unit/Scheduler/SchedulerTest.php create mode 100644 tests/PHPUnit/Unit/Scheduler/TimetableTest.php delete mode 100644 tests/PHPUnit/Unit/TaskSchedulerTest.php diff --git a/CHANGELOG.md b/CHANGELOG.md index bf4d17da85..37ca98dd8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,9 @@ This is a changelog for Piwik platform developers. All changes for our HTTP API' * `Piwik\Metrics\Formatter::getCurrencyList()`: use `CurrencyDataProvider::getCurrencyList()` instead * The `Piwik\Translate` class has been deprecated in favor of `Piwik\Translation\Translator`. * The `core:plugin` console has been deprecated in favor of the new `plugin:list`, `plugin:activate` and `plugin:deactivate` commands -* The `Piwik\ScheduledTask` class has been deprecated in favor of `Piwik\Scheduler\Task` +* The following classes have been deprecated: + * `Piwik\TaskScheduler`: use `Piwik\Scheduler\Scheduler` instead + * `Piwik\ScheduledTask`: use `Piwik\Scheduler\Task` instead ## Piwik 2.10.0 diff --git a/core/ScheduledTask.php b/core/ScheduledTask.php index 96144328ec..dd4063ef88 100644 --- a/core/ScheduledTask.php +++ b/core/ScheduledTask.php @@ -18,6 +18,9 @@ use Piwik\Scheduler\Task; * See the {@link TaskScheduler} docs to learn more about scheduled tasks. * * @api + * + * @deprecated Use Piwik\Scheduler\Task instead + * @see \Piwik\Scheduler\Task */ class ScheduledTask extends Task { diff --git a/core/Scheduler/Scheduler.php b/core/Scheduler/Scheduler.php new file mode 100644 index 0000000000..c4f3d176c1 --- /dev/null +++ b/core/Scheduler/Scheduler.php @@ -0,0 +1,180 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Scheduler; + +use Exception; +use Piwik\Timer; + +/** + * Schedules task execution. + * + * A scheduled task is a callback that should be executed every so often (such as daily, + * weekly, monthly, etc.). They are registered in the **Scheduler** through the + * {@hook TaskScheduler.getScheduledTasks} event. + * + * Tasks are executed when the cron core:archive command is executed. + * + * ### Examples + * + * **Scheduling a task** + * + * // event handler for Scheduler.getScheduledTasks event + * public function getScheduledTasks(&$tasks) + * { + * $tasks[] = new Task( + * 'Piwik\Plugins\CorePluginsAdmin\MarketplaceApiClient', + * 'clearAllCacheEntries', + * null, + * Schedule::factory('daily'), + * Task::LOWEST_PRIORITY + * ); + * } + * + * **Executing all pending tasks** + * + * $results = $scheduler->run(); + * $task1Result = $results[0]; + * $task1Name = $task1Result['task']; + * $task1Output = $task1Result['output']; + * + * echo "Executed task '$task1Name'. Task output:\n$task1Output"; + */ +class Scheduler +{ + /** + * Is the scheduler running any task. + * @var bool + */ + private $isRunning = false; + + /** + * @var Timetable + */ + private $timetable; + + /** + * @var TaskLoader + */ + private $loader; + + public function __construct(TaskLoader $loader) + { + $this->timetable = new Timetable(); + $this->loader = $loader; + } + + /** + * Executes tasks that are scheduled to run, then reschedules them. + * + * @return array An array describing the results of scheduled task execution. Each element + * in the array will have the following format: + * + * ``` + * array( + * 'task' => 'task name', + * 'output' => '... task output ...' + * ) + * ``` + */ + public function run() + { + $tasks = $this->loader->loadTasks(); + + // remove from timetable tasks that are not active anymore + $this->timetable->removeInactiveTasks($tasks); + + // for every priority level, starting with the highest and concluding with the lowest + $executionResults = array(); + for ($priority = Task::HIGHEST_PRIORITY; + $priority <= Task::LOWEST_PRIORITY; + ++$priority) { + // loop through each task + foreach ($tasks as $task) { + // if the task does not have the current priority level, don't execute it yet + if ($task->getPriority() != $priority) { + continue; + } + + $taskName = $task->getName(); + $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName); + + if ($this->timetable->taskShouldBeRescheduled($taskName)) { + $this->timetable->rescheduleTask($task); + } + + if ($shouldExecuteTask) { + $this->isRunning = true; + $message = $this->executeTask($task); + $this->isRunning = false; + + $executionResults[] = array('task' => $taskName, 'output' => $message); + } + } + } + + return $executionResults; + } + + /** + * Determines a task's scheduled time and persists it, overwriting the previous scheduled time. + * + * Call this method if your task's scheduled time has changed due to, for example, an option that + * was changed. + * + * @param Task $task Describes the scheduled task being rescheduled. + * @api + */ + public function rescheduleTask(Task $task) + { + $this->timetable->rescheduleTask($task); + } + + /** + * Returns true if the scheduler is currently running a task. + * + * @return bool + */ + public function isRunning() + { + return $this->isRunning; + } + + /** + * Return the next scheduled time given the class and method names of a scheduled task. + * + * @param string $className The name of the class that contains the scheduled task method. + * @param string $methodName The name of the scheduled task method. + * @param string|null $methodParameter Optional method parameter. + * @return mixed int|bool The time in miliseconds when the scheduled task will be executed + * next or false if it is not scheduled to run. + */ + public function getScheduledTimeForMethod($className, $methodName, $methodParameter = null) + { + return $this->timetable->getScheduledTimeForMethod($className, $methodName, $methodParameter); + } + + /** + * Executes the given task + * + * @param Task $task + * @return string + */ + private function executeTask($task) + { + try { + $timer = new Timer(); + call_user_func(array($task->getObjectInstance(), $task->getMethodName()), $task->getMethodParameter()); + $message = $timer->__toString(); + } catch (Exception $e) { + $message = 'ERROR: ' . $e->getMessage(); + } + + return $message; + } +} diff --git a/core/Scheduler/Task.php b/core/Scheduler/Task.php index e74facb399..f3c4381a22 100644 --- a/core/Scheduler/Task.php +++ b/core/Scheduler/Task.php @@ -52,7 +52,7 @@ class Task /** * The scheduled time policy - * @var \Piwik\Scheduler\Schedule\Schedule + * @var Schedule */ private $scheduledTime; diff --git a/core/Scheduler/TaskLoader.php b/core/Scheduler/TaskLoader.php new file mode 100644 index 0000000000..44ec80c54b --- /dev/null +++ b/core/Scheduler/TaskLoader.php @@ -0,0 +1,40 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Scheduler; + +use Piwik\Plugin\Manager as PluginManager; +use Piwik\Plugin\Tasks; + +/** + * Loads scheduled tasks. + */ +class TaskLoader +{ + /** + * @return Task[] + */ + public function loadTasks() + { + $tasks = array(); + + /** @var Tasks[] $pluginTasks */ + $pluginTasks = PluginManager::getInstance()->findComponents('Tasks', 'Piwik\Plugin\Tasks'); + + foreach ($pluginTasks as $pluginTask) { + + $pluginTask->schedule(); + + foreach ($pluginTask->getScheduledTasks() as $task) { + $tasks[] = $task; + } + } + + return $tasks; + } +} diff --git a/core/TaskScheduler.php b/core/TaskScheduler.php index 6e660a5162..a957c4774d 100644 --- a/core/TaskScheduler.php +++ b/core/TaskScheduler.php @@ -9,9 +9,9 @@ namespace Piwik; -use Exception; -use Piwik\Plugin\Manager as PluginManager; -use Piwik\Scheduler\Timetable; +use Piwik\Container\StaticContainer; +use Piwik\Scheduler\Scheduler; +use Piwik\Scheduler\Task; // When set to true, all scheduled tasks will be triggered in all requests (careful!) //define('DEBUG_FORCE_SCHEDULED_TASKS', true); @@ -50,21 +50,11 @@ use Piwik\Scheduler\Timetable; * * echo "Executed task '$task1Name'. Task output:\n$task1Output"; * - * @method static \Piwik\TaskScheduler getInstance() + * @deprecated Use Piwik\Scheduler\Scheduler instead + * @see \Piwik\Scheduler\Scheduler */ -class TaskScheduler extends Singleton +class TaskScheduler { - const GET_TASKS_EVENT = 'TaskScheduler.getScheduledTasks'; - - private $isRunning = false; - - private $timetable = null; - - public function __construct() - { - $this->timetable = new Timetable(); - } - /** * Executes tasks that are scheduled to run, then reschedules them. * @@ -80,79 +70,7 @@ class TaskScheduler extends Singleton */ public static function runTasks() { - return self::getInstance()->doRunTasks(); - } - - // for backwards compatibility - private function collectTasksRegisteredViaEvent() - { - $tasks = array(); - - /** - * @ignore - * @deprecated - */ - Piwik::postEvent(self::GET_TASKS_EVENT, array(&$tasks)); - - return $tasks; - } - - private function getScheduledTasks() - { - /** @var \Piwik\ScheduledTask[] $tasks */ - $tasks = $this->collectTasksRegisteredViaEvent(); - - /** @var \Piwik\Plugin\Tasks[] $pluginTasks */ - $pluginTasks = PluginManager::getInstance()->findComponents('Tasks', 'Piwik\\Plugin\\Tasks'); - foreach ($pluginTasks as $pluginTask) { - - $pluginTask->schedule(); - - foreach ($pluginTask->getScheduledTasks() as $task) { - $tasks[] = $task; - } - } - - return $tasks; - } - - private function doRunTasks() - { - $tasks = $this->getScheduledTasks(); - - // remove from timetable tasks that are not active anymore - $this->timetable->removeInactiveTasks($tasks); - - // for every priority level, starting with the highest and concluding with the lowest - $executionResults = array(); - for ($priority = ScheduledTask::HIGHEST_PRIORITY; - $priority <= ScheduledTask::LOWEST_PRIORITY; - ++$priority) { - // loop through each task - foreach ($tasks as $task) { - // if the task does not have the current priority level, don't execute it yet - if ($task->getPriority() != $priority) { - continue; - } - - $taskName = $task->getName(); - $shouldExecuteTask = $this->timetable->shouldExecuteTask($taskName); - - if ($this->timetable->taskShouldBeRescheduled($taskName)) { - $this->timetable->rescheduleTask($task); - } - - if ($shouldExecuteTask) { - $this->isRunning = true; - $message = self::executeTask($task); - $this->isRunning = false; - - $executionResults[] = array('task' => $taskName, 'output' => $message); - } - } - } - - return $executionResults; + return self::getInstance()->run(); } /** @@ -161,12 +79,12 @@ class TaskScheduler extends Singleton * Call this method if your task's scheduled time has changed due to, for example, an option that * was changed. * - * @param ScheduledTask $task Describes the scheduled task being rescheduled. + * @param Task $task Describes the scheduled task being rescheduled. * @api */ - public static function rescheduleTask(ScheduledTask $task) + public static function rescheduleTask(Task $task) { - self::getInstance()->timetable->rescheduleTask($task); + self::getInstance()->rescheduleTask($task); } /** @@ -176,7 +94,7 @@ class TaskScheduler extends Singleton */ public static function isTaskBeingExecuted() { - return self::getInstance()->isRunning; + return self::getInstance()->isRunning(); } /** @@ -190,25 +108,14 @@ class TaskScheduler extends Singleton */ public static function getScheduledTimeForMethod($className, $methodName, $methodParameter = null) { - return self::getInstance()->timetable->getScheduledTimeForMethod($className, $methodName, $methodParameter); + return self::getInstance()->getScheduledTimeForMethod($className, $methodName, $methodParameter); } /** - * Executes the given taks - * - * @param ScheduledTask $task - * @return string + * @return Scheduler */ - private static function executeTask($task) + private static function getInstance() { - try { - $timer = new Timer(); - call_user_func(array($task->getObjectInstance(), $task->getMethodName()), $task->getMethodParameter()); - $message = $timer->__toString(); - } catch (Exception $e) { - $message = 'ERROR: ' . $e->getMessage(); - } - - return $message; + return StaticContainer::getContainer()->get('Piwik\Scheduler\Scheduler'); } } diff --git a/tests/PHPUnit/Unit/Scheduler/SchedulerTest.php b/tests/PHPUnit/Unit/Scheduler/SchedulerTest.php new file mode 100644 index 0000000000..7c06370b07 --- /dev/null +++ b/tests/PHPUnit/Unit/Scheduler/SchedulerTest.php @@ -0,0 +1,198 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Tests\Unit\Scheduler; + +use Piwik\Plugin; +use Piwik\Scheduler\Scheduler; +use Piwik\Scheduler\Task; +use Piwik\Scheduler\Timetable; +use Piwik\Tests\Framework\Mock\PiwikOption; +use ReflectionProperty; + +/** + * @group Scheduler + */ +class SchedulerTest extends \PHPUnit_Framework_TestCase +{ + private static function getTestTimetable() + { + return array( + 'CoreAdminHome.purgeOutdatedArchives' => 1355529607, + 'PrivacyManager.deleteReportData_1' => 1322229607, + ); + } + + /** + * Dataprovider for testGetScheduledTimeForMethod + */ + public function getScheduledTimeForMethodTestCases() + { + $timetable = serialize(self::getTestTimetable()); + + return array( + array(1355529607, 'CoreAdminHome', 'purgeOutdatedArchives', null, $timetable), + array(1322229607, 'PrivacyManager', 'deleteReportData', 1, $timetable), + array(false, 'ScheduledReports', 'weeklySchedule', null, $timetable) + ); + } + + /** + * @dataProvider getScheduledTimeForMethodTestCases + */ + public function testGetScheduledTimeForMethod($expectedTime, $className, $methodName, $methodParameter, $timetable) + { + self::stubPiwikOption($timetable); + + $taskLoader = $this->getMock('Piwik\Scheduler\TaskLoader'); + $scheduler = new Scheduler($taskLoader); + + $this->assertEquals($expectedTime, $scheduler->getScheduledTimeForMethod($className, $methodName, $methodParameter)); + + self::resetPiwikOption(); + } + + /** + * Dataprovider for testRun + */ + public function runDataProvider() + { + $now = time(); + + $dailySchedule = $this->getMock('Piwik\Scheduler\Schedule\Daily', array('getTime')); + $dailySchedule->expects($this->any()) + ->method('getTime') + ->will($this->returnValue($now)); + + $scheduledTaskOne = new Task($this, 'scheduledTaskOne', null, $dailySchedule); + $scheduledTaskTwo = new Task($this, 'scheduledTaskTwo', 1, $dailySchedule); + $scheduledTaskThree = new Task($this, 'scheduledTaskThree', null, $dailySchedule); + + $caseOneExpectedTable = array( + __CLASS__ . '.scheduledTaskOne' => $scheduledTaskOne->getRescheduledTime(), + __CLASS__ . '.scheduledTaskTwo_1' => $now + 60000, + __CLASS__ . '.scheduledTaskThree' => $scheduledTaskThree->getRescheduledTime(), + ); + + $caseTwoTimetableBeforeExecution = $caseOneExpectedTable; + $caseTwoTimetableBeforeExecution[__CLASS__ . '.scheduledTaskThree'] = $now; // simulate elapsed time between case 1 and 2 + + return array( + + // case 1) contains : + // - scheduledTaskOne: already scheduled before, should be executed and rescheduled + // - scheduledTaskTwo: already scheduled before, should not be executed and therefore not rescheduled + // - scheduledTaskThree: not already scheduled before, should be scheduled but not executed + array( + $caseOneExpectedTable, + // methods that should be executed + array( + __CLASS__ . '.scheduledTaskOne' + ), + // timetable before task execution + array( + __CLASS__ . '.scheduledTaskOne' => $now, + __CLASS__ . '.scheduledTaskTwo_1' => $now + 60000, + ), + // configured tasks + array( + $scheduledTaskOne, + $scheduledTaskTwo, + $scheduledTaskThree, + ) + ), + + // case 2) follows case 1) with : + // - scheduledTaskOne: already scheduled before, should not be executed and therefore not rescheduled + // - scheduledTaskTwo: not configured for execution anymore, should be removed from the timetable + // - scheduledTaskThree: already scheduled before, should be executed and rescheduled + array( + // expected timetable + array( + __CLASS__ . '.scheduledTaskOne' => $scheduledTaskOne->getRescheduledTime(), + __CLASS__ . '.scheduledTaskThree' => $scheduledTaskThree->getRescheduledTime() + ), + // methods that should be executed + array( + __CLASS__ . '.scheduledTaskThree' + ), + // timetable before task execution + $caseTwoTimetableBeforeExecution, + // configured tasks + array( + $scheduledTaskOne, +// $scheduledTaskTwo, Not configured anymore (ie. not returned after TaskScheduler::GET_TASKS_EVENT is issued) + $scheduledTaskThree, + ) + ), + ); + } + + public function scheduledTaskOne() + { + // nothing to do + } + public function scheduledTaskTwo($param) + { + // nothing to do + } + public function scheduledTaskThree() + { + // nothing to do + } + + /** + * @dataProvider runDataProvider + */ + public function testRun($expectedTimetable, $expectedExecutedTasks, $timetableBeforeTaskExecution, $configuredTasks) + { + $taskLoader = $this->getMock('Piwik\Scheduler\TaskLoader'); + $taskLoader->expects($this->atLeastOnce()) + ->method('loadTasks') + ->willReturn($configuredTasks); + + // stub the piwik option object to control the returned option value + self::stubPiwikOption(serialize($timetableBeforeTaskExecution)); + + $scheduler = new Scheduler($taskLoader); + + // execute tasks + $executionResults = $scheduler->run(); + + // assert methods are executed + $executedTasks = array(); + foreach ($executionResults as $executionResult) { + $executedTasks[] = $executionResult['task']; + $this->assertNotEmpty($executionResult['output']); + } + $this->assertEquals($expectedExecutedTasks, $executedTasks); + + // assert the timetable is correctly updated + $timetable = new Timetable(); + $this->assertEquals($expectedTimetable, $timetable->getTimetable()); + + self::resetPiwikOption(); + } + + private static function stubPiwikOption($timetable) + { + self::getReflectedPiwikOptionInstance()->setValue(new PiwikOption($timetable)); + } + + private static function resetPiwikOption() + { + self::getReflectedPiwikOptionInstance()->setValue(null); + } + + private static function getReflectedPiwikOptionInstance() + { + $piwikOptionInstance = new ReflectionProperty('Piwik\Option', 'instance'); + $piwikOptionInstance->setAccessible(true); + return $piwikOptionInstance; + } +} diff --git a/tests/PHPUnit/Unit/Scheduler/TimetableTest.php b/tests/PHPUnit/Unit/Scheduler/TimetableTest.php new file mode 100644 index 0000000000..6af4b53911 --- /dev/null +++ b/tests/PHPUnit/Unit/Scheduler/TimetableTest.php @@ -0,0 +1,154 @@ +<?php +/** + * Piwik - free/libre analytics platform + * + * @link http://piwik.org + * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later + */ + +namespace Piwik\Tests\Unit\Scheduler; + +use Piwik\Plugin; +use Piwik\Scheduler\Timetable; +use Piwik\Tests\Framework\Mock\PiwikOption; +use ReflectionProperty; + +/** + * @group Scheduler + */ +class TimetableTest extends \PHPUnit_Framework_TestCase +{ + private static function getTestTimetable() + { + return array( + 'CoreAdminHome.purgeOutdatedArchives' => 1355529607, + 'PrivacyManager.deleteReportData_1' => 1322229607, + ); + } + + /** + * Dataprovider for testGetTimetableFromOptionValue + */ + public function getTimetableFromOptionValueTestCases() + { + return array( + + // invalid option values should return a fresh array + array(array(), false), + array(array(), null), + array(array(), 1), + array(array(), ''), + array(array(), 'test'), + + // valid serialized array + array( + array( + 'CoreAdminHome.purgeOutdatedArchives' => 1355529607, + 'PrivacyManager.deleteReportData' => 1355529607, + ), + 'a:2:{s:35:"CoreAdminHome.purgeOutdatedArchives";i:1355529607;s:31:"PrivacyManager.deleteReportData";i:1355529607;}' + ), + ); + } + + /** + * @dataProvider getTimetableFromOptionValueTestCases + */ + public function testGetTimetableFromOptionValue($expectedTimetable, $option) + { + self::stubPiwikOption($option); + + $timetable = new Timetable(); + $this->assertEquals($expectedTimetable, $timetable->getTimetable()); + + self::resetPiwikOption(); + } + + /** + * Dataprovider for testTaskHasBeenScheduledOnce + */ + public function taskHasBeenScheduledOnceTestCases() + { + $timetable = self::getTestTimetable(); + + return array( + array(true, 'CoreAdminHome.purgeOutdatedArchives', $timetable), + array(true, 'PrivacyManager.deleteReportData_1', $timetable), + array(false, 'ScheduledReports.weeklySchedule"', $timetable) + ); + } + + /** + * @dataProvider taskHasBeenScheduledOnceTestCases + */ + public function testTaskHasBeenScheduledOnce($expectedDecision, $taskName, $timetable) + { + $timetableObj = new Timetable(); + $timetableObj->setTimetable($timetable); + $this->assertEquals($expectedDecision, $timetableObj->taskHasBeenScheduledOnce($taskName)); + } + + /** + * Dataprovider for testGetScheduledTimeForMethod + */ + public function getScheduledTimeForMethodTestCases() + { + $timetable = serialize(self::getTestTimetable()); + + return array( + array(1355529607, 'CoreAdminHome', 'purgeOutdatedArchives', null, $timetable), + array(1322229607, 'PrivacyManager', 'deleteReportData', 1, $timetable), + array(false, 'ScheduledReports', 'weeklySchedule', null, $timetable) + ); + } + + /** + * Dataprovider for testTaskShouldBeExecuted + */ + public function taskShouldBeExecutedTestCases() + { + $timetable = self::getTestTimetable(); + + // set a date in the future (should not run) + $timetable['CoreAdminHome.purgeOutdatedArchives'] = time() + 60000; + + // set now (should run) + $timetable['PrivacyManager.deleteReportData_1'] = time(); + + return array( + array(false, 'CoreAdminHome.purgeOutdatedArchives', $timetable), + array(true, 'PrivacyManager.deleteReportData_1', $timetable), + array(false, 'ScheduledReports.weeklySchedule"', $timetable) + ); + } + + /** + * @dataProvider taskShouldBeExecutedTestCases + */ + public function testTaskShouldBeExecuted($expectedDecision, $taskName, $timetable) + { + self::stubPiwikOption(serialize($timetable)); + + $timetable = new Timetable(); + $this->assertEquals($expectedDecision, $timetable->shouldExecuteTask($taskName)); + + self::resetPiwikOption(); + } + + private static function stubPiwikOption($timetable) + { + self::getReflectedPiwikOptionInstance()->setValue(new PiwikOption($timetable)); + } + + private static function resetPiwikOption() + { + self::getReflectedPiwikOptionInstance()->setValue(null); + } + + private static function getReflectedPiwikOptionInstance() + { + $piwikOptionInstance = new ReflectionProperty('Piwik\Option', 'instance'); + $piwikOptionInstance->setAccessible(true); + return $piwikOptionInstance; + } +} diff --git a/tests/PHPUnit/Unit/TaskSchedulerTest.php b/tests/PHPUnit/Unit/TaskSchedulerTest.php deleted file mode 100644 index 16e0fde567..0000000000 --- a/tests/PHPUnit/Unit/TaskSchedulerTest.php +++ /dev/null @@ -1,330 +0,0 @@ -<?php -/** - * Piwik - free/libre analytics platform - * - * @link http://piwik.org - * @license http://www.gnu.org/licenses/gpl-3.0.html GPL v3 or later - */ - -namespace Piwik\Tests\Unit; - -use Piwik\EventDispatcher; -use Piwik\Piwik; -use Piwik\Plugin; -use Piwik\Scheduler\Schedule\Schedule; -use Piwik\Scheduler\Task; -use Piwik\Scheduler\Timetable; -use Piwik\TaskScheduler; -use Piwik\Tests\Framework\Mock\PiwikOption; -use ReflectionMethod; -use ReflectionProperty; - -/** - * @group Scheduler - */ -class TaskSchedulerTest extends \PHPUnit_Framework_TestCase -{ - private static function getTestTimetable() - { - return array( - 'CoreAdminHome.purgeOutdatedArchives' => 1355529607, - 'PrivacyManager.deleteReportData_1' => 1322229607, - ); - } - - /** - * Dataprovider for testGetTimetableFromOptionValue - */ - public function getTimetableFromOptionValueTestCases() - { - return array( - - // invalid option values should return a fresh array - array(array(), false), - array(array(), null), - array(array(), 1), - array(array(), ''), - array(array(), 'test'), - - // valid serialized array - array( - array( - 'CoreAdminHome.purgeOutdatedArchives' => 1355529607, - 'PrivacyManager.deleteReportData' => 1355529607, - ), - 'a:2:{s:35:"CoreAdminHome.purgeOutdatedArchives";i:1355529607;s:31:"PrivacyManager.deleteReportData";i:1355529607;}' - ), - ); - } - - /** - * @dataProvider getTimetableFromOptionValueTestCases - */ - public function testGetTimetableFromOptionValue($expectedTimetable, $option) - { - self::stubPiwikOption($option); - - $timetable = new Timetable(); - $this->assertEquals($expectedTimetable, $timetable->getTimetable()); - } - - /** - * Dataprovider for testTaskHasBeenScheduledOnce - */ - public function taskHasBeenScheduledOnceTestCases() - { - $timetable = self::getTestTimetable(); - - return array( - array(true, 'CoreAdminHome.purgeOutdatedArchives', $timetable), - array(true, 'PrivacyManager.deleteReportData_1', $timetable), - array(false, 'ScheduledReports.weeklySchedule"', $timetable) - ); - } - - /** - * @dataProvider taskHasBeenScheduledOnceTestCases - */ - public function testTaskHasBeenScheduledOnce($expectedDecision, $taskName, $timetable) - { - $timetableObj = new Timetable(); - $timetableObj->setTimetable($timetable); - $this->assertEquals($expectedDecision, $timetableObj->taskHasBeenScheduledOnce($taskName)); - } - - /** - * Dataprovider for testGetScheduledTimeForMethod - */ - public function getScheduledTimeForMethodTestCases() - { - $timetable = serialize(self::getTestTimetable()); - - return array( - array(1355529607, 'CoreAdminHome', 'purgeOutdatedArchives', null, $timetable), - array(1322229607, 'PrivacyManager', 'deleteReportData', 1, $timetable), - array(false, 'ScheduledReports', 'weeklySchedule', null, $timetable) - ); - } - - /** - * @dataProvider getScheduledTimeForMethodTestCases - */ - public function testGetScheduledTimeForMethod($expectedTime, $className, $methodName, $methodParameter, $timetable) - { - self::stubPiwikOption($timetable); - - $this->assertEquals($expectedTime, TaskScheduler::getScheduledTimeForMethod($className, $methodName, $methodParameter)); - - self::resetPiwikOption(); - } - - /** - * Dataprovider for testTaskShouldBeExecuted - */ - public function taskShouldBeExecutedTestCases() - { - $timetable = self::getTestTimetable(); - - // set a date in the future (should not run) - $timetable['CoreAdminHome.purgeOutdatedArchives'] = time() + 60000; - - // set now (should run) - $timetable['PrivacyManager.deleteReportData_1'] = time(); - - return array( - array(false, 'CoreAdminHome.purgeOutdatedArchives', $timetable), - array(true, 'PrivacyManager.deleteReportData_1', $timetable), - array(false, 'ScheduledReports.weeklySchedule"', $timetable) - ); - } - - /** - * @dataProvider taskShouldBeExecutedTestCases - */ - public function testTaskShouldBeExecuted($expectedDecision, $taskName, $timetable) - { - self::stubPiwikOption(serialize($timetable)); - - $timetable = new Timetable(); - $this->assertEquals($expectedDecision, $timetable->shouldExecuteTask($taskName)); - } - - /** - * Dataprovider for testExecuteTask - */ - public function executeTaskTestCases() - { - return array( - array('scheduledTaskOne', null), - array('scheduledTaskTwo', 'parameterValue'), - array('scheduledTaskTwo', 1), - ); - } - - /** - * @dataProvider executeTaskTestCases - */ - public function testExecuteTask($methodName, $parameterValue) - { - // assert the scheduled method is executed once with the correct parameter - $mock = $this->getMock('TaskSchedulerTest', array($methodName)); - $mock->expects($this->once())->method($methodName)->with($this->equalTo($parameterValue)); - - $executeTask = new ReflectionMethod('\Piwik\TaskScheduler', 'executeTask'); - $executeTask->setAccessible(true); - - $this->assertNotEmpty($executeTask->invoke( - new TaskScheduler(), - new Task($mock, $methodName, $parameterValue, Schedule::factory('daily')) - )); - } - - /** - * Dataprovider for testRunTasks - */ - public function testRunTasksTestCases() - { - $systemTime = time(); - - $dailySchedule = $this->getMock('Piwik\Scheduler\Schedule\Daily', array('getTime')); - $dailySchedule->expects($this->any()) - ->method('getTime') - ->will($this->returnValue($systemTime)); - - $scheduledTaskOne = new Task($this, 'scheduledTaskOne', null, $dailySchedule); - $scheduledTaskTwo = new Task($this, 'scheduledTaskTwo', 1, $dailySchedule); - $scheduledTaskThree = new Task($this, 'scheduledTaskThree', null, $dailySchedule); - - $caseOneExpectedTable = array( - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskOne' => $scheduledTaskOne->getRescheduledTime(), - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskTwo_1' => $systemTime + 60000, - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskThree' => $scheduledTaskThree->getRescheduledTime(), - ); - - $caseTwoTimetableBeforeExecution = $caseOneExpectedTable; - $caseTwoTimetableBeforeExecution['Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskThree'] = $systemTime; // simulate elapsed time between case 1 and 2 - - return array( - - // case 1) contains : - // - scheduledTaskOne: already scheduled before, should be executed and rescheduled - // - scheduledTaskTwo: already scheduled before, should not be executed and therefore not rescheduled - // - scheduledTaskThree: not already scheduled before, should be scheduled but not executed - array( - $caseOneExpectedTable, - - // methods that should be executed - array( - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskOne' - ), - - // timetable before task execution - array( - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskOne' => $systemTime, - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskTwo_1' => $systemTime + 60000, - ), - // configured tasks - array( - $scheduledTaskOne, - $scheduledTaskTwo, - $scheduledTaskThree, - ) - ), - - // case 2) follows case 1) with : - // - scheduledTaskOne: already scheduled before, should not be executed and therefore not rescheduled - // - scheduledTaskTwo: not configured for execution anymore, should be removed from the timetable - // - scheduledTaskThree: already scheduled before, should be executed and rescheduled - array( - // expected timetable - array( - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskOne' => $scheduledTaskOne->getRescheduledTime(), - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskThree' => $scheduledTaskThree->getRescheduledTime() - ), - - // methods that should be executed - array( - 'Piwik\Tests\Unit\TaskSchedulerTest.scheduledTaskThree' - ), - - // timetable before task execution - $caseTwoTimetableBeforeExecution, - - // configured tasks - array( - $scheduledTaskOne, -// $scheduledTaskTwo, Not configured anymore (ie. not returned after TaskScheduler::GET_TASKS_EVENT is issued) - $scheduledTaskThree, - ) - ), - ); - } - - public function scheduledTaskOne() - { - } // nothing to do - public function scheduledTaskTwo($param) - { - } // nothing to do - public function scheduledTaskThree() - { - } // nothing to do - - /** - * @dataProvider testRunTasksTestCases - */ - public function testRunTasks($expectedTimetable, $expectedExecutedTasks, $timetableBeforeTaskExecution, $configuredTasks) - { - // temporarily unload plugins - $plugins = Plugin\Manager::getInstance()->getLoadedPlugins(); - $plugins = array_map(function (Plugin $p) { return $p->getPluginName(); }, $plugins); - - Plugin\Manager::getInstance()->unloadPlugins(); - - // make sure the get tasks event returns our configured tasks - Piwik::addAction(TaskScheduler::GET_TASKS_EVENT, function(&$tasks) use($configuredTasks) { - $tasks = $configuredTasks; - }); - - // stub the piwik option object to control the returned option value - self::stubPiwikOption(serialize($timetableBeforeTaskExecution)); - TaskScheduler::unsetInstance(); - - // execute tasks - $executionResults = TaskScheduler::runTasks(); - - // assert methods are executed - $executedTasks = array(); - foreach ($executionResults as $executionResult) { - $executedTasks[] = $executionResult['task']; - $this->assertNotEmpty($executionResult['output']); - } - $this->assertEquals($expectedExecutedTasks, $executedTasks); - - // assert the timetable is correctly updated - $timetable = new Timetable(); - $this->assertEquals($expectedTimetable, $timetable->getTimetable()); - - // restore loaded plugins & piwik options - EventDispatcher::getInstance()->clearObservers(TaskScheduler::GET_TASKS_EVENT); - Plugin\Manager::getInstance()->loadPlugins($plugins); - self::resetPiwikOption(); - } - - private static function stubPiwikOption($timetable) - { - self::getReflectedPiwikOptionInstance()->setValue(new PiwikOption($timetable)); - } - - private static function resetPiwikOption() - { - self::getReflectedPiwikOptionInstance()->setValue(null); - } - - private static function getReflectedPiwikOptionInstance() - { - $piwikOptionInstance = new ReflectionProperty('Piwik\Option', 'instance'); - $piwikOptionInstance->setAccessible(true); - return $piwikOptionInstance; - } -} -- GitLab