JobTest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. <?php namespace GO\Job\Tests;
  2. use GO\Job;
  3. use PHPUnit\Framework\TestCase;
  4. class JobTest extends TestCase
  5. {
  6. public function testShouldAlwaysGenerateAnId()
  7. {
  8. $job1 = new Job('ls');
  9. $this->assertTrue(is_string($job1->getId()));
  10. $job2 = new Job(function () {
  11. return true;
  12. });
  13. $this->assertTrue(is_string($job2->getId()));
  14. $job3 = new Job(['MyClass', 'myMethod']);
  15. $this->assertTrue(is_string($job3->getId()));
  16. }
  17. public function testShouldGenerateIdFromSignature()
  18. {
  19. $job1 = new Job('ls');
  20. $this->assertEquals(md5('ls'), $job1->getId());
  21. $job2 = new Job('whoami');
  22. $this->assertNotEquals($job1->getId(), $job2->getId());
  23. $job3 = new Job(['MyClass', 'myMethod']);
  24. $this->assertNotEquals($job1->getId(), $job3->getId());
  25. }
  26. public function testShouldAllowCustomId()
  27. {
  28. $job = new Job('ls', [], 'aCustomId');
  29. $this->assertNotEquals(md5('ls'), $job->getId());
  30. $this->assertEquals('aCustomId', $job->getId());
  31. $job2 = new Job(['MyClass', 'myMethod'], null, 'myCustomId');
  32. $this->assertEquals('myCustomId', $job2->getId());
  33. }
  34. public function testShouldKnowIfDue()
  35. {
  36. $job1 = new Job('ls');
  37. $this->assertTrue($job1->isDue());
  38. $job2 = new Job('ls');
  39. $job2->at('* * * * *');
  40. $this->assertTrue($job2->isDue());
  41. $job3 = new Job('ls');
  42. $job3->at('10 * * * *');
  43. $this->assertTrue($job3->isDue(\DateTime::createFromFormat('i', '10')));
  44. $this->assertFalse($job3->isDue(\DateTime::createFromFormat('i', '12')));
  45. }
  46. public function testShouldKnowIfCanRunInBackground()
  47. {
  48. $job = new Job('ls');
  49. $this->assertTrue($job->canRunInBackground());
  50. $job2 = new Job(function () {
  51. return "I can't run in background";
  52. });
  53. $this->assertFalse($job2->canRunInBackground());
  54. }
  55. public function testShouldForceTheJobToRunInForeground()
  56. {
  57. $job = new Job('ls');
  58. $this->assertTrue($job->canRunInBackground());
  59. $this->assertFalse($job->inForeground()->canRunInBackground());
  60. }
  61. public function testShouldReturnCompiledJobCommand()
  62. {
  63. $job1 = new Job('ls');
  64. $this->assertEquals('ls', $job1->inForeground()->compile());
  65. $fn = function () {
  66. return true;
  67. };
  68. $job2 = new Job($fn);
  69. $this->assertEquals($fn, $job2->compile());
  70. }
  71. public function testShouldCompileWithArguments()
  72. {
  73. $job = new Job('ls', [
  74. '-l' => null,
  75. '-arg' => 'value',
  76. ]);
  77. $this->assertEquals("ls '-l' '-arg' 'value'", $job->inForeground()->compile());
  78. }
  79. public function testShouldCompileCommandInBackground()
  80. {
  81. $job1 = new Job('ls');
  82. $job1->at('* * * * *');
  83. $this->assertEquals('(ls) > /dev/null 2>&1 &', $job1->compile());
  84. }
  85. public function testShouldRunInBackground()
  86. {
  87. // This script has a 5 seconds sleep
  88. $command = PHP_BINARY . ' ' . __DIR__ . '/../async_job.php';
  89. $job = new Job($command);
  90. $startTime = microtime(true);
  91. $job->at('* * * * *')->run();
  92. $endTime = microtime(true);
  93. $this->assertTrue(5 > ($endTime - $startTime));
  94. $startTime = microtime(true);
  95. $job->at('* * * * *')->inForeground()->run();
  96. $endTime = microtime(true);
  97. $this->assertTrue(($endTime - $startTime) >= 5);
  98. }
  99. public function testShouldRunInForegroundIfSendsEmails()
  100. {
  101. $job = new Job('ls');
  102. $job->email('test@mail.com');
  103. $this->assertFalse($job->canRunInBackground());
  104. }
  105. public function testShouldAcceptSingleOrMultipleEmails()
  106. {
  107. $job = new Job('ls');
  108. $this->assertInstanceOf(Job::class, $job->email('test@mail.com'));
  109. $this->assertInstanceOf(Job::class, $job->email(['test@mail.com', 'other@mail.com']));
  110. }
  111. public function testShouldFailIfEmailInputIsNotStringOrArray()
  112. {
  113. $this->expectException(\InvalidArgumentException::class);
  114. $job = new Job('ls');
  115. $job->email(1);
  116. }
  117. public function testShouldAcceptEmailConfigurationAndItShouldBeChainable()
  118. {
  119. $job = new Job('ls');
  120. $this->assertInstanceOf(Job::class, $job->configure([
  121. 'email' => [],
  122. ]));
  123. }
  124. public function testShouldFailIfEmailConfigurationIsNotArray()
  125. {
  126. $this->expectException(\InvalidArgumentException::class);
  127. $job = new Job('ls');
  128. $job->configure([
  129. 'email' => 123,
  130. ]);
  131. }
  132. public function testShouldCreateLockFileIfOnlyOne()
  133. {
  134. $command = PHP_BINARY . ' ' . __DIR__ . '/../async_job.php';
  135. $job = new Job($command);
  136. // Default temp dir
  137. $tmpDir = sys_get_temp_dir();
  138. $lockFile = $tmpDir . '/' . $job->getId() . '.lock';
  139. @unlink($lockFile);
  140. $this->assertFalse(file_exists($lockFile));
  141. $job->onlyOne()->run();
  142. $this->assertTrue(file_exists($lockFile));
  143. }
  144. public function testShouldCreateLockFilesInCustomPath()
  145. {
  146. $command = PHP_BINARY . ' ' . __DIR__ . '/../async_job.php';
  147. $job = new Job($command);
  148. // Default temp dir
  149. $tmpDir = __DIR__ . '/../tmp';
  150. $lockFile = $tmpDir . '/' . $job->getId() . '.lock';
  151. @unlink($lockFile);
  152. $this->assertFalse(file_exists($lockFile));
  153. $job->onlyOne($tmpDir)->run();
  154. $this->assertTrue(file_exists($lockFile));
  155. }
  156. public function testShouldRemoveLockFileAfterRunningClosures()
  157. {
  158. $job = new Job(function () {
  159. sleep(3);
  160. });
  161. // Default temp dir
  162. $tmpDir = __DIR__ . '/../tmp';
  163. $lockFile = $tmpDir . '/' . $job->getId() . '.lock';
  164. $job->onlyOne($tmpDir)->run();
  165. $this->assertFalse(file_exists($lockFile));
  166. }
  167. public function testShouldRemoveLockFileAfterRunningCommands()
  168. {
  169. $command = PHP_BINARY . ' ' . __DIR__ . '/../async_job.php';
  170. $job = new Job($command);
  171. // Default temp dir
  172. $tmpDir = __DIR__ . '/../tmp';
  173. $lockFile = $tmpDir . '/' . $job->getId() . '.lock';
  174. $job->onlyOne($tmpDir)->run();
  175. sleep(1);
  176. $this->assertTrue(file_exists($lockFile));
  177. sleep(5);
  178. $this->assertFalse(file_exists($lockFile));
  179. }
  180. public function testShouldKnowIfOverlapping()
  181. {
  182. $command = PHP_BINARY . ' ' . __DIR__ . '/../async_job.php';
  183. $job = new Job($command);
  184. $this->assertFalse($job->isOverlapping());
  185. $tmpDir = __DIR__ . '/../tmp';
  186. $job->onlyOne($tmpDir)->run();
  187. sleep(1);
  188. $this->assertTrue($job->isOverlapping());
  189. sleep(5);
  190. $this->assertFalse($job->isOverlapping());
  191. }
  192. public function testShouldNotRunIfOverlapping()
  193. {
  194. $command = PHP_BINARY . ' ' . __DIR__ . '/../async_job.php';
  195. $job = new Job($command);
  196. $this->assertFalse($job->isOverlapping());
  197. $tmpDir = __DIR__ . '/../tmp';
  198. $job->onlyOne($tmpDir);
  199. sleep(1);
  200. $this->assertTrue($job->run());
  201. $this->assertFalse($job->run());
  202. sleep(6);
  203. $this->assertTrue($job->run());
  204. }
  205. public function testShouldRunIfOverlappingCallbackReturnsTrue()
  206. {
  207. $command = PHP_BINARY . ' ' . __DIR__ . '/../async_job.php';
  208. $job = new Job($command);
  209. $this->assertFalse($job->isOverlapping());
  210. $tmpDir = __DIR__ . '/../tmp';
  211. $job->onlyOne($tmpDir, function ($lastExecution) {
  212. return time() - $lastExecution > 2;
  213. })->run();
  214. // The job should not run as it is overlapping
  215. $this->assertFalse($job->run());
  216. sleep(3);
  217. // The job should run now as the function should now return true,
  218. // while it's still being executed
  219. $lockFile = $tmpDir . '/' . $job->getId() . '.lock';
  220. $this->assertTrue(file_exists($lockFile));
  221. $this->assertTrue($job->run());
  222. }
  223. public function testShouldAcceptTempDirInConfiguration()
  224. {
  225. $command = PHP_BINARY . ' ' . __DIR__ . '/../async_job.php';
  226. $job = new Job($command);
  227. $tmpDir = __DIR__ . '/../tmp';
  228. $job->configure([
  229. 'tempDir' => $tmpDir,
  230. ])->onlyOne()->run();
  231. sleep(1);
  232. $this->assertTrue(file_exists($tmpDir . '/' . $job->getId() . '.lock'));
  233. }
  234. public function testWhenMethodShouldBeChainable()
  235. {
  236. $job = new Job('ls');
  237. $this->assertInstanceOf(Job::class, $job->when(function () {
  238. return true;
  239. }));
  240. }
  241. public function testShouldNotRunIfTruthTestFails()
  242. {
  243. $job = new Job('ls');
  244. $this->assertFalse($job->when(function () {
  245. return false;
  246. })->run());
  247. $this->assertTrue($job->when(function () {
  248. return true;
  249. })->run());
  250. }
  251. public function testShouldReturnOutputOfJobExecution()
  252. {
  253. $job1 = new Job(function () {
  254. echo 'hi';
  255. });
  256. $job1->run();
  257. $this->assertEquals('hi', $job1->getOutput());
  258. $job2 = new Job(function () {
  259. return 'hello';
  260. });
  261. $job2->run();
  262. $this->assertEquals('hello', $job2->getOutput());
  263. $command = PHP_BINARY . ' ' . __DIR__ . '/../test_job.php';
  264. $job3 = new Job($command);
  265. $job3->inForeground()->run();
  266. $this->assertEquals(['hi'], $job3->getOutput());
  267. }
  268. public function testShouldRunCallbackBeforeJobExecution()
  269. {
  270. $job = new Job(function () {
  271. return 'Job for testing before function';
  272. });
  273. $callbackWasExecuted = false;
  274. $outputWasSet = false;
  275. $job->before(function () use ($job, &$callbackWasExecuted, &$outputWasSet) {
  276. $callbackWasExecuted = true;
  277. $outputWasSet = ! is_null($job->getOutput());
  278. })->run();
  279. $this->assertTrue($callbackWasExecuted);
  280. $this->assertFalse($outputWasSet);
  281. }
  282. public function testShouldRunCallbackAfterJobExecution()
  283. {
  284. $job = new Job(function () {
  285. $visitors = 1000;
  286. return 'Daily visitors: ' . $visitors;
  287. });
  288. $jobResult = null;
  289. $job->then(function ($output) use (&$jobResult) {
  290. $jobResult = $output;
  291. })->run();
  292. $this->assertEquals($jobResult, $job->getOutput());
  293. $command = PHP_BINARY . ' ' . __DIR__ . '/../test_job.php';
  294. $job2 = new Job($command);
  295. $job2Result = null;
  296. $job2->then(function ($output) use (&$job2Result) {
  297. $job2Result = $output;
  298. }, true)->run();
  299. // Commands in background should return an empty string
  300. $this->assertTrue(empty($job2Result));
  301. $job2Result = null;
  302. $job2->then(function ($output) use (&$job2Result) {
  303. $job2Result = $output;
  304. })->inForeground()->run();
  305. $this->assertTrue(! empty($job2Result) &&
  306. $job2Result === $job2->getOutput());
  307. }
  308. public function testThenMethodShouldPassReturnCode()
  309. {
  310. $command_success = PHP_BINARY . ' ' . __DIR__ . '/../test_job.php';
  311. $command_fail = $command_success . ' fail';
  312. $run = function ($command) {
  313. $job = new Job($command);
  314. $testReturnCode = null;
  315. $job->then(function ($output, $returnCode) use (&$testReturnCode, &$testOutput) {
  316. $testReturnCode = $returnCode;
  317. })->run();
  318. return $testReturnCode;
  319. };
  320. $this->assertEquals(0, $run($command_success));
  321. $this->assertNotEquals(0, $run($command_fail));
  322. }
  323. public function testThenMethodShouldBeChainable()
  324. {
  325. $job = new Job('ls');
  326. $this->assertInstanceOf(Job::class, $job->then(function () {
  327. return true;
  328. }));
  329. }
  330. public function testShouldDefaultExecutionInForegroundIfMethodThenIsDefined()
  331. {
  332. $job = new Job('ls');
  333. $job->then(function () {
  334. return true;
  335. });
  336. $this->assertFalse($job->canRunInBackground());
  337. }
  338. public function testShouldAllowForcingTheJobToRunInBackgroundIfMethodThenIsDefined()
  339. {
  340. // This is a use case when you want to execute a callback every time your
  341. // job is executed, but you don't care about the output of the job
  342. $job = new Job('ls');
  343. $job->then(function () {
  344. return true;
  345. }, true);
  346. $this->assertTrue($job->canRunInBackground());
  347. }
  348. }