Skip to content

[12.x] ScheduledTaskFailed not dispatched on scheduled forground task fails #55624

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: 12.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Illuminate/Console/Scheduling/ScheduleRunCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Illuminate\Console\Scheduling;

use Exception;
use Illuminate\Console\Application;
use Illuminate\Console\Command;
use Illuminate\Console\Events\ScheduledTaskFailed;
Expand Down Expand Up @@ -196,6 +197,10 @@ protected function runEvent($event)
round(microtime(true) - $start, 2)
));

if ($event->exitCode != 0 && ! $event->runInBackground) {
throw new Exception("Scheduled command [{$event->command}] failed with exit code [{$event->exitCode}].");
}

$this->eventsRan = true;
} catch (Throwable $e) {
$this->dispatcher->dispatch(new ScheduledTaskFailed($event, $e));
Expand Down
216 changes: 216 additions & 0 deletions tests/Integration/Console/Scheduling/ScheduleRunCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
<?php

namespace Illuminate\Tests\Integration\Console\Scheduling;

use Illuminate\Console\Events\ScheduledTaskFailed;
use Illuminate\Console\Events\ScheduledTaskFinished;
use Illuminate\Console\Events\ScheduledTaskStarting;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Event;
use Orchestra\Testbench\TestCase;

class ScheduleRunCommandTest extends TestCase
{
protected function setUp(): void
{
parent::setUp();
Carbon::setTestNow(Carbon::now());
}

protected function tearDown(): void
{
parent::tearDown();
Carbon::setTestNow();
}

/**
* @throws BindingResolutionException
*/
public function test_failing_command_in_foreground_triggers_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()
->withoutOverlapping();

// 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()
->withoutOverlapping();

// 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()
->withoutOverlapping();

// 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);
}
}