diff --git a/tests/integration/Cron/CronWorkerRunnerTest.php b/tests/integration/Cron/CronWorkerRunnerTest.php new file mode 100644 index 0000000000..ff13216c7e --- /dev/null +++ b/tests/integration/Cron/CronWorkerRunnerTest.php @@ -0,0 +1,218 @@ +cron_worker_runner = $this->di_container->get(CronWorkerRunner::class); + $this->cron_helper = $this->di_container->get(CronHelper::class); + ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table); + } + + function testItCanInitBeforeProcessing() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'init' => Expected::once(), + 'scheduleAutomatically' => Expected::once(false), + ]); + $this->cron_worker_runner->run($worker); + } + + function testItPreparesTask() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'prepareTaskStrategy' => Expected::once(true), + 'processTaskStrategy' => Expected::never(), + ]); + + $task = $this->createScheduledTask(); + $result = $this->cron_worker_runner->run($worker); + expect($result)->true(); + expect(ScheduledTask::findOne($task->id)->status)->null(); + } + + function testItProcessesTask() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'prepareTaskStrategy' => Expected::never(), + 'processTaskStrategy' => Expected::once(true), + ]); + + $task = $this->createRunningTask(); + $result = $this->cron_worker_runner->run($worker); + expect($result)->true(); + expect(ScheduledTask::findOne($task->id)->status)->same(ScheduledTask::STATUS_COMPLETED); + } + + function testItFailsToProcessWithoutTasks() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'scheduleAutomatically' => Expected::once(false), + 'prepareTaskStrategy' => Expected::never(), + 'processTaskStrategy' => Expected::never(), + ]); + + $result = $this->cron_worker_runner->run($worker); + expect($result)->false(); + } + + function testItFailsToProcessWithoutProcessingRequirementsMet() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'checkProcessingRequirements' => Expected::once(false), + 'prepareTaskStrategy' => Expected::never(), + 'processTaskStrategy' => Expected::never(), + ]); + + $this->createScheduledTask(); + $this->createRunningTask(); + + $result = $this->cron_worker_runner->run($worker); + expect($result)->false(); + } + + function testItCanScheduleTaskAutomatically() { + $in_one_week = Carbon::now()->addWeek()->startOfDay(); + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'scheduleAutomatically' => Expected::once(true), + 'getTaskType' => Expected::atLeastOnce(SimpleWorkerMockImplementation::TASK_TYPE), + 'getNextRunDate' => Expected::once($in_one_week), + ]); + + $result = $this->cron_worker_runner->run($worker); + expect($result)->false(); + expect(ScheduledTask::findOne()->scheduled_at)->same($in_one_week->format('Y-m-d H:i:s')); + } + + function testItWillRescheduleTaskIfItIsRunningForTooLong() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'processTaskStrategy' => Expected::once(false), + ]); + $worker->__construct(); + + $task = $this->createRunningTask(); + $task = ScheduledTask::findOne($task->id); // make sure `updated_at` is set by the DB + + $result = $this->cron_worker_runner->run($worker); + expect($result)->true(); + + $scheduled_at = $task->scheduled_at; + $task->updated_at = Carbon::createFromTimestamp(strtotime($task->updated_at)) + ->subMinutes(CronWorkerRunner::TASK_RUN_TIMEOUT + 1); + $task->save(); + + $result = $this->cron_worker_runner->run($worker); + expect($result)->true(); + + $task = ScheduledTask::findOne($task->id); + expect($task->scheduled_at)->greaterThan($scheduled_at); + expect($task->status)->same(ScheduledTask::STATUS_SCHEDULED); + expect($task->in_progress)->isEmpty(); + } + + function testItWillRescheduleATaskIfItFails() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'processTaskStrategy' => Expected::once(function () { + throw new \Exception('test error'); + }), + ]); + + $task = $this->createRunningTask(); + $scheduled_at = $task->scheduled_at; + try { + $this->cron_worker_runner->run($worker); + $this->fail('An exception should be thrown'); + } catch (\Exception $e) { + expect($e->getMessage())->equals('test error'); + $task = ScheduledTask::findOne($task->id); + expect($task->scheduled_at)->greaterThan($scheduled_at); + expect($task->status)->same(ScheduledTask::STATUS_SCHEDULED); + expect($task->reschedule_count)->equals(1); + expect($task->in_progress)->isEmpty(); + } + } + + function testWillNotRescheduleATaskOnCronTimeout() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'processTaskStrategy' => Expected::once(function () { + $this->cron_helper->enforceExecutionLimit(microtime(true) - CronHelper::DAEMON_EXECUTION_LIMIT - 1); + }), + ]); + + $task = $this->createRunningTask(); + $scheduled_at = $task->scheduled_at; + try { + $this->cron_worker_runner->run($worker); + $this->fail('An exception should be thrown'); + } catch (\Exception $e) { + expect($e->getCode())->same(CronHelper::DAEMON_EXECUTION_LIMIT_REACHED); + $task = ScheduledTask::findOne($task->id); + expect($scheduled_at)->equals($task->scheduled_at); + expect($task->status)->null(); + expect($task->reschedule_count)->equals(0); + } + } + + function testItWillNotRunInMultipleInstances() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'supportsMultipleInstances' => Expected::once(false), + 'processTaskStrategy' => Expected::never(), + ]); + + $task = $this->createRunningTask(); + $task->in_progress = true; + $task->save(); + + $this->cron_worker_runner->run($worker); + } + + function testItThrowsExceptionWhenExecutionLimitIsReached() { + $worker = $this->make(SimpleWorkerMockImplementation::class, [ + 'processTaskStrategy' => Expected::never(), + ]); + + $cron_worker_runner = Stub::copy($this->cron_worker_runner, [ + 'timer' => microtime(true) - $this->di_container->get(CronHelper::class)->getDaemonExecutionLimit(), + ]); + try { + $cron_worker_runner->run($worker); + self::fail('Maximum execution time limit exception was not thrown.'); + } catch (\Exception $e) { + expect($e->getMessage())->same('Maximum execution time has been reached.'); + } + } + + private function createScheduledTask() { + $task = ScheduledTask::create(); + $task->type = SimpleWorkerMockImplementation::TASK_TYPE; + $task->status = ScheduledTask::STATUS_SCHEDULED; + $task->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp')); + $task->save(); + return $task; + } + + private function createRunningTask() { + $task = ScheduledTask::create(); + $task->type = SimpleWorkerMockImplementation::TASK_TYPE; + $task->status = null; + $task->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp')); + $task->save(); + return $task; + } + + function _after() { + ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table); + } +} diff --git a/tests/integration/Cron/CronWorkerSchedulerTest.php b/tests/integration/Cron/CronWorkerSchedulerTest.php new file mode 100644 index 0000000000..3a85a0c06c --- /dev/null +++ b/tests/integration/Cron/CronWorkerSchedulerTest.php @@ -0,0 +1,58 @@ +cron_worker_scheduler = $this->di_container->get(CronWorkerScheduler::class); + ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table); + } + + function testItSchedulesTask() { + $next_run_date = Carbon::now()->addWeek(); + $this->cron_worker_scheduler->schedule('test', $next_run_date); + + $tasks = ScheduledTask::findMany(); + expect($tasks)->count(1); + expect($tasks[0]->type)->same('test'); + expect($tasks[0]->status)->same(ScheduledTask::STATUS_SCHEDULED); + expect($tasks[0]->scheduled_at)->same($next_run_date->format('Y-m-d H:i:s')); + } + + function testItDoesNotScheduleTaskTwice() { + $next_run_date = Carbon::now()->addWeek(); + $this->cron_worker_scheduler->schedule('test', $next_run_date); + expect(ScheduledTask::findMany())->count(1); + + $result = $this->cron_worker_scheduler->schedule('test', $next_run_date); + expect($result)->false(); + expect(ScheduledTask::findMany())->count(1); + } + + function testItReschedulesTask() { + $next_run_date = Carbon::now()->subDay(); + $task = $this->cron_worker_scheduler->schedule('test', $next_run_date); + $this->cron_worker_scheduler->reschedule($task, 10); + + $tasks = ScheduledTask::findMany(); + expect($tasks)->count(1); + expect($tasks[0]->type)->same('test'); + expect($tasks[0]->status)->same(ScheduledTask::STATUS_SCHEDULED); + expect($tasks[0]->scheduled_at)->greaterThan($next_run_date); + expect($tasks[0]->scheduled_at)->greaterThan(Carbon::now()); + } + + function _after() { + ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table); + } +} diff --git a/tests/integration/Cron/Workers/SimpleWorkerTest.php b/tests/integration/Cron/Workers/SimpleWorkerTest.php index 225a32f08b..caf98976c1 100644 --- a/tests/integration/Cron/Workers/SimpleWorkerTest.php +++ b/tests/integration/Cron/Workers/SimpleWorkerTest.php @@ -3,7 +3,6 @@ namespace MailPoet\Test\Cron\Workers; use Codeception\Stub; -use Codeception\Stub\Expected; use MailPoet\Cron\CronHelper; use MailPoet\Cron\Workers\SimpleWorkerMockImplementation as MockSimpleWorker; use MailPoet\DI\ContainerWrapper; @@ -12,7 +11,7 @@ use MailPoet\Settings\SettingsRepository; use MailPoetVendor\Carbon\Carbon; use MailPoetVendor\Idiorm\ORM; -require_once('SimpleWorkerMockImplementation.php'); +require_once __DIR__ . '/SimpleWorkerMockImplementation.php'; class SimpleWorkerTest extends \MailPoetTest { function _before() { @@ -36,15 +35,6 @@ class SimpleWorkerTest extends \MailPoetTest { } } - function testItThrowsExceptionWhenExecutionLimitIsReached() { - try { - $this->worker->process(microtime(true) - $this->cron_helper->getDaemonExecutionLimit()); - self::fail('Maximum execution time limit exception was not thrown.'); - } catch (\Exception $e) { - expect($e->getMessage())->equals('Maximum execution time has been reached.'); - } - } - function testItSchedulesTask() { expect(ScheduledTask::where('type', MockSimpleWorker::TASK_TYPE)->findMany())->isEmpty(); (new MockSimpleWorker())->schedule(); @@ -60,214 +50,6 @@ class SimpleWorkerTest extends \MailPoetTest { expect(count(ScheduledTask::where('type', MockSimpleWorker::TASK_TYPE)->findMany()))->equals(1); } - function testItCanGetScheduledTasks() { - $worker = new MockSimpleWorker(); - expect($worker->getDueTasks())->isEmpty(); - $this->createScheduledTask(); - expect($worker->getDueTasks())->notEmpty(); - } - - function testItCanGetABatchOfScheduledTasks() { - $worker = new MockSimpleWorker(); - for ($i = 0; $i < MockSimpleWorker::TASK_BATCH_SIZE + 5; $i += 1) { - $this->createScheduledTask(); - } - expect(count($worker->getDueTasks()))->equals(MockSimpleWorker::TASK_BATCH_SIZE); - } - - function testItCanGetRunningTasks() { - $worker = new MockSimpleWorker(); - expect($worker->getRunningTasks())->isEmpty(); - $this->createRunningTask(); - expect($worker->getRunningTasks())->notEmpty(); - } - - function testItCanGetBatchOfRunningTasks() { - $worker = new MockSimpleWorker(); - for ($i = 0; $i < MockSimpleWorker::TASK_BATCH_SIZE + 5; $i += 1) { - $this->createRunningTask(); - } - expect(count($worker->getRunningTasks()))->equals(MockSimpleWorker::TASK_BATCH_SIZE); - } - - function testItCanGetBatchOfCompletedTasks() { - $worker = new MockSimpleWorker(); - for ($i = 0; $i < MockSimpleWorker::TASK_BATCH_SIZE + 5; $i += 1) { - $this->createCompletedTask(); - } - expect(count($worker->getCompletedTasks()))->equals(MockSimpleWorker::TASK_BATCH_SIZE); - } - - function testItFailsToProcessWithoutTasks() { - expect($this->worker->process())->false(); - } - - function testItFailsToProcessWithoutProcessingRequirementsMet() { - $this->createScheduledTask(); - $this->createRunningTask(); - $worker = Stub::make( - $this->worker, - ['checkProcessingRequirements' => false], - $this - ); - $worker->__construct(); - expect($worker->process())->false(); - } - - function testItCanInitBeforeProcessing() { - $worker = Stub::make( - $this->worker, - [ - 'init' => Expected::once(), - 'schedule' => Expected::once(), - ], - $this - ); - $worker->__construct(); - $worker->process(); - } - - function testItProcesses() { - $this->createScheduledTask(); - $this->createRunningTask(); - expect($this->worker->process())->true(); - } - - function testItPreparesTask() { - $task = $this->createScheduledTask(); - $this->worker->prepareTask($task, microtime(true)); - expect($task->status)->null(); - } - - function testItProcessesTask() { - $task = $this->createRunningTask(); - $result = $this->worker->processTask($task, microtime(true)); - expect($task->status)->equals(ScheduledTask::STATUS_COMPLETED); - expect($result)->equals(true); - } - - function testItReturnsFalseIfInnerProcessingFunctionReturnsFalse() { - $task = $this->createRunningTask(); - $worker = Stub::construct( - $this->worker, - [], - ['processTaskStrategy' => false], - $this - ); - $result = $worker->processTask($task, microtime(true)); - expect($task->status)->equals(null); - expect($result)->equals(false); - } - - function testItCanRescheduleTasks() { - $task = $this->createRunningTask(); - $scheduled_at = $task->scheduled_at; - $this->worker->reschedule($task, 10); - expect($scheduled_at < $task->scheduled_at)->true(); - expect($task->status)->equals(ScheduledTask::STATUS_SCHEDULED); - } - - function testWillRescheduleATaskIfItFails() { - $task = $this->createRunningTask(); - $worker = Stub::construct( - $this->worker, - [], - [ - 'processTaskStrategy' => function () { - throw new \Exception('test error'); - }, - ], - $this - ); - $scheduled_at = $task->scheduled_at; - try { - $worker->process(); - $this->fail('An exception should be thrown'); - } catch (\Exception $e) { - expect($e->getMessage())->equals('test error'); - $task = ScheduledTask::findOne($task->id); - expect($scheduled_at < $task->scheduled_at)->true(); - expect($task->status)->equals(ScheduledTask::STATUS_SCHEDULED); - expect($task->reschedule_count)->equals(1); - } - } - - function testWillNotRescheduleATaskOnCronTimeout() { - $task = $this->createRunningTask(); - $worker = Stub::construct( - $this->worker, - [], - [ - 'processTaskStrategy' => function () { - $this->cron_helper->enforceExecutionLimit(microtime(true) - CronHelper::DAEMON_EXECUTION_LIMIT - 1); - }, - ], - $this - ); - $scheduled_at = $task->scheduled_at; - try { - $worker->process(); - $this->fail('An exception should be thrown'); - } catch (\Exception $e) { - expect($e->getCode())->equals(CronHelper::DAEMON_EXECUTION_LIMIT_REACHED); - $task = ScheduledTask::findOne($task->id); - expect($scheduled_at)->equals($task->scheduled_at); - expect($task->status)->equals(null); - expect($task->reschedule_count)->equals(0); - } - } - - function testItWillNotRunInMultipleInstances() { - $worker = $this->getMockBuilder(MockSimpleWorker::class) - ->setMethods(['processTaskStrategy']) - ->getMock(); - $worker->expects($this->once()) - ->method('processTaskStrategy') - ->willReturn(true); - $task = $this->createRunningTask(); - expect(empty($task->in_progress))->equals(true); - expect($worker->processTask($task, microtime(true)))->equals(true); - $task->in_progress = true; - expect($worker->processTask($task, microtime(true)))->equals(false); - } - - function testItWillResetTheInProgressFlagOnFail() { - $worker = $this->getMockBuilder(MockSimpleWorker::class) - ->setMethods(['processTaskStrategy']) - ->getMock(); - $worker->expects($this->once()) - ->method('processTaskStrategy') - ->willThrowException(new \Exception('test error')); - $task = $this->createRunningTask(); - try { - $worker->processTask($task, microtime(true)); - $this->fail('An exception should be thrown'); - } catch (\Exception $e) { - expect($e->getMessage())->equals('test error'); - expect(empty($task->in_progress))->equals(true); - } - } - - function testItWillRescheduleTaskIfItIsRunningForTooLong() { - $worker = $this->getMockBuilder(MockSimpleWorker::class) - ->setMethods(['processTaskStrategy']) - ->getMock(); - $worker->expects($this->once()) - ->method('processTaskStrategy') - ->willReturn(true); - $task = $this->createRunningTask(); - $task = ScheduledTask::findOne($task->id); // make sure `updated_at` is set by the DB - expect($worker->processTask($task, microtime(true)))->equals(true); - $scheduled_at = $task->scheduled_at; - $task->updated_at = Carbon::createFromTimestamp(strtotime($task->updated_at)) - ->subMinutes(MockSimpleWorker::TASK_RUN_TIMEOUT + 1); - expect($worker->processTask($task, microtime(true)))->equals(false); - $task = ScheduledTask::findOne($task->id); - expect($scheduled_at < $task->scheduled_at)->true(); - expect($task->status)->equals(ScheduledTask::STATUS_SCHEDULED); - expect(empty($task->in_progress))->equals(true); - } - function testItCalculatesNextRunDateWithinNextWeekBoundaries() { $current_date = Carbon::createFromTimestamp(current_time('timestamp')); $next_run_date = (new MockSimpleWorker())->getNextRunDate(); @@ -278,33 +60,6 @@ class SimpleWorkerTest extends \MailPoetTest { expect($difference)->greaterOrEquals(0); } - private function createScheduledTask() { - $task = ScheduledTask::create(); - $task->type = MockSimpleWorker::TASK_TYPE; - $task->status = ScheduledTask::STATUS_SCHEDULED; - $task->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp')); - $task->save(); - return $task; - } - - private function createRunningTask() { - $task = ScheduledTask::create(); - $task->type = MockSimpleWorker::TASK_TYPE; - $task->status = null; - $task->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp')); - $task->save(); - return $task; - } - - private function createCompletedTask() { - $task = ScheduledTask::create(); - $task->type = MockSimpleWorker::TASK_TYPE; - $task->status = ScheduledTask::STATUS_COMPLETED; - $task->scheduled_at = Carbon::createFromTimestamp(current_time('timestamp')); - $task->save(); - return $task; - } - function _after() { $this->di_container->get(SettingsRepository::class)->truncate(); ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table); diff --git a/tests/integration/Models/ScheduledTaskTest.php b/tests/integration/Models/ScheduledTaskTest.php index 631f3f8e9f..2947273ef3 100644 --- a/tests/integration/Models/ScheduledTaskTest.php +++ b/tests/integration/Models/ScheduledTaskTest.php @@ -157,6 +157,142 @@ class ScheduledTaskTest extends \MailPoetTest { expect($timeout)->equals(ScheduledTask::MAX_RESCHEDULE_TIMEOUT); } + function testItCanGetDueTasks() { + // due (scheduled in past) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_SCHEDULED, + 'scheduled_at' => Carbon::now()->subDay(), + ]); + + // deleted (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_SCHEDULED, + 'scheduled_at' => Carbon::now()->subDay(), + 'deleted_at' => Carbon::now(), + ]); + + // scheduled in future (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_SCHEDULED, + 'scheduled_at' => Carbon::now()->addDay(), + ]); + + // wrong status (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => null, + 'scheduled_at' => Carbon::now()->subDay(), + ]); + + $tasks = ScheduledTask::findDueByType('test', 10); + expect($tasks)->count(1); + } + + function testItCanGetRunningTasks() { + // running (scheduled in past) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => null, + 'scheduled_at' => Carbon::now()->subDay(), + ]); + + // deleted (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => null, + 'scheduled_at' => Carbon::now()->subDay(), + 'deleted_at' => Carbon::now(), + ]); + + // scheduled in future (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => null, + 'scheduled_at' => Carbon::now()->addDay(), + ]); + + // wrong status (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_COMPLETED, + 'scheduled_at' => Carbon::now()->subDay(), + ]); + + $tasks = ScheduledTask::findRunningByType('test', 10); + expect($tasks)->count(1); + } + + function testItCanGetCompletedTasks() { + // completed (scheduled in past) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_COMPLETED, + 'scheduled_at' => Carbon::now()->subDay(), + ]); + + // deleted (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_COMPLETED, + 'scheduled_at' => Carbon::now()->subDay(), + 'deleted_at' => Carbon::now(), + ]); + + // scheduled in future (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_COMPLETED, + 'scheduled_at' => Carbon::now()->addDay(), + ]); + + // wrong status (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_SCHEDULED, + 'scheduled_at' => Carbon::now()->subDay(), + ]); + + $tasks = ScheduledTask::findCompletedByType('test', 10); + expect($tasks)->count(1); + } + + function testItCanGetFutureScheduledTasks() { + // scheduled (in future) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_SCHEDULED, + 'scheduled_at' => Carbon::now()->addDay(), + ]); + + // deleted (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_SCHEDULED, + 'scheduled_at' => Carbon::now()->addDay(), + 'deleted_at' => Carbon::now(), + ]); + + // scheduled in past (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => ScheduledTask::STATUS_SCHEDULED, + 'scheduled_at' => Carbon::now()->subDay(), + ]); + + // wrong status (should not be fetched) + ScheduledTask::createOrUpdate([ + 'type' => 'test', + 'status' => null, + 'scheduled_at' => Carbon::now()->addDay(), + ]); + + $tasks = ScheduledTask::findDueByType('test', 10); + expect($tasks)->count(1); + } + function _after() { ORM::raw_execute('TRUNCATE ' . ScheduledTask::$_table); ORM::raw_execute('TRUNCATE ' . ScheduledTaskSubscriber::$_table);