diff --git a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php index 75cb579925cf..8a2a30bac64d 100644 --- a/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php +++ b/src/Illuminate/Console/Scheduling/ScheduleRunCommand.php @@ -2,6 +2,7 @@ namespace Illuminate\Console\Scheduling; +use Exception; use Illuminate\Console\Application; use Illuminate\Console\Command; use Illuminate\Console\Events\ScheduledTaskFailed; @@ -197,6 +198,10 @@ protected function runEvent($event) )); $this->eventsRan = true; + + if ($event->exitCode != 0 && ! $event->runInBackground) { + throw new Exception("Scheduled command [{$event->command}] failed with exit code [{$event->exitCode}]."); + } } catch (Throwable $e) { $this->dispatcher->dispatch(new ScheduledTaskFailed($event, $e)); diff --git a/tests/Integration/Console/Scheduling/ScheduleRunCommandTest.php b/tests/Integration/Console/Scheduling/ScheduleRunCommandTest.php new file mode 100644 index 000000000000..1ede51aa3f24 --- /dev/null +++ b/tests/Integration/Console/Scheduling/ScheduleRunCommandTest.php @@ -0,0 +1,213 @@ +app->make(Schedule::class); + $task = $schedule->exec('exit 1') + ->everyMinute(); + + // Make sure it will run regardless of schedule + $task->when(function () { + return true; + }); + + // Execute the scheduler + $this->artisan('schedule:run'); + + // Verify the event sequence + Event::assertDispatched(ScheduledTaskStarting::class); + Event::assertDispatched(ScheduledTaskFinished::class); + Event::assertDispatched(ScheduledTaskFailed::class, function ($event) use ($task) { + return $event->task === $task && + $event->exception->getMessage() === 'Scheduled command [exit 1] failed with exit code [1].'; + }); + } + + /** + * @throws BindingResolutionException + */ + public function test_failing_command_in_background_does_not_trigger_event() + { + Event::fake([ + ScheduledTaskStarting::class, + ScheduledTaskFinished::class, + ScheduledTaskFailed::class, + ]); + + // Create a schedule and add the command + $schedule = $this->app->make(Schedule::class); + $task = $schedule->exec('exit 1') + ->everyMinute() + ->runInBackground(); + + // Make sure it will run regardless of schedule + $task->when(function () { + return true; + }); + + // Execute the scheduler + $this->artisan('schedule:run'); + + // Verify the event sequence + Event::assertDispatched(ScheduledTaskStarting::class); + Event::assertDispatched(ScheduledTaskFinished::class); + Event::assertNotDispatched(ScheduledTaskFailed::class); + } + + /** + * @throws BindingResolutionException + */ + public function test_successful_command_does_not_trigger_event() + { + Event::fake([ + ScheduledTaskStarting::class, + ScheduledTaskFinished::class, + ScheduledTaskFailed::class, + ]); + + // Create a schedule and add the command + $schedule = $this->app->make(Schedule::class); + $task = $schedule->exec('exit 0') + ->everyMinute(); + + // Make sure it will run regardless of schedule + $task->when(function () { + return true; + }); + + // Execute the scheduler + $this->artisan('schedule:run'); + + // Verify the event sequence + Event::assertDispatched(ScheduledTaskStarting::class); + Event::assertDispatched(ScheduledTaskFinished::class); + Event::assertNotDispatched(ScheduledTaskFailed::class); + } + + /** + * @throws BindingResolutionException + */ + public function test_command_with_no_explicit_return_does_not_trigger_event() + { + Event::fake([ + ScheduledTaskStarting::class, + ScheduledTaskFinished::class, + ScheduledTaskFailed::class, + ]); + + // Create a schedule and add the command that just performs an action without explicit exit + $schedule = $this->app->make(Schedule::class); + $task = $schedule->exec('true') + ->everyMinute(); + + // Make sure it will run regardless of schedule + $task->when(function () { + return true; + }); + + // Execute the scheduler + $this->artisan('schedule:run'); + + // Verify the event sequence + Event::assertDispatched(ScheduledTaskStarting::class); + Event::assertDispatched(ScheduledTaskFinished::class); + Event::assertNotDispatched(ScheduledTaskFailed::class); + } + + /** + * @throws BindingResolutionException + */ + public function test_successful_command_in_background_does_not_trigger_event() + { + Event::fake([ + ScheduledTaskStarting::class, + ScheduledTaskFinished::class, + ScheduledTaskFailed::class, + ]); + + // Create a schedule and add the command + $schedule = $this->app->make(Schedule::class); + $task = $schedule->exec('exit 0') + ->everyMinute() + ->runInBackground(); + + // Make sure it will run regardless of schedule + $task->when(function () { + return true; + }); + + // Execute the scheduler + $this->artisan('schedule:run'); + + // Verify the event sequence + Event::assertDispatched(ScheduledTaskStarting::class); + Event::assertDispatched(ScheduledTaskFinished::class); + Event::assertNotDispatched(ScheduledTaskFailed::class); + } + + /** + * @throws BindingResolutionException + */ + public function test_command_with_no_explicit_return_in_background_does_not_trigger_event() + { + Event::fake([ + ScheduledTaskStarting::class, + ScheduledTaskFinished::class, + ScheduledTaskFailed::class, + ]); + + // Create a schedule and add the command that just performs an action without explicit exit + $schedule = $this->app->make(Schedule::class); + $task = $schedule->exec('true') + ->everyMinute() + ->runInBackground(); + + // Make sure it will run regardless of schedule + $task->when(function () { + return true; + }); + + // Execute the scheduler + $this->artisan('schedule:run'); + + // Verify the event sequence + Event::assertDispatched(ScheduledTaskStarting::class); + Event::assertDispatched(ScheduledTaskFinished::class); + Event::assertNotDispatched(ScheduledTaskFailed::class); + } +}