diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index 94a9b3b7..d7ecbc83 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -2,6 +2,7 @@ version: '3.8' services: laravel51: + init: true build: context: laravel51 args: diff --git a/features/fixtures/laravel51/.env b/features/fixtures/laravel51/.env index cebc8926..cf3794c3 100644 --- a/features/fixtures/laravel51/.env +++ b/features/fixtures/laravel51/.env @@ -2,15 +2,11 @@ APP_ENV=local APP_DEBUG=true APP_KEY=SomeRandomString -DB_CONNECTION=mysql -DB_HOST=127.0.0.1 -DB_DATABASE=homestead -DB_USERNAME=homestead -DB_PASSWORD=secret +DB_CONNECTION=sqlite CACHE_DRIVER=file SESSION_DRIVER=file -QUEUE_DRIVER=sync +QUEUE_DRIVER=database REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null diff --git a/features/fixtures/laravel51/Dockerfile b/features/fixtures/laravel51/Dockerfile index c0bba8aa..b5551f8c 100644 --- a/features/fixtures/laravel51/Dockerfile +++ b/features/fixtures/laravel51/Dockerfile @@ -16,4 +16,7 @@ COPY --from=composer:2.2 /usr/bin/composer /usr/local/bin/composer RUN composer install --no-dev RUN php artisan key:generate +# create database & apply migrations +RUN touch database/database.sqlite && php artisan migrate --no-interaction + CMD php artisan serve --port=8000 --host=0.0.0.0 diff --git a/features/fixtures/laravel51/app/Http/routes.php b/features/fixtures/laravel51/app/Http/routes.php index dfc4b2d1..f1e8cbb5 100644 --- a/features/fixtures/laravel51/app/Http/routes.php +++ b/features/fixtures/laravel51/app/Http/routes.php @@ -61,6 +61,14 @@ return view('handlederror'); }); +Route::get('/queue/unhandled', function () { + Queue::push(new \App\Jobs\UnhandledJob()); +}); + +Route::get('/queue/handled', function () { + Queue::push(new \App\Jobs\HandledJob()); +}); + /** * Return some diagnostics if an OOM did not happen when it should have. * diff --git a/features/fixtures/laravel51/app/Jobs/HandledJob.php b/features/fixtures/laravel51/app/Jobs/HandledJob.php new file mode 100644 index 00000000..b706f6d7 --- /dev/null +++ b/features/fixtures/laravel51/app/Jobs/HandledJob.php @@ -0,0 +1,22 @@ +bigIncrements('id'); + $table->string('queue'); + $table->longText('payload'); + $table->tinyInteger('attempts')->unsigned(); + $table->tinyInteger('reserved')->unsigned(); + $table->unsignedInteger('reserved_at')->nullable(); + $table->unsignedInteger('available_at'); + $table->unsignedInteger('created_at'); + $table->index(['queue', 'reserved', 'reserved_at']); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('jobs'); + } +} diff --git a/features/fixtures/laravel51/database/migrations/2022_12_06_094156_create_failed_jobs_table.php b/features/fixtures/laravel51/database/migrations/2022_12_06_094156_create_failed_jobs_table.php new file mode 100644 index 00000000..3d733b6b --- /dev/null +++ b/features/fixtures/laravel51/database/migrations/2022_12_06_094156_create_failed_jobs_table.php @@ -0,0 +1,33 @@ +increments('id'); + $table->text('connection'); + $table->text('queue'); + $table->longText('payload'); + $table->timestamp('failed_at')->useCurrent(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('failed_jobs'); + } +} diff --git a/features/lib/laravel.rb b/features/lib/laravel.rb index 4f9d6301..a0eb9a91 100644 --- a/features/lib/laravel.rb +++ b/features/lib/laravel.rb @@ -31,6 +31,20 @@ def major_version Integer(/^(?:laravel|lumen)(\d)/.match(fixture)[1]) end + def version + # e.g. laravel56 -> 56, lumen8 -> 8 + raw_digits = /^(?:laravel|lumen)(\d+)/.match(fixture)[1] + + # convert the raw digits to an array of: [major, minor, patch] + # in practice we only have 1 or 2 digits in our fixture names, so fill the + # rest with 0s to make sure Gem::Version doesn't get confused + # e.g. ['5', '6'] -> ['5', '6', '0'], ['8'] -> ['8', '0', '0'] + version_string = raw_digits.chars + version_string.fill("0", version_string.length..2) + + Gem::Version.new(version_string.join(".")) + end + def lumen? fixture.start_with?("lumen") end @@ -46,6 +60,17 @@ def supports_sessions? true end + def queue_worker_command(tries:) + # the command to run the queue worker was 'queue:listen' but changed to + # 'queue:work' in Laravel 5.3 ('queue:work' exists on older Laravels, but + # is not quite equivalent) + if version < '5.3.0' + "php artisan queue:listen --tries=#{tries}" + else + "php artisan queue:work --tries=#{tries}" + end + end + private def load_port_from_docker_compose diff --git a/features/queues.feature b/features/queues.feature index b09737a1..4d58bc9e 100644 --- a/features/queues.feature +++ b/features/queues.feature @@ -1,6 +1,6 @@ Feature: Queue support -@not-laravel-latest @not-laravel51 @not-lumen8 +@not-laravel-latest @not-lumen8 Scenario: Unhandled exceptions are delivered from queues Given I start the laravel fixture And I start the laravel queue worker @@ -9,22 +9,23 @@ Scenario: Unhandled exceptions are delivered from queues Then the error is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier And the exception "errorClass" equals "RuntimeException" And the exception "message" equals "uh oh :o" - And the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" - And the event "metaData.job.queue" equals "default" - And the event "metaData.job.attempts" equals 1 - And the event "metaData.job.connection" equals "database" - And the event "metaData.job.resolved" equals "App\Jobs\UnhandledJob" - And the event "app.type" equals "Queue" - And the event "context" equals "App\Jobs\UnhandledJob" + And on Laravel versions > 5.1 the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" + And on Laravel versions > 5.1 the event "metaData.job.queue" equals "default" + And on Laravel versions > 5.1 the event "metaData.job.attempts" equals 1 + And on Laravel versions > 5.1 the event "metaData.job.connection" equals "database" + And on Laravel versions > 5.1 the event "metaData.job.resolved" equals "App\Jobs\UnhandledJob" + And on Laravel versions > 5.1 the event "app.type" equals "Queue" + And on Laravel versions > 5.1 the event "context" equals "App\Jobs\UnhandledJob" + And on Laravel versions == 5.1 the event "metaData.job" is null And the event "severity" equals "error" And the event "unhandled" is true And the event "severityReason.type" equals "unhandledExceptionMiddleware" And the event "severityReason.attributes.framework" equals "Laravel" -@not-laravel-latest @not-laravel51 @not-lumen8 +@not-laravel-latest @not-lumen8 Scenario: Unhandled exceptions are delivered from queued jobs with multiple attmpts Given I start the laravel fixture - And I start the laravel queue worker + And I start the laravel queue worker with --tries=3 When I navigate to the route "/queue/unhandled?tries=3" And I wait to receive 3 errors @@ -32,13 +33,14 @@ Scenario: Unhandled exceptions are delivered from queued jobs with multiple attm Then the error is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier And the exception "errorClass" equals "RuntimeException" And the exception "message" equals "uh oh :o" - And the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" - And the event "metaData.job.queue" equals "default" - And the event "metaData.job.attempts" equals 1 - And the event "metaData.job.connection" equals "database" - And the event "metaData.job.resolved" equals "App\Jobs\UnhandledJob" - And the event "app.type" equals "Queue" - And the event "context" equals "App\Jobs\UnhandledJob" + And on Laravel versions > 5.1 the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" + And on Laravel versions > 5.1 the event "metaData.job.queue" equals "default" + And on Laravel versions > 5.1 the event "metaData.job.attempts" equals 1 + And on Laravel versions > 5.1 the event "metaData.job.connection" equals "database" + And on Laravel versions > 5.1 the event "metaData.job.resolved" equals "App\Jobs\UnhandledJob" + And on Laravel versions > 5.1 the event "app.type" equals "Queue" + And on Laravel versions > 5.1 the event "context" equals "App\Jobs\UnhandledJob" + And on Laravel versions == 5.1 the event "metaData.job" is null And the event "severity" equals "error" And the event "unhandled" is true And the event "severityReason.type" equals "unhandledExceptionMiddleware" @@ -49,13 +51,14 @@ Scenario: Unhandled exceptions are delivered from queued jobs with multiple attm Then the error is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier And the exception "errorClass" equals "RuntimeException" And the exception "message" equals "uh oh :o" - And the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" - And the event "metaData.job.queue" equals "default" - And the event "metaData.job.attempts" equals 2 - And the event "metaData.job.connection" equals "database" - And the event "metaData.job.resolved" equals "App\Jobs\UnhandledJob" - And the event "app.type" equals "Queue" - And the event "context" equals "App\Jobs\UnhandledJob" + And on Laravel versions > 5.1 the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" + And on Laravel versions > 5.1 the event "metaData.job.queue" equals "default" + And on Laravel versions > 5.1 the event "metaData.job.attempts" equals 2 + And on Laravel versions > 5.1 the event "metaData.job.connection" equals "database" + And on Laravel versions > 5.1 the event "metaData.job.resolved" equals "App\Jobs\UnhandledJob" + And on Laravel versions > 5.1 the event "app.type" equals "Queue" + And on Laravel versions > 5.1 the event "context" equals "App\Jobs\UnhandledJob" + And on Laravel versions == 5.1 the event "metaData.job" is null And the event "severity" equals "error" And the event "unhandled" is true And the event "severityReason.type" equals "unhandledExceptionMiddleware" @@ -66,19 +69,20 @@ Scenario: Unhandled exceptions are delivered from queued jobs with multiple attm Then the error is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier And the exception "errorClass" equals "RuntimeException" And the exception "message" equals "uh oh :o" - And the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" - And the event "metaData.job.queue" equals "default" - And the event "metaData.job.attempts" equals 3 - And the event "metaData.job.connection" equals "database" - And the event "metaData.job.resolved" equals "App\Jobs\UnhandledJob" - And the event "app.type" equals "Queue" - And the event "context" equals "App\Jobs\UnhandledJob" + And on Laravel versions > 5.1 the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" + And on Laravel versions > 5.1 the event "metaData.job.queue" equals "default" + And on Laravel versions > 5.1 the event "metaData.job.attempts" equals 3 + And on Laravel versions > 5.1 the event "metaData.job.connection" equals "database" + And on Laravel versions > 5.1 the event "metaData.job.resolved" equals "App\Jobs\UnhandledJob" + And on Laravel versions > 5.1 the event "app.type" equals "Queue" + And on Laravel versions > 5.1 the event "context" equals "App\Jobs\UnhandledJob" + And on Laravel versions == 5.1 the event "metaData.job" is null And the event "severity" equals "error" And the event "unhandled" is true And the event "severityReason.type" equals "unhandledExceptionMiddleware" And the event "severityReason.attributes.framework" equals "Laravel" -@not-laravel-latest @not-laravel51 @not-lumen8 +@not-laravel-latest @not-lumen8 Scenario: Handled exceptions are delivered from queues Given I start the laravel fixture And I start the laravel queue worker @@ -87,13 +91,14 @@ Scenario: Handled exceptions are delivered from queues Then the error is valid for the error reporting API version "4.0" for the "Bugsnag Laravel" notifier And the exception "errorClass" equals "Exception" And the exception "message" equals "Handled :)" - And the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" - And the event "metaData.job.queue" equals "default" - And the event "metaData.job.attempts" equals 1 - And the event "metaData.job.connection" equals "database" - And the event "metaData.job.resolved" equals "App\Jobs\HandledJob" - And the event "app.type" equals "Queue" - And the event "context" equals "App\Jobs\HandledJob" + And on Laravel versions > 5.1 the event "metaData.job.name" equals "Illuminate\Queue\CallQueuedHandler@call" + And on Laravel versions > 5.1 the event "metaData.job.queue" equals "default" + And on Laravel versions > 5.1 the event "metaData.job.attempts" equals 1 + And on Laravel versions > 5.1 the event "metaData.job.connection" equals "database" + And on Laravel versions > 5.1 the event "metaData.job.resolved" equals "App\Jobs\HandledJob" + And on Laravel versions > 5.1 the event "app.type" equals "Queue" + And on Laravel versions > 5.1 the event "context" equals "App\Jobs\HandledJob" + And on Laravel versions == 5.1 the event "metaData.job" is null And the event "severity" equals "warning" And the event "unhandled" is false And the event "severityReason.type" equals "handledException" diff --git a/features/steps/laravel_steps.rb b/features/steps/laravel_steps.rb index 7777928e..1f44ad7e 100644 --- a/features/steps/laravel_steps.rb +++ b/features/steps/laravel_steps.rb @@ -29,7 +29,11 @@ def exec(service, command, detach: false) end When("I start the laravel queue worker") do - Maze::Docker.exec(Laravel.fixture, "php artisan queue:work", detach: true) + step("I start the laravel queue worker with --tries=1") +end + +When("I start the laravel queue worker with --tries={int}") do |tries| + Maze::Docker.exec(Laravel.fixture, Laravel.queue_worker_command(tries: tries), detach: true) end When("I navigate to the route {string}") do |route| @@ -99,3 +103,20 @@ def exec(service, command, detach: false) step("the session payload field '#{path}' matches the regex '^((\\d+\\.){2}\\d+|\\d\\.x-dev)$'") end + +# conditionally run a step if the laravel version matches a specified version +# +# e.g. this will only check app.type on Laravel 5.2 and above: +# on Laravel versions > 5.1 the event "app.type" equals "Queue" +Then(/^on Laravel versions (>=?|<=?|==) ([0-9.]+) (.*)$/) do |operator, version, step_to_run| + should_run_step = Laravel.version.send(operator, version) + + # make sure this step is debuggable! + $logger.debug("Laravel v#{Laravel.version} #{operator} #{version}? #{should_run_step}") + + if should_run_step + step(step_to_run) + else + $logger.info("Skipping step on Laravel v#{Laravel.version}: #{step_to_run}") + end +end