diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 2598c9331..db036ca1f 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -21,6 +21,7 @@ steps: use-aliases: true env: RUBY_TEST_VERSION: "1.9.3" + BUNDLE_VERSION: "1.12.0" - label: ':ruby: Ruby 2.7 unit tests' timeout_in_minutes: 30 @@ -85,7 +86,7 @@ steps: env: RUBY_TEST_VERSION: "2.5" - - label: ':sidekiq: Sidekiq 5 tests' + - label: ':sidekiq: Sidekiq 6 tests' timeout_in_minutes: 30 plugins: docker-compose#v3.1.0: @@ -94,7 +95,7 @@ steps: command: ["features/sidekiq.feature", "--tags", "not @wip"] env: RUBY_TEST_VERSION: "2.5" - SIDEKIQ_VERSION: "~> 5" + SIDEKIQ_VERSION: "6" - wait @@ -115,6 +116,7 @@ steps: use-aliases: true env: RUBY_TEST_VERSION: "2.0" + BUNDLE_VERSION: "1.12.0" concurrency: 4 concurrency_group: 'ruby/unit-tests' @@ -126,6 +128,7 @@ steps: use-aliases: true env: RUBY_TEST_VERSION: "2.1" + BUNDLE_VERSION: "1.12.0" concurrency: 4 concurrency_group: 'ruby/unit-tests' @@ -137,6 +140,7 @@ steps: use-aliases: true env: RUBY_TEST_VERSION: "2.2" + BUNDLE_VERSION: "1.12.0" GEMSETS: "test sidekiq" concurrency: 4 concurrency_group: 'ruby/unit-tests' @@ -149,6 +153,7 @@ steps: use-aliases: true env: RUBY_TEST_VERSION: "2.3" + BUNDLE_VERSION: "1.12.0" GEMSETS: "test sidekiq" concurrency: 4 concurrency_group: 'ruby/unit-tests' @@ -189,6 +194,17 @@ steps: concurrency: 4 concurrency_group: 'ruby/unit-tests' + - label: ':ruby: Ruby 3.0 unit tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-unit-tests + use-aliases: true + env: + RUBY_TEST_VERSION: "3.0" + concurrency: 4 + concurrency_group: 'ruby/unit-tests' + - label: ':ruby: Ruby 1.9 plain tests' timeout_in_minutes: 30 plugins: @@ -198,6 +214,7 @@ steps: command: ["features/plain_features", "--tags", "not @wip"] env: RUBY_TEST_VERSION: "1.9.3" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -210,6 +227,7 @@ steps: command: ["features/plain_features", "--tags", "not @wip"] env: RUBY_TEST_VERSION: "2.0" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -222,6 +240,7 @@ steps: command: ["features/plain_features", "--tags", "not @wip"] env: RUBY_TEST_VERSION: "2.1" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -234,6 +253,7 @@ steps: command: ["features/plain_features", "--tags", "not @wip"] env: RUBY_TEST_VERSION: "2.2" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -285,6 +305,18 @@ steps: concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' + - label: ':ruby: Ruby 3.0 plain tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ["features/plain_features/", "--tags", "not @wip"] + env: + RUBY_TEST_VERSION: "3.0" + concurrency: 8 + concurrency_group: 'ruby/slow-maze-runner-tests' + - label: ':sidekiq: Sidekiq 2 tests' timeout_in_minutes: 30 plugins: @@ -294,7 +326,7 @@ steps: command: ["features/sidekiq.feature", "--tags", "not @wip"] env: RUBY_TEST_VERSION: "2.5" - SIDEKIQ_VERSION: "~> 2" + SIDEKIQ_VERSION: "2" concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' @@ -307,7 +339,7 @@ steps: command: ["features/sidekiq.feature", "--tags", "not @wip"] env: RUBY_TEST_VERSION: "2.5" - SIDEKIQ_VERSION: "~> 3" + SIDEKIQ_VERSION: "3" concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' @@ -320,10 +352,21 @@ steps: command: ["features/sidekiq.feature", "--tags", "not @wip"] env: RUBY_TEST_VERSION: "2.5" - SIDEKIQ_VERSION: "~> 4" + SIDEKIQ_VERSION: "4" concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':sidekiq: Sidekiq 5 tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ["features/sidekiq.feature", "--tags", "not @wip"] + env: + RUBY_TEST_VERSION: "2.5" + SIDEKIQ_VERSION: "5" + - label: ':rails: Rails 3 Ruby 2.0 tests' timeout_in_minutes: 30 plugins: @@ -334,6 +377,7 @@ steps: env: RUBY_TEST_VERSION: "2.0" RAILS_VERSION: "3" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -347,6 +391,7 @@ steps: env: RUBY_TEST_VERSION: "2.1" RAILS_VERSION: "3" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -360,6 +405,7 @@ steps: env: RUBY_TEST_VERSION: "2.2" RAILS_VERSION: "3" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -412,6 +458,7 @@ steps: env: RUBY_TEST_VERSION: "2.2" RAILS_VERSION: "4" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -438,6 +485,7 @@ steps: env: RUBY_TEST_VERSION: "2.2" RAILS_VERSION: "5" + BUNDLE_VERSION: "1.12.0" concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' @@ -519,6 +567,32 @@ steps: concurrency: 8 concurrency_group: 'ruby/slow-maze-runner-tests' + - label: ':rails: Rails 6 Ruby 3.0 tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ["features/rails_features/", "--tags", "@rails6 and not @wip"] + env: + RUBY_TEST_VERSION: "3.0" + RAILS_VERSION: "6" + concurrency: 8 + concurrency_group: 'ruby/slow-maze-runner-tests' + + - label: ':rails: Rails integrations Ruby 3.0 tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ["features/rails_features/", "--tags", "@rails_integrations"] + env: + RUBY_TEST_VERSION: "3.0" + RAILS_VERSION: "_integrations" + concurrency: 8 + concurrency_group: 'ruby/slow-maze-runner-tests' + - label: ':clipboard: Rake Ruby 1.9 tests' timeout_in_minutes: 30 plugins: @@ -627,6 +701,18 @@ steps: concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':clipboard: Rake Ruby 3.0 tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ['features/rake.feature', '--tags', 'not @wip'] + env: + RUBY_TEST_VERSION: "3.0" + concurrency: 4 + concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':postbox: Mailman Ruby 2.0 tests' timeout_in_minutes: 30 plugins: @@ -638,8 +724,6 @@ steps: RUBY_TEST_VERSION: '2.0' concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' - soft_fail: - - exit_status: "*" - label: ':postbox: Mailman Ruby 2.1 tests' timeout_in_minutes: 30 @@ -725,6 +809,18 @@ steps: concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':postbox: Mailman Ruby 3.0 tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ['features/mailman.feature', '--tags', 'not @wip'] + env: + RUBY_TEST_VERSION: "3.0" + concurrency: 4 + concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':key: Que Ruby 2.0 tests' timeout_in_minutes: 30 plugins: @@ -736,8 +832,6 @@ steps: RUBY_TEST_VERSION: '2.0' concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' - soft_fail: - - exit_status: "*" - label: ':key: Que Ruby 2.1 tests' timeout_in_minutes: 30 @@ -823,6 +917,18 @@ steps: concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':key: Que Ruby 3.0 tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ['features/que.feature', '--tags', 'not @wip'] + env: + RUBY_TEST_VERSION: "3.0" + concurrency: 4 + concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':bed: Rack 1 Ruby 1.9 tests' timeout_in_minutes: 30 plugins: @@ -940,6 +1046,19 @@ steps: concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':bed: Rack 1 Ruby 3.0 tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ['features/rack.feature', '--tags', 'not @wip'] + env: + RUBY_TEST_VERSION: "3.0" + RACK_VERSION: '1' + concurrency: 4 + concurrency_group: 'ruby/integrations-maze-runner-tests' + - label: ':bed: Rack 2 Ruby 2.2 tests' timeout_in_minutes: 30 plugins: @@ -1017,3 +1136,16 @@ steps: RACK_VERSION: '2' concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' + + - label: ':bed: Rack 2 Ruby 3.0 tests' + timeout_in_minutes: 30 + plugins: + docker-compose#v3.1.0: + run: ruby-maze-runner + use-aliases: true + command: ['features/rack.feature', '--tags', 'not @wip'] + env: + RUBY_TEST_VERSION: "3.0" + RACK_VERSION: '2' + concurrency: 4 + concurrency_group: 'ruby/integrations-maze-runner-tests' diff --git a/CHANGELOG.md b/CHANGELOG.md index 12375b161..fe10db57e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ Changelog ========= +## 6.19.0 (6 January 2021) + +### Enhancements + +* Exception messages will be truncated if they have a length greater than 3,072 + | [#636](https://github.com/bugsnag/bugsnag-ruby/pull/636) + | [joshuapinter](https://github.com/joshuapinter) + +* Breadcrumb metadata can now contain any type + | [#648](https://github.com/bugsnag/bugsnag-ruby/pull/648) + ## 6.18.0 (27 October 2020) ### Enhancements diff --git a/Gemfile b/Gemfile index 1b7040d03..188f9df79 100644 --- a/Gemfile +++ b/Gemfile @@ -9,13 +9,20 @@ group :test, optional: true do gem 'yard', '~> 0.9.25' gem 'pry' gem 'addressable', '~> 2.3.8' + if ruby_version >= Gem::Version.new('2.2.2') gem 'delayed_job', ruby_version < Gem::Version.new('2.5.0') ? '4.1.7': '>4.1.7' gem 'i18n', ruby_version <= Gem::Version.new('2.3.0') ? '1.4.0' : '>1.4.0' end + gem 'webmock', ruby_version <= Gem::Version.new('1.9.3') ? '2.3.2': '>2.3.2' + gem 'crack', '< 0.4.5' if ruby_version <= Gem::Version.new('1.9.3') + gem 'hashdiff', ruby_version <= Gem::Version.new('1.9.3') ? '0.3.8': '>0.3.8' - if ruby_version >= Gem::Version.new('2.7.0') + + if ruby_version >= Gem::Version.new('3.0.0') + gem 'did_you_mean', '~> 1.5.0' + elsif ruby_version >= Gem::Version.new('2.7.0') gem 'did_you_mean', '~> 1.4.0' elsif ruby_version >= Gem::Version.new('2.5.0') gem 'did_you_mean', '~> 1.3.1' @@ -24,6 +31,9 @@ group :test, optional: true do elsif ruby_version >= Gem::Version.new('2.3.0') gem 'did_you_mean', '~> 1.0.4' end + + # WEBrick is no longer in the stdlib in Ruby 3.0 + gem 'webrick' if ruby_version >= Gem::Version.new('3.0.0') end group :coverage, optional: true do diff --git a/TESTING.md b/TESTING.md index 513d83588..28afd94ff 100644 --- a/TESTING.md +++ b/TESTING.md @@ -59,7 +59,7 @@ Configure the tests to be run in the following way: - Determine the Ruby version to be tested using the environment variable `RUBY_TEST_VERSION` e.g. `RUBY_TEST_VERSION=2.6` - If testing rails, set the rails version to be tested using the environment variable `RAILS_VERSION` e.g. `RAILS_VERSION=3` -- If testing sidekiq, set the version to be tested using the environment variable `SIDEKIQ_VERSION` as the bundler version, e.g. `SIDEKIQ_VERSION="~> 2"` +- If testing sidekiq, set the version to be tested using the environment variable `SIDEKIQ_VERSION`, e.g. `SIDEKIQ_VERSION=2` When running the end-to-end tests, you'll want to restrict the feature files run to the specific test features for the platform. This is done using the Cucumber CLI syntax at the end of the `docker-compose run ruby-maze-runner` command, i.e: diff --git a/VERSION b/VERSION index 1b386a547..74c926af4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.18.0 +6.19.0 diff --git a/docker-compose.yml b/docker-compose.yml index 4abcce1b5..7940cbcec 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,7 +28,7 @@ services: args: - RUBY_TEST_VERSION - GEMSETS=${GEMSETS:-test} - - BUNDLE_VERSION=${BUNDLE_VERSION:-1.12.0} + - BUNDLE_VERSION=${BUNDLE_VERSION:-2.2.0} environment: COVERALLS_REPO_TOKEN: GEMSETS: @@ -40,7 +40,7 @@ services: dockerfile: dockerfiles/Dockerfile.jruby-unit-tests args: - GEMSETS=${GEMSETS:-test} - - BUNDLE_VERSION=${BUNDLE_VERSION:-1.12.0} + - BUNDLE_VERSION=${BUNDLE_VERSION:-2.2.0} networks: default: diff --git a/features/delayed_job.feature b/features/delayed_job.feature index 4cc2933e8..c94c31c3c 100644 --- a/features/delayed_job.feature +++ b/features/delayed_job.feature @@ -18,7 +18,7 @@ Scenario: An unhandled RuntimeError sends a report with arguments And the event "metaData.job.payload.display_name" equals "TestModel.fail_with_args" And the event "metaData.job.payload.method_name" equals "fail_with_args" And the payload field "events.0.metaData.job.payload.args.0" equals "Test" - And the event "device.runtimeVersions.delayed_job" equals "4.1.8" + And the event "device.runtimeVersions.delayed_job" matches "\d+\.\d+\.\d+" Scenario: A handled exception sends a report Given I run the service "delayed_job" with the command "bundle exec rake delayed_job_tests:notify_with_args" @@ -37,7 +37,7 @@ Scenario: A handled exception sends a report And the event "metaData.job.payload.display_name" equals "TestModel.notify_with_args" And the event "metaData.job.payload.method_name" equals "notify_with_args" And the payload field "events.0.metaData.job.payload.args.0" equals "Test" - And the event "device.runtimeVersions.delayed_job" equals "4.1.8" + And the event "device.runtimeVersions.delayed_job" matches "\d+\.\d+\.\d+" Scenario: The report context uses the class name if no display name is available Given I run the service "delayed_job" with the command "bundle exec rake delayed_job_tests:report_context" @@ -55,4 +55,4 @@ Scenario: The report context uses the class name if no display name is available And the event "metaData.job.max_attempts" equals 1 And the event "metaData.job.payload.display_name" is null And the event "metaData.job.payload.method_name" is null - And the event "device.runtimeVersions.delayed_job" equals "4.1.8" + And the event "device.runtimeVersions.delayed_job" matches "\d+\.\d+\.\d+" diff --git a/features/fixtures/mailman/app/Gemfile b/features/fixtures/mailman/app/Gemfile index b202fc4a8..5297171f5 100644 --- a/features/fixtures/mailman/app/Gemfile +++ b/features/fixtures/mailman/app/Gemfile @@ -8,3 +8,6 @@ gem 'rb-inotify', '0.9.8' gem 'maildir', '~> 2.1.0' gem 'activesupport', '~> 3.2' gem 'rack', '~> 1.6.11' + +# Install a compatible FFI version on Ruby <2.3 +gem 'ffi', '< 1.13.0' if RUBY_VERSION < '2.3.0' diff --git a/features/fixtures/que/app/Gemfile b/features/fixtures/que/app/Gemfile index 1fc888479..d8ed45fc0 100644 --- a/features/fixtures/que/app/Gemfile +++ b/features/fixtures/que/app/Gemfile @@ -5,5 +5,7 @@ gem "bugsnag", path: "/bugsnag" gem "que", "~> 0.14.3" gem "pg", RUBY_VERSION < "2.2.0" ? "0.21.0" : "> 0.21.0" - gem "activerecord", RUBY_VERSION < "2.2.0" ? "4.2.11" : "> 4.2.11" + +# Install a compatible Minitest version on Ruby <2.3 +gem 'minitest', '5.11.3' if RUBY_VERSION < '2.3.0' diff --git a/features/fixtures/que/app/app.rb b/features/fixtures/que/app/app.rb index da050c877..267638e93 100644 --- a/features/fixtures/que/app/app.rb +++ b/features/fixtures/que/app/app.rb @@ -1,5 +1,6 @@ require 'pg' require 'que' +require 'socket' require 'bugsnag' require 'active_record' @@ -10,6 +11,23 @@ config.endpoint = ENV['BUGSNAG_ENDPOINT'] end +postgres_ready = false +attempts = 0 +MAX_ATTEMPTS = 10 + +until postgres_ready || attempts >= MAX_ATTEMPTS + begin + Timeout::timeout(5) { TCPSocket.new('postgres', 5432).close } + + postgres_ready = true + rescue Exception + attempts += 1 + sleep 1 + end +end + +raise 'postgres was not ready in time!' unless postgres_ready + ActiveRecord::Base.establish_connection( adapter: 'postgresql', database: 'postgres', diff --git a/features/fixtures/rack1/app/Gemfile b/features/fixtures/rack1/app/Gemfile index e7f60dd3d..58bbd2883 100644 --- a/features/fixtures/rack1/app/Gemfile +++ b/features/fixtures/rack1/app/Gemfile @@ -2,3 +2,4 @@ source 'https://rubygems.org' gem 'bugsnag', path: '/bugsnag' gem 'rack', '~> 1' +gem 'webrick' if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('3.0.0') diff --git a/features/fixtures/rack2/app/Gemfile b/features/fixtures/rack2/app/Gemfile index b0706f46a..3d2908859 100644 --- a/features/fixtures/rack2/app/Gemfile +++ b/features/fixtures/rack2/app/Gemfile @@ -2,3 +2,4 @@ source 'https://rubygems.org' gem 'bugsnag', path: '/bugsnag' gem 'rack', '~> 2' +gem 'webrick' if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('3.0.0') diff --git a/features/fixtures/rails_integrations/app/Gemfile b/features/fixtures/rails_integrations/app/Gemfile index 7a245a5b2..f63d67907 100644 --- a/features/fixtures/rails_integrations/app/Gemfile +++ b/features/fixtures/rails_integrations/app/Gemfile @@ -6,6 +6,14 @@ gem 'bugsnag', path: ENV.fetch('BUGSNAG_GEM_PATH', '../../../../') gem 'delayed_job_active_record' gem 'mailman', '~> 0.7.3' gem 'que', '~> 0.14.3' + +# Ensure we get a version of 'redis-namespace' that's compatible with Ruby 3 +# There isn't a tag for this yet, so we fetch a commit that's known to work +# TODO remove this when a new version is released +if RUBY_VERSION >= '3.0.0' + gem 'redis-namespace', github: 'resque/redis-namespace', ref: 'c31e63dc3cd5e59ef5ea394d4d46ac60d1e6f82e' +end + gem 'resque', '~> 2.0.0' gem 'sidekiq', '~> 6.1.0' diff --git a/features/fixtures/sidekiq/app/Gemfile b/features/fixtures/sidekiq/app/Gemfile index 6b241dfd9..cb822e8ce 100644 --- a/features/fixtures/sidekiq/app/Gemfile +++ b/features/fixtures/sidekiq/app/Gemfile @@ -1,8 +1,13 @@ source 'https://rubygems.org' +sidekiq_version = ENV.fetch('SIDEKIQ_VERSION') + gem 'bugsnag', path: '/bugsnag' -gem 'hitimes', '~> 1.2.6' gem 'rake', '~> 12.3.0' -gem 'redis', '~> 3.3.5' -gem 'sidekiq', ENV['SIDEKIQ_VERSION'] -gem 'timers', '~> 4.1.2' +gem 'sidekiq', "~> #{sidekiq_version}" + +if sidekiq_version == '2' + gem 'hitimes', '~> 1.2.6' + gem 'redis', '~> 3.3.5' + gem 'timers', '~> 4.1.2' +end diff --git a/features/steps/ruby_notifier_steps.rb b/features/steps/ruby_notifier_steps.rb index 6fb8e0c77..c8a9f7bb0 100644 --- a/features/steps/ruby_notifier_steps.rb +++ b/features/steps/ruby_notifier_steps.rb @@ -45,11 +45,17 @@ } end +# When running tests against Rails on Ruby 3, the base Maze Runner step +# "I open the url {string}" commonly flakes due to a "Errno::ECONNREFUSED" +# error, which MR doesn't rescue. The notifier request is still fired so the +# test passes when these errors are rescued and there's no risk of swallowing an +# actual failure because any assertion steps will fail if the notifier request +# isn't fired. This may become unnecessary in future, when running Rails on +# Ruby 3 is more stable When("I navigate to the route {string} on the rails app") do |route| - rails_version = ENV["RAILS_VERSION"] - steps %Q{ - When I open the URL "http://rails#{rails_version}:3000#{route}" - } + URI.open("http://rails#{ENV["RAILS_VERSION"]}:3000#{route}", &:read) +rescue => e + $logger.debug(e.inspect) end When("I run {string} in the rails app") do |command| @@ -86,22 +92,18 @@ end Then("the payload field {string} matches the appropriate Sidekiq handled payload") do |field| - if ENV["SIDEKIQ_VERSION"] == "~> 2" - created_at_present = "false" - else - created_at_present = "true" - end + # Sidekiq 2 doesn't include the "created_at" field + created_at_present = ENV["SIDEKIQ_VERSION"] > "2" + steps %Q{ And the payload field "#{field}" matches the JSON fixture in "features/fixtures/sidekiq/payloads/handled_metadata_ca_#{created_at_present}.json" } end Then("the payload field {string} matches the appropriate Sidekiq unhandled payload") do |field| - if ENV["SIDEKIQ_VERSION"] == "~> 2" - created_at_present = "false" - else - created_at_present = "true" - end + # Sidekiq 2 doesn't include the "created_at" field + created_at_present = ENV["SIDEKIQ_VERSION"] > "2" + steps %Q{ And the payload field "#{field}" matches the JSON fixture in "features/fixtures/sidekiq/payloads/unhandled_metadata_ca_#{created_at_present}.json" } diff --git a/lib/bugsnag/breadcrumbs/validator.rb b/lib/bugsnag/breadcrumbs/validator.rb index d1f559492..71ddbb16a 100644 --- a/lib/bugsnag/breadcrumbs/validator.rb +++ b/lib/bugsnag/breadcrumbs/validator.rb @@ -15,16 +15,6 @@ def initialize(configuration) # # @param breadcrumb [Bugsnag::Breadcrumbs::Breadcrumb] the breadcrumb to be validated def validate(breadcrumb) - # Check meta_data hash doesn't contain complex values - breadcrumb.meta_data = breadcrumb.meta_data.select do |k, v| - if valid_meta_data_type?(v) - true - else - @configuration.debug("Breadcrumb #{breadcrumb.name} meta_data #{k}:#{v.class} has been dropped for having an invalid data type") - false - end - end - # Check type is valid, set to manual otherwise unless Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES.include?(breadcrumb.type) @configuration.debug("Invalid type: #{breadcrumb.type} for breadcrumb: #{breadcrumb.name}, defaulting to #{Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE}") @@ -37,17 +27,5 @@ def validate(breadcrumb) @configuration.debug("Automatic breadcrumb of type #{breadcrumb.type} ignored: #{breadcrumb.name}") breadcrumb.ignore! end - - private - - ## - # Tests whether the meta_data types are non-complex objects. - # - # Acceptable types are String, Symbol, Numeric, TrueClass, FalseClass, and nil. - # - # @param value [Object] the object to be type checked - def valid_meta_data_type?(value) - value.nil? || value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(FalseClass) || value.is_a?(TrueClass) - end end end diff --git a/lib/bugsnag/helpers.rb b/lib/bugsnag/helpers.rb index 05c60be6d..7a1c41786 100644 --- a/lib/bugsnag/helpers.rb +++ b/lib/bugsnag/helpers.rb @@ -4,7 +4,7 @@ module Bugsnag - module Helpers + module Helpers # rubocop:todo Metrics/ModuleLength MAX_STRING_LENGTH = 3072 MAX_PAYLOAD_LENGTH = 512000 MAX_ARRAY_LENGTH = 80 @@ -19,8 +19,12 @@ def self.trim_if_needed(value) return value unless payload_too_long?(value) + # Truncate exception messages + reduced_value = truncate_exception_messages(value) + return reduced_value unless payload_too_long?(reduced_value) + # Trim metadata - reduced_value = trim_metadata(value) + reduced_value = trim_metadata(reduced_value) return reduced_value unless payload_too_long?(reduced_value) # Trim code from stacktrace @@ -71,6 +75,15 @@ def self.deep_merge!(l_hash, r_hash) TRUNCATION_INFO = '[TRUNCATED]' + ## + # Truncate exception messages + def self.truncate_exception_messages(payload) + extract_exception(payload) do |exception| + exception[:message] = trim_as_string(exception[:message]) + end + payload + end + ## # Remove all code from stacktraces def self.trim_stacktrace_code(payload) diff --git a/spec/breadcrumbs/validator_spec.rb b/spec/breadcrumbs/validator_spec.rb index 581b243c3..99e0e1015 100644 --- a/spec/breadcrumbs/validator_spec.rb +++ b/spec/breadcrumbs/validator_spec.rb @@ -5,171 +5,68 @@ require 'bugsnag/breadcrumbs/validator' RSpec.describe Bugsnag::Breadcrumbs::Validator do - let(:enabled_automatic_breadcrumb_types) { Bugsnag::Breadcrumbs::VALID_BREADCRUMB_TYPES } - let(:auto) { false } + let(:auto) { :manual } let(:name) { "Valid message" } let(:type) { Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE } - let(:meta_data) { {} } describe "#validate" do it "does not 'ignore!' a valid breadcrumb" do - config = instance_double(Bugsnag::Configuration) - allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types) - validator = Bugsnag::Breadcrumbs::Validator.new(config) + breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(name, type, {}, auto) - breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, { - :auto => auto, - :name => name, - :type => type, - :meta_data => meta_data, - :meta_data= => nil - }) - - expect(breadcrumb).to_not receive(:ignore!) - expect(config).to_not receive(:debug) + expect(breadcrumb.ignore?).to eq(false) + validator = Bugsnag::Breadcrumbs::Validator.new(Bugsnag.configuration) validator.validate(breadcrumb) - end - - describe "tests meta_data types" do - it "accepts Strings, Numerics, Booleans, & nil" do - config = instance_double(Bugsnag::Configuration) - allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types) - validator = Bugsnag::Breadcrumbs::Validator.new(config) - - meta_data = { - :string => "This is a string", - :symbol => :this_is_a_symbol, - :integer => 12345, - :float => 12345.6789, - :false => false, - :true => true, - :nil => nil - } - - breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, { - :auto => auto, - :name => name, - :type => type, - :meta_data => meta_data, - :meta_data= => nil - }) - - expect(breadcrumb).to_not receive(:ignore!) - expect(config).to_not receive(:debug) - - validator.validate(breadcrumb) - end - it "rejects Arrays, Hashes, and non-primitive objects" do - config = instance_double(Bugsnag::Configuration) - allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types) - validator = Bugsnag::Breadcrumbs::Validator.new(config) - - class TestClass - end - - meta_data = { - :fine => 1, - :array => [1, 2, 3], - :hash => { - :a => 1 - }, - :object => TestClass.new - } - - breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, { - :auto => auto, - :name => name, - :type => type, - :meta_data => meta_data - }) - - expect(breadcrumb).to_not receive(:ignore!) - expected_string_1 = "Breadcrumb #{breadcrumb.name} meta_data array:Array has been dropped for having an invalid data type" - expected_string_2 = "Breadcrumb #{breadcrumb.name} meta_data hash:Hash has been dropped for having an invalid data type" - expected_string_3 = "Breadcrumb #{breadcrumb.name} meta_data object:TestClass has been dropped for having an invalid data type" - expect(config).to receive(:debug).with(expected_string_1) - expect(config).to receive(:debug).with(expected_string_2) - expect(config).to receive(:debug).with(expected_string_3) - - # Confirms that the meta_data is being filtered - expect(breadcrumb).to receive(:meta_data=).with({ - :fine => 1 - }) - - validator.validate(breadcrumb) - end + expect(breadcrumb.ignore?).to eq(false) end it "tests type, defaulting to 'manual' if invalid" do - config = instance_double(Bugsnag::Configuration) - allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(enabled_automatic_breadcrumb_types) - validator = Bugsnag::Breadcrumbs::Validator.new(config) - - type = "Not a valid type" - - breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, { - :auto => auto, - :name => name, - :type => type, - :meta_data => meta_data, - :meta_data= => nil - }) + invalid_type = "I'm not a valid type :-)" + breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(name, invalid_type, {}, auto) - expect(breadcrumb).to receive(:type=).with(Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE) - expect(breadcrumb).to_not receive(:ignore!) - expected_string = "Invalid type: #{type} for breadcrumb: #{breadcrumb.name}, defaulting to #{Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE}" - expect(config).to receive(:debug).with(expected_string) + expect(breadcrumb.ignore?).to eq(false) + expect(breadcrumb.type).to eq(invalid_type) + validator = Bugsnag::Breadcrumbs::Validator.new(Bugsnag.configuration) validator.validate(breadcrumb) + + expect(breadcrumb.ignore?).to eq(false) + expect(breadcrumb.type).to eq(Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE) end describe "with enabled_automatic_breadcrumb_types set" do it "rejects automatic breadcrumbs with rejected types" do - config = instance_double(Bugsnag::Configuration) - allowed_breadcrumb_types = [] - allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(allowed_breadcrumb_types) - validator = Bugsnag::Breadcrumbs::Validator.new(config) - - auto = true - type = Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE - - breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, { - :auto => auto, - :name => name, - :type => type, - :meta_data => meta_data, - :meta_data= => nil - }) - - expect(breadcrumb).to receive(:ignore!) - expected_string = "Automatic breadcrumb of type #{Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE} ignored: #{breadcrumb.name}" - expect(config).to receive(:debug).with(expected_string) + Bugsnag.configuration.logger = spy(Logger) + Bugsnag.configuration.enabled_automatic_breadcrumb_types.delete(type) + + breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(name, type, {}, :auto) + + expect(breadcrumb.ignore?).to eq(false) + validator = Bugsnag::Breadcrumbs::Validator.new(Bugsnag.configuration) validator.validate(breadcrumb) + + expect(breadcrumb.ignore?).to eq(true) + + expect(Bugsnag.configuration.logger).to have_received(:debug) do |&block| + expect(block.call).to eq("Automatic breadcrumb of type #{type} ignored: #{name}") + end end it "does not reject manual breadcrumbs with rejected types" do - config = instance_double(Bugsnag::Configuration) - allowed_breadcrumb_types = [] - allow(config).to receive(:enabled_automatic_breadcrumb_types).and_return(allowed_breadcrumb_types) - validator = Bugsnag::Breadcrumbs::Validator.new(config) + Bugsnag.configuration.logger = spy(Logger) + Bugsnag.configuration.enabled_automatic_breadcrumb_types.delete(type) - type = Bugsnag::Breadcrumbs::ERROR_BREADCRUMB_TYPE + breadcrumb = Bugsnag::Breadcrumbs::Breadcrumb.new(name, type, {}, :manual) - breadcrumb = instance_double(Bugsnag::Breadcrumbs::Breadcrumb, { - :auto => auto, - :name => name, - :type => type, - :meta_data => meta_data, - :meta_data= => nil - }) - - expect(breadcrumb).to_not receive(:ignore!) - expect(config).to_not receive(:debug) + expect(breadcrumb.ignore?).to eq(false) + validator = Bugsnag::Breadcrumbs::Validator.new(Bugsnag.configuration) validator.validate(breadcrumb) + + expect(breadcrumb.ignore?).to eq(false) + expect(Bugsnag.configuration.logger).to_not have_received(:debug) end end end diff --git a/spec/bugsnag_spec.rb b/spec/bugsnag_spec.rb index 8c21e7eb2..5d2e1aa5e 100644 --- a/spec/bugsnag_spec.rb +++ b/spec/bugsnag_spec.rb @@ -266,12 +266,18 @@ module Kernel }, "Not a real type" ) + expect(breadcrumbs.to_a.size).to eq(1) expect(breadcrumbs.first.to_h).to match({ :name => "123123123123123123123123123123456456456456456456456456456456", :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, :metaData => { - :a => 1 + :a => 1, + :b => [1, 2, 3, 4], + :c => { + :test => true, + :test2 => false + } }, :timestamp => match(timestamp_regex) }) @@ -308,13 +314,20 @@ module Kernel breadcrumb.type = "Not a real type" breadcrumb.name = "123123123123123123123123123123456456456456456" end + Bugsnag.leave_breadcrumb("TestName") + expect(breadcrumbs.to_a.size).to eq(1) expect(breadcrumbs.first.to_h).to match({ :name => "123123123123123123123123123123456456456456456", :type => Bugsnag::Breadcrumbs::MANUAL_BREADCRUMB_TYPE, :metaData => { - :int => 1 + :int => 1, + :array => [1, 2, 3], + :hash => { + :a => 1, + :b => 2 + } }, :timestamp => match(timestamp_regex) }) diff --git a/spec/fixtures/apps/rails-initializer-config/Gemfile b/spec/fixtures/apps/rails-initializer-config/Gemfile index 4948af013..a7a2ca6c3 100644 --- a/spec/fixtures/apps/rails-initializer-config/Gemfile +++ b/spec/fixtures/apps/rails-initializer-config/Gemfile @@ -4,6 +4,6 @@ ruby_version = Gem::Version.new(RUBY_VERSION.dup) gem 'railties', ruby_version <= Gem::Version.new('2.6') ? '4.2.10' : '~> 6.0.2', require: %w(action_controller rails) gem 'rake', ruby_version <= Gem::Version.new('1.9.3') ? '~> 11.3.0' : '~> 12.3.0' -gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.13.0' +gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.14.0' gem 'nokogiri', '1.6.8' gem 'bugsnag', path: '../../../..' diff --git a/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile b/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile index 4948af013..a7a2ca6c3 100644 --- a/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile +++ b/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile @@ -4,6 +4,6 @@ ruby_version = Gem::Version.new(RUBY_VERSION.dup) gem 'railties', ruby_version <= Gem::Version.new('2.6') ? '4.2.10' : '~> 6.0.2', require: %w(action_controller rails) gem 'rake', ruby_version <= Gem::Version.new('1.9.3') ? '~> 11.3.0' : '~> 12.3.0' -gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.13.0' +gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.14.0' gem 'nokogiri', '1.6.8' gem 'bugsnag', path: '../../../..' diff --git a/spec/fixtures/apps/rails-no-config/Gemfile b/spec/fixtures/apps/rails-no-config/Gemfile index 4948af013..a7a2ca6c3 100644 --- a/spec/fixtures/apps/rails-no-config/Gemfile +++ b/spec/fixtures/apps/rails-no-config/Gemfile @@ -4,6 +4,6 @@ ruby_version = Gem::Version.new(RUBY_VERSION.dup) gem 'railties', ruby_version <= Gem::Version.new('2.6') ? '4.2.10' : '~> 6.0.2', require: %w(action_controller rails) gem 'rake', ruby_version <= Gem::Version.new('1.9.3') ? '~> 11.3.0' : '~> 12.3.0' -gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.13.0' +gem 'minitest', ruby_version <= Gem::Version.new('2.2') ? '5.11.3' : '~> 5.14.0' gem 'nokogiri', '1.6.8' gem 'bugsnag', path: '../../../..' diff --git a/spec/helper_spec.rb b/spec/helper_spec.rb index 11323c932..21a8cd0f8 100644 --- a/spec/helper_spec.rb +++ b/spec/helper_spec.rb @@ -41,31 +41,75 @@ end context "payload length is greater than allowed" do + it "trims exception messages" do + long_message = "should truncate" * 50000 + + payload = { + :events => [{ + :exceptions => [{ + :message => long_message, + :preserved => "Foo" + }] + }] + } + + expect(::JSON.dump(payload).length).to be > Bugsnag::Helpers::MAX_PAYLOAD_LENGTH + + trimmed = Bugsnag::Helpers.trim_if_needed(payload) + + expect(::JSON.dump(trimmed).length).to be <= Bugsnag::Helpers::MAX_PAYLOAD_LENGTH + + truncation_info = "[TRUNCATED]" + expected_length = Bugsnag::Helpers::MAX_STRING_LENGTH - truncation_info.length + expected_truncation = long_message[0...expected_length] + truncation_info + + expect(trimmed[:events][0][:exceptions][0][:message]).to eq(expected_truncation) + expect(trimmed[:events][0][:exceptions][0][:preserved]).to eq("Foo") + end + it "trims metadata strings" do + long_message = "should truncate" * 50000 + payload = { :events => [{ - :metaData => 50000.times.map {|i| "should truncate" }.join(""), + :exceptions => [], + :metaData => long_message, :preserved => "Foo" }] } + expect(::JSON.dump(payload).length).to be > Bugsnag::Helpers::MAX_PAYLOAD_LENGTH + trimmed = Bugsnag::Helpers.trim_if_needed(payload) + expect(::JSON.dump(trimmed).length).to be <= Bugsnag::Helpers::MAX_PAYLOAD_LENGTH - expect(trimmed[:events][0][:metaData].length).to be <= Bugsnag::Helpers::MAX_STRING_LENGTH + + truncation_info = "[TRUNCATED]" + expected_length = Bugsnag::Helpers::MAX_STRING_LENGTH - truncation_info.length + expected_truncation = long_message[0...expected_length] + truncation_info + + expect(trimmed[:events][0][:metaData]).to eq(expected_truncation) expect(trimmed[:events][0][:preserved]).to eq("Foo") end it "truncates metadata arrays" do payload = { :events => [{ + :exceptions => [], :metaData => 50000.times.map {|i| "should truncate" }, :preserved => "Foo" }] } + expect(::JSON.dump(payload).length).to be > Bugsnag::Helpers::MAX_PAYLOAD_LENGTH + trimmed = Bugsnag::Helpers.trim_if_needed(payload) + expect(::JSON.dump(trimmed).length).to be <= Bugsnag::Helpers::MAX_PAYLOAD_LENGTH - expect(trimmed[:events][0][:metaData].length).to be <= Bugsnag::Helpers::MAX_ARRAY_LENGTH + + expected_meta_data = payload[:events][0][:metaData].take(Bugsnag::Helpers::MAX_ARRAY_LENGTH) + + expect(trimmed[:events][0][:metaData]).to eq(expected_meta_data) expect(trimmed[:events][0][:preserved]).to eq("Foo") end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1b869e377..d51db2fec 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -42,12 +42,8 @@ def notify_test_exception(*args) Bugsnag.notify(RuntimeError.new("test message"), *args) end -def ruby_version_greater_equal?(version) - current_version = RUBY_VERSION.split "." - target_version = version.split "." - (Integer(current_version[0]) >= Integer(target_version[0])) && - (Integer(current_version[1]) >= Integer(target_version[1])) && - (Integer(current_version[2]) >= Integer(target_version[2])) +def ruby_version_greater_equal?(target_version) + Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new(target_version) end RSpec.configure do |config|