/ */ class Cron_Test_CronTest extends ModuleTest { protected static $_counters = array(); /** * Test run() method where each cron run should trigger all cycles. */ public function testRunIndependent() { $tests = array( array( 'line' => __LINE__, 'date' => '2011-01-01 15:45:27' ), array( 'line' => __LINE__, 'date' => '2001-11-17 00:00:01' ), array( 'line' => __LINE__, 'date' => '2007-02-25 07:00:01' ), array( 'line' => __LINE__, 'date' => '2020-05-31 23:59:59' ), array( 'line' => __LINE__, 'date' => '2020-06-01 00:00:00' ) ); // run tests $this->_initSubscribe(); $revisionsCount = 2; $cronFiles = array('hourly', 'daily', 'weekly', 'monthly'); foreach ($tests as $test) { static::_resetCounters(); $timestamp = strtotime($test['date']); $result = Cron_Model_Cron::run(null, $timestamp); $this->_verifyResult($result); $this->_verifyCounters( array('hourly' => 1, 'daily' => 1, 'weekly' => 1, 'monthly' => 1), "Line {$test['line']}: Expected each cron cycle was run once." ); // verify cron file revisions and attributes foreach ($cronFiles as $record) { $cronFile = Cron_Model_Cron::fetch($record); // ensure 2 new revisions of each cron file have been created $this->assertSame( $revisionsCount, count($cronFile->toP4File()->getChanges()), "Line {$test['line']}: Expected 2 new revisions created." ); // verify last run attribute $this->assertSame( $timestamp, $cronFile->getRunTime(), "Line {$test['line']}: Expected value for lastRun attribute." ); // verify completed attribute $this->assertSame( true, $cronFile->getCompleted(), "Line {$test['line']}: Expected value for completed attribute." ); // ensure that returned messages are contained in the data attribute // of the cron file $messages = array( "foo", "p4cms.cron.$record published" ); $this->assertTrue( in_array($messages, $cronFile->getData()), "Line {$test['line']}: Expected messages are captured in the cron data attribute." ); // verify filetype of cron log file $this->assertSame( 'ctext', $cronFile->toP4File()->getStatus('headType'), "LINE {$test['line']}: Expected ctext filetype" ); } $revisionsCount += 2; } } /** * Test run() method where each run may not trigger all cycles. */ public function testRunDependent() { $this->_initSubscribe(); static::_resetCounters(); $timestamp = strtotime("2012-09-02 13:13:13"); $result = Cron_Model_Cron::run(null, $timestamp); $this->_verifyResult($result); $this->_verifyCounters( array('hourly' => 1, 'daily' => 1, 'weekly' => 1, 'monthly' => 1), "Expected each cron cycle was run once." ); // run the same cron and ensure no cycles were run static::_resetCounters(); $result = Cron_Model_Cron::run(null, $timestamp); $this->_verifyResult($result, 'ssss'); $this->_verifyCounters( array(), "Expected no cron cycle was run." ); // run with time shifted towards by half an hour $timestamp = strtotime("2012-09-02 13:43:13"); static::_resetCounters(); $result = Cron_Model_Cron::run(null, $timestamp); $this->_verifyResult($result, 'ssss'); $this->_verifyCounters( array(), "Unexpected cron run cycle." ); // run with time in next hour, should update only cron.hourly $timestamp = strtotime("2012-09-02 14:02:01"); static::_resetCounters(); $result = Cron_Model_Cron::run(null, $timestamp); $this->_verifyResult($result, 'esss'); $this->_verifyCounters( array('hourly' => 1), "Unexpected cron run cycle." ); // run next day $timestamp = strtotime("2012-09-03 01:00:05"); static::_resetCounters(); $result = Cron_Model_Cron::run(null, $timestamp); $this->_verifyResult($result, 'eees'); $this->_verifyCounters( array('hourly' => 1, 'daily' => 1, 'weekly' => 1), "Unexpected cron run cycle." ); // run next day $timestamp = strtotime("2012-09-04 01:00:05"); static::_resetCounters(); $result = Cron_Model_Cron::run(null, $timestamp); $this->_verifyResult($result, 'eess'); $this->_verifyCounters( array('hourly' => 1, 'daily' => 1), "Unexpected cron run cycle." ); } /** * Perform series of cron runs in constant intervals. */ public function testRunPeriodic() { $this->_initSubscribe(); $timestamp = strtotime("2012-10-31 00:00:01"); $result = Cron_Model_Cron::run(null, $timestamp); $this->_verifyResult($result); // simulate running cron for a day in 30-min intervals for ($i = 1; $i < 24; $i++) { // add 30 minutes $timestamp += 1800; static::_resetCounters(); $result = Cron_Model_Cron::run(null, $timestamp); $expected = $i % 2 === 0 ? array('hourly' => 1) : array(); $this->_verifyResult($result, count($expected) ? 'esss' : 'ssss'); $this->_verifyCounters( $expected, "Unexpected cron run cycle." ); } // simulate running cron for a month in 1-day intervals $timeStart = strtotime("2010-01-16 00:00:01"); $timeStop = strtotime("2010-02-16 00:00:00"); static::_resetCounters(); $timestamp = $timeStart; while ($timestamp < $timeStop) { $result = Cron_Model_Cron::run(null, $timestamp); $timestamp += 86400; } $this->_verifyCounters( array('hourly' => 31, 'daily' => 31, 'weekly' => 6, 'monthly' => 2), "Unexpected cron run cycle." ); // ensure cron files have ctext filetype $records = Cron_Model_Cron::fetchAll(); $this->assertSame( 4, $records->count(), "Expected 4 cron log files." ); foreach ($records as $record) { $this->assertSame( 'ctext', $record->toP4File()->getStatus('headType'), "Expected ctext filetype of {$record->getId()} file." ); } } /** * Helper function to subscribe to all cron topics. */ protected function _initSubscribe() { // prepare callback for the pubsub $callback = function ($frequency) { return function() use ($frequency) { Cron_Test_CronTest::updateCounter($frequency); return array( "foo", "p4cms.cron.$frequency published" ); }; }; P4Cms_PubSub::subscribe('p4cms.cron.hourly', $callback('hourly')); P4Cms_PubSub::subscribe('p4cms.cron.daily', $callback('daily')); P4Cms_PubSub::subscribe('p4cms.cron.weekly', $callback('weekly')); P4Cms_PubSub::subscribe('p4cms.cron.monthly', $callback('monthly')); } /** * Helper function to compare counters with expected values. * * @param array $expected list of expected counters and their values * @param type $message (optional) message for assert */ protected function _verifyCounters(array $expected, $message = '') { $counters = static::$_counters; ksort($counters); ksort($expected); $this->assertSame($expected, $counters, $message); } /** * Helper function to compare result of Cron_Model_Cron::run() method with * provided $expected values where $expected is string of length 4 where first * letter represents expected result for hourly frequency, second for daily, * third for weekly and fourth for monthly. Letters are mapped as follows: * e ... executed * s ... skipped * f ... failed * * @param array $result result array returned by the Cron_Model_Cron::run() * method * @param string|null $expected (optional) encoded expected result, defaults to 'eeee' */ protected function _verifyResult(array $result, $expected = null) { if ($expected === null) { $expected = 'eeee'; } $cron = array('hourly', 'daily', 'weekly', 'monthly'); $expectedResult = array(); $statusMap = array( 'e' => 'executed', 's' => 'skipped', 'f' => 'failed' ); foreach ($cron as $index => $frequence) { $statusShort = substr($expected, $index, 1); $expectedResult[$frequence] = $statusMap[$statusShort]; } $this->assertSame( $expectedResult, $result, "Expected cron run result." ); } /** * Resets counters. */ protected static function _resetCounters() { static::$_counters = array(); } /** * Updates specified counter by one. * * @param string $counter counter to update */ public static function updateCounter($counter) { if (!isset(static::$_counters[$counter])) { static::$_counters[$counter] = 1; } else { static::$_counters[$counter]++; } } }