From 6fb63f6672709e81a8029411c21fb428f95c43d5 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 3 Nov 2022 11:17:12 +0000 Subject: [PATCH 01/61] Fix concurrent-ruby version on Rubies < 2.2 concurrent-ruby v1.1.10 requires Ruby 2.2 to install --- bugsnag.gemspec | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bugsnag.gemspec b/bugsnag.gemspec index e56504df4..3a0338738 100644 --- a/bugsnag.gemspec +++ b/bugsnag.gemspec @@ -18,5 +18,13 @@ Gem::Specification.new do |s| ] s.require_paths = ["lib"] s.required_ruby_version = '>= 1.9.2' - s.add_runtime_dependency 'concurrent-ruby', '~> 1.0' + + ruby_version = Gem::Version.new(RUBY_VERSION.dup) + + if ruby_version < Gem::Version.new('2.2.0') + # concurrent-ruby 1.1.10 requires Ruby 2.2+ + s.add_runtime_dependency 'concurrent-ruby', '~> 1.0', '< 1.1.10' + else + s.add_runtime_dependency 'concurrent-ruby', '~> 1.0' + end end From 79cd8d3d323bae9a566d9fffa187e1732fd4af1d Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 3 Nov 2022 12:14:59 +0000 Subject: [PATCH 02/61] Loosen Resque and Sidekiq version requirements --- features/fixtures/rails_integrations/app/Gemfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/fixtures/rails_integrations/app/Gemfile b/features/fixtures/rails_integrations/app/Gemfile index f63d67907..f0daa0f2c 100644 --- a/features/fixtures/rails_integrations/app/Gemfile +++ b/features/fixtures/rails_integrations/app/Gemfile @@ -14,8 +14,8 @@ 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' +gem 'resque', '~> 2.0' +gem 'sidekiq', '~> 6.1' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 6.0.3', '>= 6.0.3.2' From 1dc15d7edb2d784a586c9667d7e868ac5a30de52 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 4 Nov 2022 09:36:06 +0000 Subject: [PATCH 03/61] Restrict minitest version on Ruby 2.2 & 2.3 Support was dropped for these versions in 5.16.0 --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index a574155f9..ebaa4a86e 100644 --- a/Gemfile +++ b/Gemfile @@ -28,6 +28,10 @@ group :test, optional: true do gem 'webrick' if ruby_version >= Gem::Version.new('3.0.0') gem 'rexml', '< 3.2.5' if ruby_version == Gem::Version.new('2.0.0') + + if ruby_version >= Gem::Version.new('2.2.0') && ruby_version < Gem::Version.new('2.4.0') + gem 'minitest', '< 5.16.0' + end end group :coverage, optional: true do From 0626e114319dd33097fcba8fea77024355bee58e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 4 Nov 2022 09:45:23 +0000 Subject: [PATCH 04/61] Restrict connection_pool version on Ruby 2.2 & 2.3 Support was dropped for these versions in 2.3.0 --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index ebaa4a86e..3c0edcb33 100644 --- a/Gemfile +++ b/Gemfile @@ -31,6 +31,7 @@ group :test, optional: true do if ruby_version >= Gem::Version.new('2.2.0') && ruby_version < Gem::Version.new('2.4.0') gem 'minitest', '< 5.16.0' + gem 'connection_pool', '< 2.3.0' end end From fac2b9c839df451b7cee58570d4ae1f3c50f8063 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 4 Nov 2022 10:43:46 +0000 Subject: [PATCH 05/61] Restrict rack-protection version on Ruby < 2.6 3.0.0 requires Ruby 2.6+ --- Gemfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index 3c0edcb33..ec613b7f8 100644 --- a/Gemfile +++ b/Gemfile @@ -59,6 +59,9 @@ group :sidekiq, optional: true do # rack 2.2.0 dropped support for Ruby 2.2 gem 'rack', ruby_version < Gem::Version.new('2.3.0') ? '< 2.2.0' : '~> 2.2' + + # rack-protection 3.0.0 requires Ruby 2.6+ + gem 'rack-protection', '< 3.0.0' if ruby_version < Gem::Version.new('2.6.0') end gemspec From 1e340b5da0d1a41aea69f5882efb93360bcf689e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 7 Nov 2022 10:51:36 +0000 Subject: [PATCH 06/61] Define optional groups only on compatible Rubies Bundler _should_ always check dependency resolution with all optional groups, even when not actually installing them. However on old versions this check didn't happen, which is intuitive but wrong We were depending on the broken behaviour of older Bundler versions to get our tests to run on old Ruby versions. Instead, we should be upfront about these groups not installing on old Rubies by not defining them unless they will resolve This allows us to use more up-to-date Bundler versions on old Rubies --- Gemfile | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/Gemfile b/Gemfile index ec613b7f8..6a736ccce 100644 --- a/Gemfile +++ b/Gemfile @@ -40,28 +40,32 @@ group :coverage, optional: true do gem 'coveralls' end -group :rubocop, optional: true do - gem 'rubocop', '~> 1.0.0' +if ruby_version >= Gem::Version.new('2.4.0') + group :rubocop, optional: true do + gem 'rubocop', '~> 1.0.0' + end end -group :sidekiq, optional: true do - gem 'sidekiq', '~> 5.2.7' - - if ruby_version < Gem::Version.new('2.3.0') - # redis 4.1.2 dropped support for Ruby 2.2 - gem 'redis', '4.1.1' - elsif ruby_version < Gem::Version.new('2.4.0') - # redis 4.5.0 dropped support for Ruby 2.3 - gem 'redis', '< 4.5.0' - else - gem 'redis' +if ruby_version >= Gem::Version.new('2.2.0') + group :sidekiq, optional: true do + gem 'sidekiq', '~> 5.2.7' + + if ruby_version < Gem::Version.new('2.3.0') + # redis 4.1.2 dropped support for Ruby 2.2 + gem 'redis', '4.1.1' + elsif ruby_version < Gem::Version.new('2.4.0') + # redis 4.5.0 dropped support for Ruby 2.3 + gem 'redis', '< 4.5.0' + else + gem 'redis' + end + + # rack 2.2.0 dropped support for Ruby 2.2 + gem 'rack', ruby_version < Gem::Version.new('2.3.0') ? '< 2.2.0' : '~> 2.2' + + # rack-protection 3.0.0 requires Ruby 2.6+ + gem 'rack-protection', '< 3.0.0' if ruby_version < Gem::Version.new('2.6.0') end - - # rack 2.2.0 dropped support for Ruby 2.2 - gem 'rack', ruby_version < Gem::Version.new('2.3.0') ? '< 2.2.0' : '~> 2.2' - - # rack-protection 3.0.0 requires Ruby 2.6+ - gem 'rack-protection', '< 3.0.0' if ruby_version < Gem::Version.new('2.6.0') end gemspec From f32595733c81303b071f525b6c1b2cf06bb6c080 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 7 Nov 2022 10:18:42 +0000 Subject: [PATCH 07/61] Prevent infinite loop in rake_spec --- spec/integrations/rake_spec.rb | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/spec/integrations/rake_spec.rb b/spec/integrations/rake_spec.rb index 6139ae86c..553df011f 100644 --- a/spec/integrations/rake_spec.rb +++ b/spec/integrations/rake_spec.rb @@ -44,7 +44,7 @@ res.status = 200 res.body = "OK\n" end - Thread.new{ server.start } + Thread.new { server.start } end after do @@ -52,8 +52,6 @@ queue.clear end - let(:request) { JSON.parse(queue.pop) } - it 'should run the rake middleware when rake tasks crash' do ENV['BUGSNAG_TEST_SERVER_PORT'] = server.config[:Port].to_s task_fixtures_path = File.join(File.dirname(__FILE__), '../fixtures', 'tasks') @@ -61,7 +59,23 @@ system("../../../bin/rake test:crash") end - result = request() + result = nil + attempts = 0 + + while result.nil? && attempts < 20 + begin + sleep 0.1 + attempts += 1 + + result = queue.pop(true) + rescue ThreadError + end + end + + expect(result).not_to be_nil + + result = JSON.parse(result) + expect(result["events"][0]["metaData"]["rake_task"]).not_to be_nil expect(result["events"][0]["metaData"]["rake_task"]["name"]).to eq("test:crash") expect(result["events"][0]["app"]["type"]).to eq("rake") From d195ca105d4a58b5757fb79d6000dfedb05069d9 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 7 Nov 2022 09:38:09 +0000 Subject: [PATCH 08/61] Run specs on GH Actions --- .github/workflows/test-package.yml | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/test-package.yml diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml new file mode 100644 index 000000000..8c1f67e7f --- /dev/null +++ b/.github/workflows/test-package.yml @@ -0,0 +1,34 @@ +name: test + +on: [push, pull_request] + +jobs: + specs: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + ruby-version: ['2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] + optional-groups: ['test sidekiq'] + include: + - ruby-version: '1.9' + optional-groups: 'test' + - ruby-version: '2.0' + optional-groups: 'test' + - ruby-version: '2.1' + optional-groups: 'test' + - ruby-version: 'jruby' + optional-groups: 'test' + + steps: + - uses: actions/checkout@v3 + + - name: install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: ${{ matrix.ruby-version }} + + - run: bundle install --with "${{ matrix.optional-groups }}" --binstubs + + - run: ./bin/rake spec From 02b2880c5c19b463f5805a33c03f25bdf5c9915d Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 7 Nov 2022 11:10:03 +0000 Subject: [PATCH 09/61] Run linting on GH Actions --- .github/workflows/test-package.yml | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index 8c1f67e7f..252af86d8 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -32,3 +32,20 @@ jobs: - run: bundle install --with "${{ matrix.optional-groups }}" --binstubs - run: ./bin/rake spec + + linting: + runs-on: ubuntu-latest + + env: + BUNDLE_WITH: rubocop + + steps: + - uses: actions/checkout@v3 + + - name: install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + + - run: bundle exec rubocop lib/ From c35a6f5258d5733ea39fb312c4e70f64736ff3cd Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 9 Nov 2022 09:19:09 +0000 Subject: [PATCH 10/61] Run license audit on GH Actions --- .github/workflows/license-audit.yml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .github/workflows/license-audit.yml diff --git a/.github/workflows/license-audit.yml b/.github/workflows/license-audit.yml new file mode 100644 index 000000000..6c3b1644e --- /dev/null +++ b/.github/workflows/license-audit.yml @@ -0,0 +1,24 @@ +name: license audit + +on: [push, pull_request] + +jobs: + license-audit: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: fetch decisions.yml + run: | + curl https://raw.githubusercontent.com/bugsnag/license-audit/master/config/decision_files/global.yml -o config/decisions.yml + curl https://raw.githubusercontent.com/bugsnag/license-audit/master/config/decision_files/bugsnag-ruby.yml >> config/decisions.yml + + - name: run license finder + # for some reason license finder doesn't run without a login shell (-l) + run: > + docker run -v $PWD:/scan licensefinder/license_finder /bin/bash -lc " + cd /scan && + bundle install && + license_finder --decisions-file config/decisions.yml + " From 7ccb1883ad300052a287566a313cd5ec9ed9503d Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 9 Nov 2022 10:41:26 +0000 Subject: [PATCH 11/61] Remove specs, linting & auditing from Buildkite --- .buildkite/pipeline.yml | 106 ---------------------------------------- 1 file changed, 106 deletions(-) diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml index 5632ec94a..3cc547bce 100644 --- a/.buildkite/pipeline.yml +++ b/.buildkite/pipeline.yml @@ -13,36 +13,6 @@ steps: - wait - - label: ':ruby: Ruby 1.9 unit tests' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-unit-tests - 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 - plugins: - docker-compose#v3.1.0: - run: ruby-unit-tests - use-aliases: true - env: - RUBY_TEST_VERSION: "2.7" - GEMSETS: "test sidekiq coverage" - - label: ':ruby: Ruby 2.7 linting' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-unit-tests - use-aliases: true - env: - RUBY_TEST_VERSION: "2.7" - GEMSETS: "test rubocop" - command: "bundle exec ./bin/rubocop lib/" - - label: ':ruby: Ruby 2.7 plain tests' timeout_in_minutes: 30 plugins: @@ -108,76 +78,6 @@ steps: - wait - - label: ':ruby: JRuby unit tests' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: jruby-unit-tests - use-aliases: true - concurrency: 4 - concurrency_group: 'ruby/unit-tests' - - - label: ':ruby: Ruby {{matrix}} unit tests' - matrix: - - '2.0' - - '2.1' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-unit-tests - use-aliases: true - env: - RUBY_TEST_VERSION: "{{matrix}}" - BUNDLE_VERSION: "1.12.0" - concurrency: 4 - concurrency_group: 'ruby/unit-tests' - - - label: ':ruby: Ruby {{matrix}} unit tests' - matrix: - - '2.2' - - '2.3' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-unit-tests - use-aliases: true - env: - RUBY_TEST_VERSION: "{{matrix}}" - BUNDLE_VERSION: "1.12.0" - GEMSETS: "test sidekiq" - concurrency: 4 - concurrency_group: 'ruby/unit-tests' - - - label: ':ruby: Ruby {{matrix}} unit tests' - matrix: - - '2.4' - - '2.5' - - '2.6' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-unit-tests - use-aliases: true - env: - RUBY_TEST_VERSION: "{{matrix}}" - GEMSETS: "test sidekiq" - concurrency: 4 - concurrency_group: 'ruby/unit-tests' - - - label: ':ruby: Ruby {{matrix}} unit tests' - matrix: - - '3.0' - - '3.1' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-unit-tests - use-aliases: true - env: - RUBY_TEST_VERSION: "{{matrix}}" - concurrency: 4 - concurrency_group: 'ruby/unit-tests' - - label: ':ruby: Ruby {{matrix}} plain tests' matrix: - "1.9.3" @@ -490,9 +390,3 @@ steps: RACK_VERSION: '2' concurrency: 4 concurrency_group: 'ruby/integrations-maze-runner-tests' - - - name: ':copyright: License Audit' - plugins: - docker-compose#v3.7.0: - run: license_finder - command: /bin/bash -lc '/scan/scripts/license_finder.sh' From 05acfeff5148725e36dc5444a2b7549e48a721c1 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 9 Nov 2022 11:35:07 +0000 Subject: [PATCH 12/61] Remove unused license finder dockerfile & script --- dockerfiles/Dockerfile.audit | 5 ----- scripts/license_finder.sh | 6 ------ 2 files changed, 11 deletions(-) delete mode 100644 dockerfiles/Dockerfile.audit delete mode 100755 scripts/license_finder.sh diff --git a/dockerfiles/Dockerfile.audit b/dockerfiles/Dockerfile.audit deleted file mode 100644 index 5a19c1a96..000000000 --- a/dockerfiles/Dockerfile.audit +++ /dev/null @@ -1,5 +0,0 @@ -FROM licensefinder/license_finder - -WORKDIR /scan - -CMD /scan/scripts/license_finder.sh diff --git a/scripts/license_finder.sh b/scripts/license_finder.sh deleted file mode 100755 index b33d68ba0..000000000 --- a/scripts/license_finder.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -curl https://raw.githubusercontent.com/bugsnag/license-audit/master/config/decision_files/global.yml -o config/decisions.yml -curl https://raw.githubusercontent.com/bugsnag/license-audit/master/config/decision_files/bugsnag-ruby.yml >> config/decisions.yml - -bundle install -license_finder --decisions-file=config/decisions.yml From be5ead01641aafe355e014767990a93fae0c76d0 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 7 Nov 2022 12:05:33 +0000 Subject: [PATCH 13/61] Support running Maze Runner locally --- .gitignore | 3 +- Gemfile-maze-runner | 3 + features/fixtures/docker-compose.yml | 21 +++++ .../app/config/environments/rails_env.rb | 1 + features/lib/fixture.rb | 94 +++++++++++++++++++ features/plain_features/proxies.feature | 9 +- features/steps/ruby_notifier_steps.rb | 89 ++++++++---------- features/support/env.rb | 39 +++++++- 8 files changed, 200 insertions(+), 59 deletions(-) create mode 100644 Gemfile-maze-runner create mode 100644 features/lib/fixture.rb diff --git a/.gitignore b/.gitignore index 9458c940f..fe9799131 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,7 @@ doc # bundler .bundle Gemfile.lock +Gemfile-maze-runner.lock # jeweler generated pkg @@ -52,4 +53,4 @@ vendor bin # For Bugsnag-Maze-Runner: -maze_output \ No newline at end of file +maze_output diff --git a/Gemfile-maze-runner b/Gemfile-maze-runner new file mode 100644 index 000000000..5ee44f80d --- /dev/null +++ b/Gemfile-maze-runner @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v3.0.2' diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index 396a73fbf..c48f728c1 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -88,6 +88,9 @@ services: - BUGSNAG_ENDPOINT - BUGSNAG_METADATA_FILTERS restart: "no" + ports: + - target: 3000 + published: 7251 rack2: build: @@ -99,6 +102,9 @@ services: - BUGSNAG_ENDPOINT - BUGSNAG_METADATA_FILTERS restart: "no" + ports: + - target: 3000 + published: 7252 rails3: build: @@ -134,6 +140,9 @@ services: - USE_DEFAULT_AUTO_CAPTURE_SESSIONS - ADD_REQUEST_ON_ERROR restart: "no" + ports: + - target: 3000 + published: 7253 rails4: build: @@ -171,6 +180,9 @@ services: - USE_DEFAULT_AUTO_CAPTURE_SESSIONS - ADD_REQUEST_ON_ERROR restart: "no" + ports: + - target: 3000 + published: 7254 rails5: build: @@ -208,6 +220,9 @@ services: - USE_DEFAULT_AUTO_CAPTURE_SESSIONS - ADD_REQUEST_ON_ERROR restart: "no" + ports: + - target: 3000 + published: 7255 rails6: build: @@ -249,6 +264,9 @@ services: default: aliases: - rails6 + ports: + - target: 3000 + published: 7256 rails7: build: @@ -290,6 +308,9 @@ services: default: aliases: - rails7 + ports: + - target: 3000 + published: 7257 rails_integrations: build: diff --git a/features/fixtures/rails6/app/config/environments/rails_env.rb b/features/fixtures/rails6/app/config/environments/rails_env.rb index 2c38dc193..c3aef279f 100644 --- a/features/fixtures/rails6/app/config/environments/rails_env.rb +++ b/features/fixtures/rails6/app/config/environments/rails_env.rb @@ -53,4 +53,5 @@ config.file_watcher = ActiveSupport::EventedFileUpdateChecker config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } config.hosts << "rails6" + config.hosts << "localhost" end diff --git a/features/lib/fixture.rb b/features/lib/fixture.rb new file mode 100644 index 000000000..10acd4453 --- /dev/null +++ b/features/lib/fixture.rb @@ -0,0 +1,94 @@ +class Fixture + def initialize(name, version) + @name = name + @version = version + end + + def docker_service + "#{@name}#{@version}" + end + + def host + if running_in_docker? + docker_service + else + "localhost" + end + end + + def port + # the rails integrations tests run with a version of "_integrations" and + # bind to port 3000 even when running outside of docker + if running_in_docker? || @version == "_integrations" + "3000" + else + "725#{@version}" + end + end + + def version_matches?(operator, version_to_compare) + @version.to_i.send(operator, version_to_compare) + end + + def uri_for(route) + URI("http://#{host}:#{port}#{route}") + end + + def navigate_to(route, headers = {}) + uri = uri_for(route) + + make_request(uri) do |http| + request = Net::HTTP::Get.new(uri.request_uri) + + headers.each do |key, value| + request[key] = value + end + + request + end + end + + def post_form(route, form_data) + uri = uri_for(route) + + make_request(uri) do |http| + request = Net::HTTP::Post.new(uri) + request.set_form_data(form_data) + + request + end + end + + def post_json(route, json_data) + uri = uri_for(route) + + make_request(uri) do |http| + request = Net::HTTP::Post.new(uri) + request.body = JSON.generate(json_data) + request["Content-Type"] = "application/json" + + request + end + end + + private + + def make_request(uri, &block) + attempts = 0 + + begin + Net::HTTP.start(uri.host, uri.port) do |http| + request = block.call(http) + + http.request(request) + end + rescue => e + raise e if attempts > 10 + + attempts += 1 + sleep 1 + + retry + end + end +end diff --git a/features/plain_features/proxies.feature b/features/plain_features/proxies.feature index 496adea2b..d5f14c97b 100644 --- a/features/plain_features/proxies.feature +++ b/features/plain_features/proxies.feature @@ -1,24 +1,21 @@ Feature: proxy configuration options Scenario: Proxy settings are provided as configuration options - When I set environment variable "BUGSNAG_PROXY_HOST" to "maze-runner" - And I set environment variable "BUGSNAG_PROXY_PORT" to "9339" - And I set environment variable "BUGSNAG_PROXY_USER" to "tester" - And I set environment variable "BUGSNAG_PROXY_PASSWORD" to "testpass" + Given I configure the BUGSNAG_PROXY environment variables When I run the service "plain-ruby" with the command "bundle exec ruby configuration/proxy.rb" Then I wait to receive a request And the "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" And the event "metaData.proxy.user" equals "tester" Scenario: Proxy settings are provided as the HTTP_PROXY environment variable - Given I set environment variable "http_proxy" to "http://tester:testpass@maze-runner:9339" + Given I configure the http_proxy environment variable When I run the service "plain-ruby" with the command "bundle exec ruby configuration/proxy.rb" Then I wait to receive a request And the "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" And the event "metaData.proxy.user" equals "tester" Scenario: Proxy settings are provided as the HTTPS_PROXY environment variable - Given I set environment variable "https_proxy" to "http://tester:testpass@maze-runner:9339" + Given I configure the https_proxy environment variable When I run the service "plain-ruby" with the command "bundle exec ruby configuration/proxy.rb" Then I wait to receive a request And the "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" diff --git a/features/steps/ruby_notifier_steps.rb b/features/steps/ruby_notifier_steps.rb index 04d324c83..f80de1165 100644 --- a/features/steps/ruby_notifier_steps.rb +++ b/features/steps/ruby_notifier_steps.rb @@ -40,24 +40,14 @@ end Given("I start the rails service") do - rails_version = ENV["RAILS_VERSION"] steps %Q{ - When I start the service "rails#{rails_version}" - And I wait for the host "rails#{rails_version}" to open port "3000" + When I start the service "#{RAILS_FIXTURE.docker_service}" + And I wait for the host "#{RAILS_FIXTURE.host}" to open port "#{RAILS_FIXTURE.port}" } 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| - URI.open("http://rails#{ENV["RAILS_VERSION"]}:3000#{route}", &:read) -rescue => e - $logger.debug(e.inspect) + RAILS_FIXTURE.navigate_to(route) end When("I run {string} in the rails app") do |command| @@ -79,49 +69,29 @@ end Given("I start the rack service") do - rack_version = ENV["RACK_VERSION"] steps %Q{ - When I start the service "rack#{rack_version}" - And I wait for the host "rack#{rack_version}" to open port "3000" + When I start the service "#{RACK_FIXTURE.docker_service}" + And I wait for the host "#{RACK_FIXTURE.host}" to open port "#{RACK_FIXTURE.port}" } end When("I navigate to the route {string} on the rack app") do |route| - rack_version = ENV["RACK_VERSION"] - steps %Q{ - When I open the URL "http://rack#{rack_version}:3000#{route}" - } + RACK_FIXTURE.navigate_to(route) end When("I navigate to the route {string} on the rack app with these cookies:") do |route, data| - rack_version = ENV["RACK_VERSION"] - uri = URI("http://rack#{rack_version}:3000#{route}") - # e.g. { "a" => "b", "c" => "d" } -> "a=b;c=d" cookie = data.rows_hash.map { |key, value| "#{key}=#{value}" }.join(";") - http = Net::HTTP.new(uri.host, uri.port) - request = Net::HTTP::Get.new(uri.request_uri) - request["Cookie"] = cookie - - http.request(request) + RACK_FIXTURE.navigate_to(route, { "Cookie" => cookie }) end When("I send a POST request to {string} in the rack app with the following form data:") do |route, data| - rack_version = ENV["RACK_VERSION"] - uri = URI("http://rack#{rack_version}:3000#{route}") - - Net::HTTP.post_form(uri, data.rows_hash) + RACK_FIXTURE.post_form(route, data.rows_hash) end When("I send a POST request to {string} in the rack app with the following JSON:") do |route, data| - rack_version = ENV["RACK_VERSION"] - - Net::HTTP.post( - URI("http://rack#{rack_version}:3000#{route}"), - JSON.generate(data.rows_hash), - { "Content-Type" => "application/json" } - ) + RACK_FIXTURE.post_json(route, data.rows_hash) end Then("the payload field {string} matches the appropriate Sidekiq handled payload") do |field| @@ -142,14 +112,8 @@ } end -def rails_version_matches?(operator, version_to_compare) - # send the given operator as a method to the current rails version - # this will evaluate to e.g. '6.send(">=", 5)', which is the same as '6 >= 5' - ENV["RAILS_VERSION"].to_i.send(operator, version_to_compare) -end - Then("in Rails versions {string} {int} the event {string} equals {string}") do |operator, version, path, expected| - if rails_version_matches?(operator, version) + if RAILS_FIXTURE.version_matches?(operator, version) steps %Q{ And the event "#{path}" equals "#{expected}" } @@ -161,7 +125,7 @@ def rails_version_matches?(operator, version_to_compare) end Then("in Rails versions {string} {int} the event {string} equals {int}") do |operator, version, path, expected| - if rails_version_matches?(operator, version) + if RAILS_FIXTURE.version_matches?(operator, version) steps %Q{ And the event "#{path}" equals #{expected} } @@ -173,7 +137,7 @@ def rails_version_matches?(operator, version_to_compare) end Then("in Rails versions {string} {int} the event {string} matches {string}") do |operator, version, path, expected| - if rails_version_matches?(operator, version) + if RAILS_FIXTURE.version_matches?(operator, version) steps %Q{ And the event "#{path}" matches "#{expected}" } @@ -185,7 +149,7 @@ def rails_version_matches?(operator, version_to_compare) end Then("in Rails versions {string} {int} the event {string} is a timestamp") do |operator, version, path| - if rails_version_matches?(operator, version) + if RAILS_FIXTURE.version_matches?(operator, version) steps %Q{ And the event "#{path}" is a timestamp } @@ -205,3 +169,30 @@ def rails_version_matches?(operator, version_to_compare) And the event "#{path}" starts with "#{que_version}" } end + +Given("I configure the BUGSNAG_PROXY environment variables") do + host = running_in_docker? ? "maze-runner" : current_ip + + steps %Q{ + When I set environment variable "BUGSNAG_PROXY_HOST" to "#{host}" + And I set environment variable "BUGSNAG_PROXY_PORT" to "#{MOCK_API_PORT}" + And I set environment variable "BUGSNAG_PROXY_USER" to "tester" + And I set environment variable "BUGSNAG_PROXY_PASSWORD" to "testpass" + } +end + +Given("I configure the http_proxy environment variable") do + host = running_in_docker? ? "maze-runner" : current_ip + + steps %Q{ + Given I set environment variable "http_proxy" to "http://tester:testpass@#{host}:#{MOCK_API_PORT}" + } +end + +Given("I configure the https_proxy environment variable") do + host = running_in_docker? ? "maze-runner" : current_ip + + steps %Q{ + Given I set environment variable "https_proxy" to "https://tester:testpass@#{host}:#{MOCK_API_PORT}" + } +end diff --git a/features/support/env.rb b/features/support/env.rb index c6121cd68..4ba1eebb3 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,16 +1,44 @@ +require 'os' require 'fileutils' +require_relative "./../lib/fixture" + +RACK_FIXTURE = Fixture.new("rack", ENV["RACK_VERSION"]) +RAILS_FIXTURE = Fixture.new("rails", ENV["RAILS_VERSION"]) + +def running_in_docker? + File.exist?("/app/bugsnag.gem") +end def install_fixture_gems - throw Error.new("Bugsnag.gem not found. Is this running in a docker-container?") unless File.exist?("/app/bugsnag.gem") + if running_in_docker? + # running in docker so the gem is built already + bugsnag_gem_path = "/app/bugsnag.gem" + else + # running locally so we need to build the gem + `gem build bugsnag.gemspec -o bugsnag.gem` + bugsnag_gem_path = "#{__dir__}/../../bugsnag.gem" + end + Dir.entries('features/fixtures').reject { |entry| ['.', '..'].include?(entry) }.each do |entry| target_dir = "features/fixtures/#{entry}" if File.directory?(target_dir) - `cp /app/bugsnag.gem #{target_dir}` + `cp #{bugsnag_gem_path} #{target_dir}` `gem unpack #{target_dir}/bugsnag.gem --target #{target_dir}/temp-bugsnag-lib` end end +ensure + File.unlink(bugsnag_gem_path) unless running_in_docker? end +def current_ip + return "host.docker.internal" if OS.mac? + + ip_addr = `ifconfig | grep -Eo 'inet (addr:)?([0-9]*\\\.){3}[0-9]*' | grep -v '127.0.0.1'` + ip_list = /((?:[0-9]*\.){3}[0-9]*)/.match(ip_addr) + ip_list.captures.first +end + + AfterConfiguration do |config| install_fixture_gems end @@ -19,5 +47,10 @@ def install_fixture_gems Docker.compose_project_name = "#{rand.to_s}:#{Time.new.strftime("%s")}" Runner.environment.clear Runner.environment["BUGSNAG_API_KEY"] = $api_key - Runner.environment["BUGSNAG_ENDPOINT"] = "http://maze-runner:#{MOCK_API_PORT}" + + if running_in_docker? + Runner.environment["BUGSNAG_ENDPOINT"] = "http://maze-runner:#{MOCK_API_PORT}" + else + Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{current_ip}:#{MOCK_API_PORT}" + end end From 1dc61b0b887cbc5b2226f018d3abb5ef7c79a9d0 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 9 Nov 2022 12:11:44 +0000 Subject: [PATCH 14/61] Update TESTING.md for running Maze Runner locally --- TESTING.md | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/TESTING.md b/TESTING.md index e10187cc3..fdc540b07 100644 --- a/TESTING.md +++ b/TESTING.md @@ -29,30 +29,12 @@ End to end tests are written in cucumber-style `.feature` files, and need Ruby-b Maze runner's CLI and the test fixtures are containerised so you'll need Docker (and Docker Compose) to run them. -__Note: only Bugsnag employees can run the end-to-end tests.__ We have dedicated test infrastructure and private BrowserStack credentials which can't be shared outside of the organisation. - -##### Authenticating with the private container registry - -You'll need to set the credentials for the aws profile in order to access the private docker registry: - -``` -aws configure --profile=opensource -``` - -Subsequently you'll need to run the following commmand to authenticate with the registry: - -``` -aws ecr get-login-password --profile=opensource | docker login --username AWS --password-stdin 855461928731.dkr.ecr.us-west-1.amazonaws.com -``` - -__Your session will periodically expire__, so you'll need to run this command to re-authenticate when that happens. - ### Running the end to end tests -Once registered with the remote repository, build the test container: +Install Maze Runner: ``` -docker-compose build ruby-maze-runner +$ BUNDLE_GEMFILE=Gemfile-maze-runner bundle install ``` Configure the tests to be run in the following way: @@ -61,10 +43,13 @@ Configure the tests to be run in the following way: - 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`, 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: +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, i.e: ``` -RUBY_TEST_VERSION=2.6 RAILS_VERSION=6 docker-compose run --use-aliases ruby-maze-runner features/rails_features --tags "@rails6" +RUBY_TEST_VERSION=2.6 \ + RAILS_VERSION=6 \ + BUNDLE_GEMFILE=Gemfile-maze-runner \ + bundle exec maze-runner features/rails_features --tags "@rails6" ``` - Plain ruby tests should target `features/plain_features` @@ -75,7 +60,10 @@ RUBY_TEST_VERSION=2.6 RAILS_VERSION=6 docker-compose run --use-aliases ruby-maze In order to target specific features the exact `.feature` file can be specified, i.e: ``` -RUBY_TEST_VERSION=2.6 RAILS_VERSION=6 docker-compose run --use-aliases ruby-maze-runner features/rails_features/app_version.feature --tags "@rails6" +RUBY_TEST_VERSION=2.6 \ + RAILS_VERSION=6 \ + BUNDLE_GEMFILE=Gemfile-maze-runner \ + bundle exec maze-runner features/rails_features/app_version.feature --tags "@rails6" ``` In order to avoid running flakey or unfinished tests, the tag `"not @wip"` can be added to the tags option. This is recommended for all CI runs. If a tag is already specified, this should be added using the `and` keyword, e.g. `--tags "@rails6 and not @wip"` From 6d6a6c2f7c04d796a9826a6fb9f96e6d1b60646f Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 10 Nov 2022 10:49:35 +0000 Subject: [PATCH 15/61] Add 'payloadVersion' to report body This is currently reported in a header, but the Event API prefers it to be in the request body. Supplying it in both is recommended by the docs: https://bugsnagerrorreportingapi.docs.apiary.io/#reference/0/notify/send-error-reports --- lib/bugsnag/report.rb | 1 + spec/report_spec.rb | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index ab0c2da6b..2b92f363a 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -239,6 +239,7 @@ def as_json :version => NOTIFIER_VERSION, :url => NOTIFIER_URL }, + :payloadVersion => CURRENT_PAYLOAD_VERSION, :events => [payload_event] } end diff --git a/spec/report_spec.rb b/spec/report_spec.rb index 20c3cd3b0..27ff6d658 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -2089,5 +2089,14 @@ def to_s }) end end + + it "reports the payload version in the header and body" do + Bugsnag.notify(BugsnagTestException.new("It crashed")) + + expect(Bugsnag).to(have_sent_notification { |payload, headers| + expect(headers["Bugsnag-Payload-Version"]).to eq("4.0") + expect(payload["payloadVersion"]).to eq("4.0") + }) + end end # rubocop:enable Metrics/BlockLength From 2ac81fa90b7578ee9258005653a4816520f319a4 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 7 Nov 2022 11:50:21 +0000 Subject: [PATCH 16/61] Run Maze Runner on GH Actions --- .github/workflows/maze-runner.yml | 154 ++++++++++++++++++++++++++ .github/workflows/run-maze-runner.yml | 53 +++++++++ 2 files changed, 207 insertions(+) create mode 100644 .github/workflows/maze-runner.yml create mode 100644 .github/workflows/run-maze-runner.yml diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml new file mode 100644 index 000000000..803c529fe --- /dev/null +++ b/.github/workflows/maze-runner.yml @@ -0,0 +1,154 @@ +name: maze-runner + +on: [push, pull_request] + +jobs: + rake-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['1.9', '2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/rake.feature + ruby-version: ${{ matrix.ruby-version }} + + mailman-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0'] + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/mailman.feature + ruby-version: ${{ matrix.ruby-version }} + + rack-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['1.9', '2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] + rack-version: ['1', '2'] + exclude: + - ruby-version: '3.1' + rack-version: '1' + - ruby-version: '1.9' + rack-version: '2' + - ruby-version: '2.0' + rack-version: '2' + - ruby-version: '2.1' + rack-version: '2' + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/rack.feature + ruby-version: ${{ matrix.ruby-version }} + rack-version: ${{ matrix.rack-version }} + + que-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] + que-version: ['0.14', '1'] + exclude: + - ruby-version: '3.1' + que-version: '0.14' + - ruby-version: '2.0' + que-version: '1' + - ruby-version: '2.1' + que-version: '1' + - ruby-version: '2.2' + que-version: '1' + - ruby-version: '2.3' + que-version: '1' + - ruby-version: '2.4' + que-version: '1' + - ruby-version: '3.0' + que-version: '1' + - ruby-version: '3.1' + que-version: '1' + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/que.feature + ruby-version: ${{ matrix.ruby-version }} + que-version: ${{ matrix.que-version }} + + sidekiq-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['2.5'] + sidekiq-version: ['2', '3', '4', '5', '6'] + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/sidekiq.feature + ruby-version: ${{ matrix.ruby-version }} + sidekiq-version: ${{ matrix.sidekiq-version }} + + delayed-job-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['2.5'] + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/delayed_job.feature + ruby-version: ${{ matrix.ruby-version }} + + rails-3-4-5-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['2.2', '2.3', '2.4', '2.5'] + rails-version: ['3', '4', '5'] + include: + - ruby-version: '2.0' + rails-version: '3' + - ruby-version: '2.1' + rails-version: '3' + - ruby-version: '2.6' + rails-version: '5' + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/rails_features/ --tags @rails${{ matrix.rails-version }} + ruby-version: ${{ matrix.ruby-version }} + rails-version: ${{ matrix.rails-version }} + + rails-6-7-integrations-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['2.7', '3.0', '3.1'] + rails-version: ['6', '7', '_integrations'] + include: + - ruby-version: '2.5' + rails-version: '6' + - ruby-version: '2.6' + rails-version: '6' + exclude: + - ruby-version: '3.1' + rails-version: '_integrations' + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/rails_features/ --tags @rails${{ matrix.rails-version }} + ruby-version: ${{ matrix.ruby-version }} + rails-version: ${{ matrix.rails-version }} + + plain-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['1.9', '2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/plain_features/ + ruby-version: ${{ matrix.ruby-version }} diff --git a/.github/workflows/run-maze-runner.yml b/.github/workflows/run-maze-runner.yml new file mode 100644 index 000000000..9a64c5030 --- /dev/null +++ b/.github/workflows/run-maze-runner.yml @@ -0,0 +1,53 @@ +name: run-maze-runner + +on: + workflow_call: + inputs: + features: + required: true + type: string + ruby-version: + required: true + type: string + rack-version: + required: false + type: string + que-version: + required: false + type: string + rails-version: + required: false + type: string + sidekiq-version: + required: false + type: string + +jobs: + maze-runner: + runs-on: ubuntu-latest + + env: + BUNDLE_GEMFILE: Gemfile-maze-runner + + steps: + - uses: actions/checkout@v3 + + - name: Install libcurl4-openssl-dev and net-tools + run: | + sudo apt-get update + sudo apt-get install libcurl4-openssl-dev net-tools + + - name: install Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: 2.7 + bundler-cache: true + + - run: bundle exec maze-runner ${{ inputs.features }} --no-source + env: + NETWORK_NAME: notwerk + RUBY_TEST_VERSION: ${{ inputs.ruby-version }} + RACK_VERSION: ${{ inputs.rack-version }} + QUE_VERSION: ${{ inputs.que-version }} + RAILS_VERSION: ${{ inputs.rails-version }} + SIDEKIQ_VERSION: ${{ inputs.sidekiq-version }} From d595827c405f31e26be0e3cb49857bcd74277452 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 9 Nov 2022 12:38:42 +0000 Subject: [PATCH 17/61] Remove remaining Buildkite pipeline --- .buildkite/pipeline.yml | 392 ---------------------------------------- 1 file changed, 392 deletions(-) delete mode 100644 .buildkite/pipeline.yml diff --git a/.buildkite/pipeline.yml b/.buildkite/pipeline.yml deleted file mode 100644 index 3cc547bce..000000000 --- a/.buildkite/pipeline.yml +++ /dev/null @@ -1,392 +0,0 @@ -steps: - - label: ':docker: Build CI image' - timeout_in_minutes: 30 - plugins: - - docker-compose#v3.1.0: - build: ruby-maze-runner - image-repository: 855461928731.dkr.ecr.us-west-1.amazonaws.com/ruby - cache-from: ruby-maze-runner:855461928731.dkr.ecr.us-west-1.amazonaws.com/ruby:base-ruby${BRANCH_NAME} - - docker-compose#v3.1.0: - push: - - ruby-maze-runner:855461928731.dkr.ecr.us-west-1.amazonaws.com/ruby:base-ruby${BRANCH_NAME} - - ruby-maze-runner:855461928731.dkr.ecr.us-west-1.amazonaws.com/ruby:base-ruby-latest - - - wait - - - label: ':ruby: Ruby 2.7 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: "2.7" - - - label: ':rails: Rails 6 Ruby 2.7 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: "2.7" - RAILS_VERSION: "6" - - - label: ':rails: Rails 7 Ruby 2.7 tests' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-maze-runner - use-aliases: true - command: ["features/rails_features/", "--tags", "@rails7 and not @wip"] - env: - RUBY_TEST_VERSION: "2.7" - RAILS_VERSION: "7" - - label: ':rails: Rails integrations Ruby 2.7 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: "2.7" - RAILS_VERSION: "_integrations" - - - label: ':construction: Delayed job tests' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-maze-runner - use-aliases: true - command: ["features/delayed_job.feature", "--tags", "not @wip"] - env: - RUBY_TEST_VERSION: "2.5" - - - label: ':sidekiq: Sidekiq 6 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: "6" - - - wait - - - label: ':ruby: Ruby {{matrix}} plain tests' - matrix: - - "1.9.3" - - "2.0" - - "2.1" - - "2.2" - 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: "{{matrix}}" - BUNDLE_VERSION: "1.12.0" - concurrency: 8 - concurrency_group: 'ruby/slow-maze-runner-tests' - - - label: ':ruby: Ruby {{matrix}} plain tests' - matrix: - - '2.3' - - '2.4' - - '2.5' - - '2.6' - - '3.0' - - '3.1' - 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: "{{matrix}}" - concurrency: 8 - concurrency_group: 'ruby/slow-maze-runner-tests' - - - label: ':sidekiq: Sidekiq {{matrix}} tests' - matrix: - - '2' - - '3' - - '4' - - '5' - 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: "{{matrix}}" - concurrency: 4 - concurrency_group: 'ruby/integrations-maze-runner-tests' - - - label: ':rails: Rails 3 Ruby {{matrix}} tests' - matrix: - - '2.0' - - '2.1' - - '2.2' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-maze-runner - use-aliases: true - command: ["features/rails_features/", "--tags", "@rails3 and not @wip"] - env: - RUBY_TEST_VERSION: "{{matrix}}" - RAILS_VERSION: "3" - BUNDLE_VERSION: "1.12.0" - concurrency: 8 - concurrency_group: 'ruby/slow-maze-runner-tests' - - - label: ':rails: Rails 3 Ruby {{matrix}} tests' - matrix: - - '2.3' - - '2.4' - - '2.5' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-maze-runner - use-aliases: true - command: ["features/rails_features/", "--tags", "@rails3 and not @wip"] - env: - RUBY_TEST_VERSION: "{{matrix}}" - RAILS_VERSION: "3" - concurrency: 8 - concurrency_group: 'ruby/slow-maze-runner-tests' - - - label: ':rails: Rails 4 Ruby 2.2 tests' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-maze-runner - use-aliases: true - command: ["features/rails_features/", "--tags", "@rails4 and not @wip"] - env: - RUBY_TEST_VERSION: "2.2" - RAILS_VERSION: "4" - BUNDLE_VERSION: "1.12.0" - concurrency: 8 - concurrency_group: 'ruby/slow-maze-runner-tests' - - - label: ':rails: Rails 4 Ruby 2.3 tests' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-maze-runner - use-aliases: true - command: ["features/rails_features/", "--tags", "@rails4 and not @wip"] - env: - RUBY_TEST_VERSION: "2.3" - RAILS_VERSION: "4" - concurrency: 8 - concurrency_group: 'ruby/slow-maze-runner-tests' - - - label: ':rails: Rails 5 Ruby {{matrix}} tests' - matrix: - - '2.2' - - '2.3' - - '2.4' - - '2.5' - - '2.6' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-maze-runner - use-aliases: true - command: ["features/rails_features/", "--tags", "@rails5 and not @wip"] - env: - RUBY_TEST_VERSION: "{{matrix}}" - RAILS_VERSION: "5" - concurrency: 8 - concurrency_group: 'ruby/slow-maze-runner-tests' - - - label: ':rails: Rails 6 Ruby {{matrix}} tests' - matrix: - - '2.5' - - '2.6' - - '3.0' - - '3.1' - 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: "{{matrix}}" - RAILS_VERSION: "6" - concurrency: 8 - concurrency_group: 'ruby/slow-maze-runner-tests' - - - label: ':rails: Rails 7 Ruby {{matrix}} tests' - matrix: - - '3.0' - - '3.1' - timeout_in_minutes: 30 - plugins: - docker-compose#v3.1.0: - run: ruby-maze-runner - use-aliases: true - command: ["features/rails_features/", "--tags", "@rails7 and not @wip"] - env: - RUBY_TEST_VERSION: "{{matrix}}" - RAILS_VERSION: "7" - - - 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 {{matrix}} tests' - matrix: - - '1.9.3' - - '2.0' - - '2.1' - - '2.2' - - '2.3' - - '2.4' - - '2.5' - - '2.6' - - '2.7' - - '3.0' - - '3.1' - 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: "{{matrix}}" - concurrency: 4 - concurrency_group: 'ruby/integrations-maze-runner-tests' - - - label: ':postbox: Mailman Ruby {{matrix}} tests' - matrix: - - '2.0' - - '2.1' - - '2.2' - - '2.3' - - '2.4' - - '2.5' - - '2.6' - - '2.7' - - '3.0' - 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: "{{matrix}}" - concurrency: 4 - concurrency_group: 'ruby/integrations-maze-runner-tests' - - - label: ':key: Que 0.14 Ruby {{matrix}} tests' - matrix: - - '2.0' - - '2.1' - - '2.2' - - '2.3' - - '2.4' - - '2.5' - - '2.6' - - '2.7' - - '3.0' - 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: '{{matrix}}' - QUE_VERSION: '0.14' - concurrency: 4 - concurrency_group: 'ruby/integrations-maze-runner-tests' - - - label: ':key: Que 1.x Ruby {{matrix}} tests' - matrix: - - '2.5' - - '2.6' - - '2.7' - 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: '{{matrix}}' - QUE_VERSION: '1' - concurrency: 4 - concurrency_group: 'ruby/integrations-maze-runner-tests' - - - label: ':bed: Rack 1 Ruby {{matrix}} tests' - matrix: - - '1.9.3' - - '2.0' - - '2.1' - - '2.2' - - '2.3' - - '2.4' - - '2.5' - - '2.6' - - '2.7' - - '3.0' - 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: '{{matrix}}' - RACK_VERSION: '1' - concurrency: 4 - concurrency_group: 'ruby/integrations-maze-runner-tests' - - - label: ':bed: Rack 2 Ruby {{matrix}} tests' - matrix: - - '2.2' - - '2.3' - - '2.4' - - '2.5' - - '2.6' - - '2.7' - - '3.0' - - '3.1' - 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: '{{matrix}}' - RACK_VERSION: '2' - concurrency: 4 - concurrency_group: 'ruby/integrations-maze-runner-tests' From c19e7485ad53e864264e6e4e8ea8b043852e078b Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 10 Nov 2022 10:46:00 +0000 Subject: [PATCH 18/61] Allow running different versions of Maze Runner --- .github/workflows/run-maze-runner.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-maze-runner.yml b/.github/workflows/run-maze-runner.yml index 9a64c5030..4158c19e5 100644 --- a/.github/workflows/run-maze-runner.yml +++ b/.github/workflows/run-maze-runner.yml @@ -21,13 +21,18 @@ on: sidekiq-version: required: false type: string + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: + required: false + type: string + default: Gemfile-maze-runner jobs: maze-runner: runs-on: ubuntu-latest env: - BUNDLE_GEMFILE: Gemfile-maze-runner + BUNDLE_GEMFILE: ${{ inputs.gemfile }} steps: - uses: actions/checkout@v3 From d340f059f50c47cb4733e5f9bee7408f2a69163d Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 10 Nov 2022 10:47:21 +0000 Subject: [PATCH 19/61] Update steps to support Maze Runner v7 --- features/steps/ruby_notifier_steps.rb | 19 ++++++++-- features/support/env.rb | 54 +++++++++++++++++++++------ 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/features/steps/ruby_notifier_steps.rb b/features/steps/ruby_notifier_steps.rb index f80de1165..6e6f469f6 100644 --- a/features/steps/ruby_notifier_steps.rb +++ b/features/steps/ruby_notifier_steps.rb @@ -2,8 +2,16 @@ require "net/http" Then(/^the "(.+)" of the top non-bugsnag stackframe equals (\d+|".+")$/) do |element, value| - stacktrace = read_key_path(Server.current_request[:body], 'events.0.exceptions.0.stacktrace') + if using_maze_runner_v7? + body = Maze::Server.errors.current[:body] + stacktrace = Maze::Helper.read_key_path(body, 'events.0.exceptions.0.stacktrace') + else + body = Server.current_request[:body] + stacktrace = read_key_path(body, 'events.0.exceptions.0.stacktrace') + end + frame_index = stacktrace.find_index { |frame| ! /.*lib\/bugsnag.*\.rb/.match(frame["file"]) } + steps %Q{ the "#{element}" of stack frame #{frame_index} equals #{value} } @@ -172,10 +180,11 @@ Given("I configure the BUGSNAG_PROXY environment variables") do host = running_in_docker? ? "maze-runner" : current_ip + port = using_maze_runner_v7? ? Maze.config.port : MOCK_API_PORT steps %Q{ When I set environment variable "BUGSNAG_PROXY_HOST" to "#{host}" - And I set environment variable "BUGSNAG_PROXY_PORT" to "#{MOCK_API_PORT}" + And I set environment variable "BUGSNAG_PROXY_PORT" to "#{port}" And I set environment variable "BUGSNAG_PROXY_USER" to "tester" And I set environment variable "BUGSNAG_PROXY_PASSWORD" to "testpass" } @@ -183,16 +192,18 @@ Given("I configure the http_proxy environment variable") do host = running_in_docker? ? "maze-runner" : current_ip + port = using_maze_runner_v7? ? Maze.config.port : MOCK_API_PORT steps %Q{ - Given I set environment variable "http_proxy" to "http://tester:testpass@#{host}:#{MOCK_API_PORT}" + Given I set environment variable "http_proxy" to "http://tester:testpass@#{host}:#{port}" } end Given("I configure the https_proxy environment variable") do host = running_in_docker? ? "maze-runner" : current_ip + port = using_maze_runner_v7? ? Maze.config.port : MOCK_API_PORT steps %Q{ - Given I set environment variable "https_proxy" to "https://tester:testpass@#{host}:#{MOCK_API_PORT}" + Given I set environment variable "https_proxy" to "https://tester:testpass@#{host}:#{port}" } end diff --git a/features/support/env.rb b/features/support/env.rb index 4ba1eebb3..6f805ece3 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -38,19 +38,51 @@ def current_ip ip_list.captures.first end - -AfterConfiguration do |config| - install_fixture_gems +def using_maze_runner_v7? + defined?(Maze) end -Before do - Docker.compose_project_name = "#{rand.to_s}:#{Time.new.strftime("%s")}" - Runner.environment.clear - Runner.environment["BUGSNAG_API_KEY"] = $api_key +if using_maze_runner_v7? + Maze.hooks.before_all do + install_fixture_gems - if running_in_docker? - Runner.environment["BUGSNAG_ENDPOINT"] = "http://maze-runner:#{MOCK_API_PORT}" - else - Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{current_ip}:#{MOCK_API_PORT}" + # log to console, not a file + Maze.config.file_log = false + Maze.config.log_requests = true + + # don't wait so long for requests/not to receive requests + Maze.config.receive_requests_wait = 10 + Maze.config.receive_no_requests_wait = 10 + + # bugsnag-ruby doesn't need to send the integrity header + Maze.config.enforce_bugsnag_integrity = false + end + + Maze.hooks.before do + # Maze::Docker.compose_project_name = "#{rand.to_s}:#{Time.new.strftime("%s")}" + + Maze::Runner.environment["BUGSNAG_API_KEY"] = $api_key + + if running_in_docker? + Maze::Runner.environment["BUGSNAG_ENDPOINT"] = "http://maze-runner:#{Maze.config.port}/notify" + else + Maze::Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{current_ip}:#{Maze.config.port}/notify" + end + end +else + AfterConfiguration do |config| + install_fixture_gems + end + + Before do + Docker.compose_project_name = "#{rand.to_s}:#{Time.new.strftime("%s")}" + Runner.environment.clear + Runner.environment["BUGSNAG_API_KEY"] = $api_key + + if running_in_docker? + Runner.environment["BUGSNAG_ENDPOINT"] = "http://maze-runner:#{MOCK_API_PORT}" + else + Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{current_ip}:#{MOCK_API_PORT}" + end end end From cc0e1fb3bd890d11f660aca8d84337814e4ceea0 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 10 Nov 2022 10:47:49 +0000 Subject: [PATCH 20/61] Update plain ruby tests to run on Maze Runner v7 --- .github/workflows/maze-runner.yml | 2 ++ Gemfile-maze-runner-v7 | 3 +++ features/plain_features/add_tab.feature | 12 ++++++------ features/plain_features/app_type.feature | 6 +++--- features/plain_features/app_version.feature | 6 +++--- features/plain_features/auto_notify.feature | 1 - features/plain_features/delivery.feature | 12 ++++++------ features/plain_features/exception_data.feature | 16 ++++++++-------- features/plain_features/filters.feature | 12 ++++++------ features/plain_features/handled_errors.feature | 13 ++++++------- features/plain_features/ignore_classes.feature | 1 - features/plain_features/ignore_report.feature | 1 - features/plain_features/proxies.feature | 12 ++++++------ features/plain_features/release_stages.feature | 5 ++--- features/plain_features/report_api_key.feature | 6 +++--- features/plain_features/report_severity.feature | 4 ++-- .../plain_features/report_stack_frames.feature | 16 ++++++++-------- features/plain_features/report_user.feature | 12 ++++++------ features/plain_features/unhandled_errors.feature | 5 ++--- 19 files changed, 72 insertions(+), 73 deletions(-) create mode 100644 Gemfile-maze-runner-v7 diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 803c529fe..bdcf3c575 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -152,3 +152,5 @@ jobs: with: features: features/plain_features/ ruby-version: ${{ matrix.ruby-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 diff --git a/Gemfile-maze-runner-v7 b/Gemfile-maze-runner-v7 new file mode 100644 index 000000000..f3b8a82d0 --- /dev/null +++ b/Gemfile-maze-runner-v7 @@ -0,0 +1,3 @@ +source "https://rubygems.org" + +gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.5.1' diff --git a/features/plain_features/add_tab.feature b/features/plain_features/add_tab.feature index 6e2218b8a..d9c73ab79 100644 --- a/features/plain_features/add_tab.feature +++ b/features/plain_features/add_tab.feature @@ -3,8 +3,8 @@ Feature: Plain add tab to metadata Scenario Outline: Metadata can be added to a report using add_tab Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby report_modification/add_tab.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.additional_metadata.foo" equals "foo" And the event "metaData.additional_metadata.bar.0" equals "b" And the event "metaData.additional_metadata.bar.1" equals "a" @@ -21,8 +21,8 @@ Scenario Outline: Metadata can be added to a report using add_tab Scenario Outline: Metadata can be added to an existing tab using add_tab Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby report_modification/add_tab_existing.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.additional_metadata.foo" equals "foo" And the event "metaData.additional_metadata.bar.0" equals "b" And the event "metaData.additional_metadata.bar.1" equals "a" @@ -41,8 +41,8 @@ Scenario Outline: Metadata can be added to an existing tab using add_tab Scenario Outline: Metadata can be overwritten using add_tab Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby report_modification/add_tab_override.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.additional_metadata.foo" equals "foo" And the event "metaData.additional_metadata.bar" equals "bar" diff --git a/features/plain_features/app_type.feature b/features/plain_features/app_type.feature index a10228d84..60efcd3e0 100644 --- a/features/plain_features/app_type.feature +++ b/features/plain_features/app_type.feature @@ -3,6 +3,6 @@ Feature: App type configuration option Scenario: The App type configuration option can be set Given I set environment variable "BUGSNAG_APP_TYPE" to "test_app" When I run the service "plain-ruby" with the command "bundle exec ruby configuration/send_handled.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" - And the event "app.type" equals "test_app" \ No newline at end of file + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event "app.type" equals "test_app" diff --git a/features/plain_features/app_version.feature b/features/plain_features/app_version.feature index aba6ec692..af70a93fd 100644 --- a/features/plain_features/app_version.feature +++ b/features/plain_features/app_version.feature @@ -3,6 +3,6 @@ Feature: App version configuration option Scenario: The App version configuration option can be set Given I set environment variable "BUGSNAG_APP_VERSION" to "9.9.8" When I run the service "plain-ruby" with the command "bundle exec ruby configuration/send_handled.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" - And the event "app.version" equals "9.9.8" \ No newline at end of file + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event "app.version" equals "9.9.8" diff --git a/features/plain_features/auto_notify.feature b/features/plain_features/auto_notify.feature index fe025d367..f366e4dcf 100644 --- a/features/plain_features/auto_notify.feature +++ b/features/plain_features/auto_notify.feature @@ -3,5 +3,4 @@ Feature: Auto notify configuration option Scenario: When Auto-notify is false notifications are not sent Given I set environment variable "BUGSNAG_AUTO_NOTIFY" to "false" When I run the service "plain-ruby" with the command "bundle exec ruby configuration/send_unhandled.rb" - And I wait for 1 second Then I should receive no requests diff --git a/features/plain_features/delivery.feature b/features/plain_features/delivery.feature index 57f2f52d9..a4a92cb6e 100644 --- a/features/plain_features/delivery.feature +++ b/features/plain_features/delivery.feature @@ -2,19 +2,19 @@ Feature: delivery_method configuration option Scenario: When the delivery_method is set to :synchronous When I run the service "plain-ruby" with the command "bundle exec ruby delivery/synchronous.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.config" matches the JSON fixture in "features/fixtures/plain/json/delivery_synchronous.json" Scenario: When the delivery_method is set to :thread_queue When I run the service "plain-ruby" with the command "bundle exec ruby delivery/threadpool.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.config" matches the JSON fixture in "features/fixtures/plain/json/delivery_threadpool.json" Scenario: When the delivery_method is set to :thread_queue in a fork When I run the service "plain-ruby" with the command "bundle exec ruby delivery/fork_threadpool.rb" - And I wait to receive 2 requests - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive 2 errors + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "RuntimeError" And the event "metaData.config" matches the JSON fixture in "features/fixtures/plain/json/delivery_fork.json" diff --git a/features/plain_features/exception_data.feature b/features/plain_features/exception_data.feature index 3f16b9e8a..66db41f7b 100644 --- a/features/plain_features/exception_data.feature +++ b/features/plain_features/exception_data.feature @@ -2,8 +2,8 @@ Feature: Plain exception data Scenario Outline: An error has built in meta-data When I run the service "plain-ruby" with the command "bundle exec ruby exception_data/_meta_data.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "CustomError" And the event "metaData.exception.exception_type" equals "FATAL" And the event "metaData.exception.exception_base" equals "RuntimeError" @@ -15,8 +15,8 @@ Scenario Outline: An error has built in meta-data Scenario Outline: An error has built in context When I run the service "plain-ruby" with the command "bundle exec ruby exception_data/_context.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "CustomError" And the event "context" equals "IntegrationTests" @@ -27,8 +27,8 @@ Scenario Outline: An error has built in context Scenario Outline: An error has built in grouping hash When I run the service "plain-ruby" with the command "bundle exec ruby exception_data/_hash.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "CustomError" And the event "groupingHash" equals "ABCDE12345" @@ -39,8 +39,8 @@ Scenario Outline: An error has built in grouping hash Scenario Outline: An error has built in user id When I run the service "plain-ruby" with the command "bundle exec ruby exception_data/_user_id.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "CustomError" And the event "user.id" equals "000001" diff --git a/features/plain_features/filters.feature b/features/plain_features/filters.feature index bedf87fa5..ffe215922 100644 --- a/features/plain_features/filters.feature +++ b/features/plain_features/filters.feature @@ -2,20 +2,20 @@ Feature: Plain filtering of metadata Scenario: Metadata is filtered through the default filters When I run the service "plain-ruby" with the command "bundle exec ruby filters/default_filters.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.filter" matches the JSON fixture in "features/fixtures/plain/json/filters_default_metadata_filters.json" Scenario: Additional filters can be added to the filter list Given I set environment variable "BUGSNAG_META_DATA_FILTERS" to "filter_me" When I run the service "plain-ruby" with the command "bundle exec ruby filters/additional_filters.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.filter.filter_me" equals "[FILTERED]" Scenario: Redacted keys can also be used to filter sensitive data Given I set environment variable "BUGSNAG_REDACTED_KEYS" to "filter_me" When I run the service "plain-ruby" with the command "bundle exec ruby filters/additional_filters.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.filter.filter_me" equals "[FILTERED]" diff --git a/features/plain_features/handled_errors.feature b/features/plain_features/handled_errors.feature index ff97e32e9..3fdcc3f92 100644 --- a/features/plain_features/handled_errors.feature +++ b/features/plain_features/handled_errors.feature @@ -2,8 +2,8 @@ Feature: Plain handled errors Scenario: A rescued exception sends a report When I run the service "plain-ruby" with the command "bundle exec ruby handled/notify_exception.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" @@ -14,8 +14,8 @@ Scenario: A rescued exception sends a report Scenario: A notified string sends a report When I run the service "plain-ruby" with the command "bundle exec ruby handled/notify_string.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" @@ -26,13 +26,12 @@ Scenario: A notified string sends a report Scenario: A handled error doesn't send a report when the :skip_bugsnag flag is set When I run the service "plain-ruby" with the command "bundle exec ruby handled/ignore_exception.rb" - And I wait for 1 second Then I should receive no requests Scenario: A handled error can attach metadata in a block When I run the service "plain-ruby" with the command "bundle exec ruby handled/block_metadata.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" diff --git a/features/plain_features/ignore_classes.feature b/features/plain_features/ignore_classes.feature index 83c9dd86d..239f1062a 100644 --- a/features/plain_features/ignore_classes.feature +++ b/features/plain_features/ignore_classes.feature @@ -2,7 +2,6 @@ Feature: Plain ignore classes Scenario Outline: An errors class is in the ignore_classes array When I run the service "plain-ruby" with the command "bundle exec ruby ignore_classes/.rb" - And I wait for 1 second Then I should receive no requests Examples: diff --git a/features/plain_features/ignore_report.feature b/features/plain_features/ignore_report.feature index c5c837829..584723f71 100644 --- a/features/plain_features/ignore_report.feature +++ b/features/plain_features/ignore_report.feature @@ -3,7 +3,6 @@ Feature: Plain ignore report Scenario Outline: A reports severity can be modified Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby report_modification/ignore_report.rb" - And I wait for 1 second Then I should receive no requests Examples: diff --git a/features/plain_features/proxies.feature b/features/plain_features/proxies.feature index d5f14c97b..5eacdef5b 100644 --- a/features/plain_features/proxies.feature +++ b/features/plain_features/proxies.feature @@ -3,20 +3,20 @@ Feature: proxy configuration options Scenario: Proxy settings are provided as configuration options Given I configure the BUGSNAG_PROXY environment variables When I run the service "plain-ruby" with the command "bundle exec ruby configuration/proxy.rb" - Then I wait to receive a request - And the "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" + Then I wait to receive an error + And the error "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" And the event "metaData.proxy.user" equals "tester" Scenario: Proxy settings are provided as the HTTP_PROXY environment variable Given I configure the http_proxy environment variable When I run the service "plain-ruby" with the command "bundle exec ruby configuration/proxy.rb" - Then I wait to receive a request - And the "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" + Then I wait to receive an error + And the error "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" And the event "metaData.proxy.user" equals "tester" Scenario: Proxy settings are provided as the HTTPS_PROXY environment variable Given I configure the https_proxy environment variable When I run the service "plain-ruby" with the command "bundle exec ruby configuration/proxy.rb" - Then I wait to receive a request - And the "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" + Then I wait to receive an error + And the error "proxy-authorization" header equals "Basic dGVzdGVyOnRlc3RwYXNz" And the event "metaData.proxy.user" equals "tester" diff --git a/features/plain_features/release_stages.feature b/features/plain_features/release_stages.feature index a9854f505..d48fe340e 100644 --- a/features/plain_features/release_stages.feature +++ b/features/plain_features/release_stages.feature @@ -4,15 +4,14 @@ Scenario: Doesn't notify in the wrong release stage Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGE" to "stage_one" And I set environment variable "BUGSNAG_RELEASE_STAGE" to "stage_two" When I run the service "plain-ruby" with the command "bundle exec ruby configuration/send_unhandled.rb" - And I wait for 1 second Then I should receive no requests Scenario: Does notify in the correct release stage Given I set environment variable "BUGSNAG_NOTIFY_RELEASE_STAGE" to "stage_one" And I set environment variable "BUGSNAG_RELEASE_STAGE" to "stage_one" When I run the service "plain-ruby" with the command "bundle exec ruby configuration/send_unhandled.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledException" diff --git a/features/plain_features/report_api_key.feature b/features/plain_features/report_api_key.feature index 6821c6125..ff36392fe 100644 --- a/features/plain_features/report_api_key.feature +++ b/features/plain_features/report_api_key.feature @@ -3,9 +3,9 @@ Feature: Plain report modify api key Scenario Outline: A report can have its api_key modified Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby report_modification/modify_api_key.rb" - And I wait to receive a request - Then the "Bugsnag-Api-Key" header equals "abcdefghijklmnopqrstuvwxyz123456" - And the payload field "apiKey" equals "abcdefghijklmnopqrstuvwxyz123456" + And I wait to receive an error + Then the error "Bugsnag-Api-Key" header equals "abcdefghijklmnopqrstuvwxyz123456" + And the error payload field "apiKey" equals "abcdefghijklmnopqrstuvwxyz123456" Examples: | initiator | diff --git a/features/plain_features/report_severity.feature b/features/plain_features/report_severity.feature index c96dc258c..ca7ef57be 100644 --- a/features/plain_features/report_severity.feature +++ b/features/plain_features/report_severity.feature @@ -3,8 +3,8 @@ Feature: Plain report modify severity Scenario Outline: A reports severity can be modified Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby report_modification/modify_severity.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "severity" equals "info" And the event "severityReason.type" equals "userCallbackSetSeverity" diff --git a/features/plain_features/report_stack_frames.feature b/features/plain_features/report_stack_frames.feature index ce34204e1..5569f0222 100644 --- a/features/plain_features/report_stack_frames.feature +++ b/features/plain_features/report_stack_frames.feature @@ -3,8 +3,8 @@ Feature: Plain report modify stack frames Scenario Outline: Stack frames can be removed Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby stack_frame_modification/remove_stack_frame.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the "file" of the top non-bugsnag stackframe equals "/usr/src/app/stack_frame_modification/initiators/.rb" And the "lineNumber" of stack frame 0 equals @@ -18,16 +18,16 @@ Scenario Outline: Stack frames can be removed Scenario: Stack frames can be removed from a notified string Given I set environment variable "CALLBACK_INITIATOR" to "handled_block" When I run the service "plain-ruby" with the command "bundle exec ruby stack_frame_modification/remove_stack_frame.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the "file" of the top non-bugsnag stackframe equals "/usr/src/app/stack_frame_modification/initiators/handled_block.rb" And the "lineNumber" of the top non-bugsnag stackframe equals 19 Scenario Outline: Stack frames can be marked as in project Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby stack_frame_modification/mark_frames_in_project.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the "file" of stack frame 0 equals "/usr/src/app/stack_frame_modification/initiators/.rb" And the event "exceptions.0.stacktrace.0.inProject" is null And the event "exceptions.0.stacktrace.1.inProject" is true @@ -44,8 +44,8 @@ Scenario Outline: Stack frames can be marked as in project Scenario: Stack frames can be marked as in project with a handled string Given I set environment variable "CALLBACK_INITIATOR" to "handled_block" And I run the service "plain-ruby" with the command "bundle exec ruby stack_frame_modification/mark_frames_in_project.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the "file" of the top non-bugsnag stackframe equals "/usr/src/app/stack_frame_modification/initiators/handled_block.rb" And the event "exceptions.0.stacktrace.0.inProject" is null And the event "exceptions.0.stacktrace.1.inProject" is true diff --git a/features/plain_features/report_user.feature b/features/plain_features/report_user.feature index 4f4a34fe5..4ddc5133c 100644 --- a/features/plain_features/report_user.feature +++ b/features/plain_features/report_user.feature @@ -3,8 +3,8 @@ Feature: Plain report modify user Scenario Outline: A report can have a user name, email, and id set Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby report_modification/set_user_details.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "user.name" equals "leo testman" And the event "user.email" equals "test@test.com" And the event "user.id" equals "0001" @@ -20,8 +20,8 @@ Scenario Outline: A report can have a user name, email, and id set Scenario Outline: A report can have custom info set Given I set environment variable "CALLBACK_INITIATOR" to "" And I run the service "plain-ruby" with the command "bundle exec ruby report_modification/set_custom_user_details.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "user.type" equals "amateur" And the event "user.location" equals "testville" And the event "user.details.a" equals "foo" @@ -38,8 +38,8 @@ Scenario Outline: A report can have custom info set Scenario Outline: A report can have its user info removed Given I set environment variable "CALLBACK_INITIATOR" to "" When I run the service "plain-ruby" with the command "bundle exec ruby report_modification/remove_user_details.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "user" is null Examples: diff --git a/features/plain_features/unhandled_errors.feature b/features/plain_features/unhandled_errors.feature index 6bca5787f..f2f075da4 100644 --- a/features/plain_features/unhandled_errors.feature +++ b/features/plain_features/unhandled_errors.feature @@ -2,8 +2,8 @@ Feature: Plain unhandled errors Scenario Outline: An unhandled error sends a report Given I run the service "plain-ruby" with the command " unhandled/.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledException" @@ -33,7 +33,6 @@ Scenario Outline: An unhandled error sends a report Scenario Outline: An unhandled error doesn't send a report When I run the service "plain-ruby" with the command " unhandled/.rb" - And I wait for 1 second Then I should receive no requests Examples: From 2f416c5cece746a0fa78e23b4d33290bc68463f6 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 10:37:08 +0000 Subject: [PATCH 21/61] Run Delayed Job tests with Maze Runner v7 --- .github/workflows/maze-runner.yml | 2 ++ features/delayed_job.feature | 16 ++++++++-------- features/fixtures/delayed_job/app/Rakefile | 8 ++++---- 3 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index bdcf3c575..7565a4626 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -100,6 +100,8 @@ jobs: with: features: features/delayed_job.feature ruby-version: ${{ matrix.ruby-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 rails-3-4-5-maze-runner: strategy: diff --git a/features/delayed_job.feature b/features/delayed_job.feature index c94c31c3c..0c7f31b46 100644 --- a/features/delayed_job.feature +++ b/features/delayed_job.feature @@ -2,8 +2,8 @@ Feature: Bugsnag detects errors in Delayed job workers Scenario: An unhandled RuntimeError sends a report with arguments Given I run the service "delayed_job" with the command "bundle exec rake delayed_job_tests:fail_with_args" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "context" equals "TestModel.fail_with_args" @@ -17,13 +17,13 @@ Scenario: An unhandled RuntimeError sends a report with arguments And the event "metaData.job.max_attempts" equals 1 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 "metaData.job.payload.args.0" equals "Test" 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" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "context" equals "TestModel.notify_with_args" @@ -36,13 +36,13 @@ Scenario: A handled exception sends a report And the event "metaData.job.max_attempts" equals 1 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 "metaData.job.payload.args.0" equals "Test" 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" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "context" equals "TestReportContextJob" diff --git a/features/fixtures/delayed_job/app/Rakefile b/features/fixtures/delayed_job/app/Rakefile index efcae1adb..5a4d14a1e 100644 --- a/features/fixtures/delayed_job/app/Rakefile +++ b/features/fixtures/delayed_job/app/Rakefile @@ -20,9 +20,9 @@ namespace :delayed_job_tests do end def run_delayed_job_test(command) - fork do - system("rake jobs:work") - end + # queue the job with rails' runner system("rails runner #{command}") - Process.wait + + # run the queued jobs and exit + system("rake jobs:workoff") end From 7da9df5bc05d21690974e7c38839cc17ce070d8e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 10:43:44 +0000 Subject: [PATCH 22/61] Run Mailman tests with Maze Runner v7 --- .github/workflows/maze-runner.yml | 2 ++ features/mailman.feature | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 7565a4626..feea70e61 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -24,6 +24,8 @@ jobs: with: features: features/mailman.feature ruby-version: ${{ matrix.ruby-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 rack-maze-runner: strategy: diff --git a/features/mailman.feature b/features/mailman.feature index 60814d2f7..12b4fe307 100644 --- a/features/mailman.feature +++ b/features/mailman.feature @@ -4,8 +4,8 @@ Scenario: An unhandled RuntimeError sends a report Given I set environment variable "TARGET_EMAIL" to "emails/unhandled_error.eml" And I set environment variable "APP_PATH" to "/usr/src" And I start the service "mailman" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledExceptionMiddleware" @@ -17,8 +17,8 @@ Scenario: A handled RuntimeError sends a report Given I set environment variable "TARGET_EMAIL" to "emails/handled_error.eml" And I set environment variable "APP_PATH" to "/usr/src" And I start the service "mailman" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" From 24814976a66ccf5ee2913aae071e38f408c75a24 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 11:05:56 +0000 Subject: [PATCH 23/61] Run Que tests with Maze Runner v7 --- .github/workflows/maze-runner.yml | 2 ++ features/que.feature | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index feea70e61..2dce1d181 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -78,6 +78,8 @@ jobs: features: features/que.feature ruby-version: ${{ matrix.ruby-version }} que-version: ${{ matrix.que-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 sidekiq-maze-runner: strategy: diff --git a/features/que.feature b/features/que.feature index dbf417651..9e872f7d5 100644 --- a/features/que.feature +++ b/features/que.feature @@ -2,9 +2,9 @@ Feature: Errors are delivered to Bugsnag from Que Scenario: Que will deliver unhandled errors Given I run the service "que" with the command "bundle exec ruby app.rb unhandled" - And I run the service "que" with the command "bundle exec que ./app.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I run the service "que" with the command "timeout 5 bundle exec que ./app.rb" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledExceptionMiddleware" @@ -15,9 +15,9 @@ Scenario: Que will deliver unhandled errors Scenario: Que will deliver handled errors Given I run the service "que" with the command "bundle exec ruby app.rb handled" - And I run the service "que" with the command "bundle exec que ./app.rb" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I run the service "que" with the command "timeout 5 bundle exec que ./app.rb" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" From 4fce1f8dbb270545c2334bff9e2e1a371ff90e6d Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 11:33:57 +0000 Subject: [PATCH 24/61] Run Rake tests with Maze Runner v7 --- .github/workflows/maze-runner.yml | 2 ++ features/rake.feature | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 2dce1d181..8db526571 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -13,6 +13,8 @@ jobs: with: features: features/rake.feature ruby-version: ${{ matrix.ruby-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 mailman-maze-runner: strategy: diff --git a/features/rake.feature b/features/rake.feature index 5e3fbf421..21066e629 100644 --- a/features/rake.feature +++ b/features/rake.feature @@ -2,8 +2,8 @@ Feature: Bugsnag raises errors in Rake Scenario: An unhandled RuntimeError sends a report Given I run the service "rake" with the command "bundle exec rake unhandled" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledExceptionMiddleware" @@ -13,8 +13,8 @@ Scenario: An unhandled RuntimeError sends a report Scenario: A handled RuntimeError sends a report Given I run the service "rake" with the command "bundle exec rake handled" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" From 87ba1b0dfe37a9124bc4b87014b4a8b5ced9a23d Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 11:42:05 +0000 Subject: [PATCH 25/61] Run Rack tests with Maze Runner v7 --- .github/workflows/maze-runner.yml | 2 ++ features/rack.feature | 24 ++++++++++++------------ 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 8db526571..92c0da4a3 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -50,6 +50,8 @@ jobs: features: features/rack.feature ruby-version: ${{ matrix.ruby-version }} rack-version: ${{ matrix.rack-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 que-maze-runner: strategy: diff --git a/features/rack.feature b/features/rack.feature index b8089d57b..5bb05027d 100644 --- a/features/rack.feature +++ b/features/rack.feature @@ -3,8 +3,8 @@ Feature: Bugsnag raises errors in Rack Scenario: An unhandled RuntimeError sends a report Given I start the rack service When I navigate to the route "/unhandled?a=123&b=456" on the rack app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledExceptionMiddleware" @@ -27,8 +27,8 @@ Scenario: An unhandled RuntimeError sends a report Scenario: A handled RuntimeError sends a report Given I start the rack service When I navigate to the route "/handled?a=123&b=456" on the rack app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" @@ -53,8 +53,8 @@ Scenario: A POST request with form data sends a report with the parsed request b | name | baba | | favourite_letter | z | | password | password1 | - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.request.body.name" equals "baba" And the event "metaData.request.body.favourite_letter" equals "z" And the event "metaData.request.body.password" equals "[FILTERED]" @@ -76,8 +76,8 @@ Scenario: A POST request with JSON sends a report with the parsed request body a | name | baba | | favourite_letter | z | | password | password1 | - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.request.body.name" equals "baba" And the event "metaData.request.body.favourite_letter" equals "z" And the event "metaData.request.body.password" equals "[FILTERED]" @@ -99,8 +99,8 @@ Scenario: A request with cookies will be filtered out by default | a | b | | c | d | | e | f | - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.request.cookie" is null And the event "metaData.request.headers.Cookie" equals "[FILTERED]" And the event "metaData.request.clientIp" is not null @@ -121,8 +121,8 @@ Scenario: A request with cookies and no matching filter will set cookies in meta | a | b | | c | d | | e | f | - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.request.cookies.a" equals "b" And the event "metaData.request.cookies.c" equals "d" And the event "metaData.request.cookies.e" equals "f" From 172036c846259c1d6409ae1b298b1e484710e43a Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 11:50:05 +0000 Subject: [PATCH 26/61] Run Sidekiq tests with Maze Runner v7 --- .github/workflows/maze-runner.yml | 2 ++ features/sidekiq.feature | 24 ++++++++++++------------ features/steps/ruby_notifier_steps.rb | 8 ++++---- 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 92c0da4a3..930fa5c00 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -97,6 +97,8 @@ jobs: features: features/sidekiq.feature ruby-version: ${{ matrix.ruby-version }} sidekiq-version: ${{ matrix.sidekiq-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 delayed-job-maze-runner: strategy: diff --git a/features/sidekiq.feature b/features/sidekiq.feature index b4df3dea6..f3f398841 100644 --- a/features/sidekiq.feature +++ b/features/sidekiq.feature @@ -1,9 +1,9 @@ Feature: Bugsnag raises errors in Sidekiq workers Scenario: An unhandled RuntimeError sends a report - Given I run the service "sidekiq" with the command "bundle exec rake sidekiq_tests:unhandled_error" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + Given I run the service "sidekiq" with the command "timeout 5 bundle exec rake sidekiq_tests:unhandled_error" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "context" equals "UnhandledError@default" @@ -13,38 +13,38 @@ Scenario: An unhandled RuntimeError sends a report And the exception "errorClass" equals "RuntimeError" And the "file" of stack frame 0 equals "/app/app.rb" And the "lineNumber" of stack frame 0 equals 44 - And the payload field "events.0.metaData.sidekiq" matches the appropriate Sidekiq unhandled payload + And the event "metaData.sidekiq" matches the appropriate Sidekiq unhandled payload And the event "metaData.sidekiq.msg.created_at" is a parsable timestamp in seconds And the event "metaData.sidekiq.msg.enqueued_at" is a parsable timestamp in seconds And the event "metaData.config.delivery_method" equals "thread_queue" Scenario: A handled RuntimeError can be notified - Given I run the service "sidekiq" with the command "bundle exec rake sidekiq_tests:handled_error" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + Given I run the service "sidekiq" with the command "timeout 5 bundle exec rake sidekiq_tests:handled_error" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "context" equals "HandledError@default" And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" And the event "app.type" equals "sidekiq" And the exception "errorClass" equals "RuntimeError" - And the payload field "events.0.metaData.sidekiq" matches the appropriate Sidekiq handled payload + And the event "metaData.sidekiq" matches the appropriate Sidekiq handled payload And the event "metaData.sidekiq.msg.created_at" is a parsable timestamp in seconds And the event "metaData.sidekiq.msg.enqueued_at" is a parsable timestamp in seconds And the event "metaData.config.delivery_method" equals "thread_queue" Scenario: Synchronous delivery can be used Given I set environment variable "BUGSNAG_DELIVERY_METHOD" to "synchronous" - And I run the service "sidekiq" with the command "bundle exec rake sidekiq_tests:handled_error" - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I run the service "sidekiq" with the command "timeout 5 bundle exec rake sidekiq_tests:handled_error" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "context" equals "HandledError@default" And the event "severity" equals "warning" And the event "severityReason.type" equals "handledException" And the event "app.type" equals "sidekiq" And the exception "errorClass" equals "RuntimeError" - And the payload field "events.0.metaData.sidekiq" matches the appropriate Sidekiq handled payload + And the event "metaData.sidekiq" matches the appropriate Sidekiq handled payload And the event "metaData.sidekiq.msg.created_at" is a parsable timestamp in seconds And the event "metaData.sidekiq.msg.enqueued_at" is a parsable timestamp in seconds And the event "metaData.config.delivery_method" equals "synchronous" diff --git a/features/steps/ruby_notifier_steps.rb b/features/steps/ruby_notifier_steps.rb index 6e6f469f6..7fe2e80ee 100644 --- a/features/steps/ruby_notifier_steps.rb +++ b/features/steps/ruby_notifier_steps.rb @@ -102,21 +102,21 @@ RACK_FIXTURE.post_json(route, data.rows_hash) end -Then("the payload field {string} matches the appropriate Sidekiq handled payload") do |field| +Then("the event {string} matches the appropriate Sidekiq handled payload") do |field| # 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" + And the event "#{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| +Then("the event {string} matches the appropriate Sidekiq unhandled payload") do |field| # 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" + And the event "#{field}" matches the JSON fixture in "features/fixtures/sidekiq/payloads/unhandled_metadata_ca_#{created_at_present}.json" } end From 21a2869e3c1193304d04adef1041097ff85ab6cb Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 15:46:45 +0000 Subject: [PATCH 27/61] Use separate endpoints for notify & sessions --- features/fixtures/docker-compose.yml | 1 + .../rails3/app/config/initializers/bugsnag.rb | 2 +- .../rails4/app/config/initializers/bugsnag.rb | 2 +- .../rails5/app/config/initializers/bugsnag.rb | 2 +- .../rails6/app/config/initializers/bugsnag.rb | 2 +- .../rails7/app/config/initializers/bugsnag.rb | 2 +- .../app/config/initializers/bugsnag.rb | 2 +- features/support/env.rb | 18 ++++++++---------- 8 files changed, 15 insertions(+), 16 deletions(-) diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index c48f728c1..e39120361 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -323,6 +323,7 @@ services: environment: - BUGSNAG_API_KEY - BUGSNAG_ENDPOINT + - BUGSNAG_SESSION_ENDPOINT - DB_USERNAME=postgres - DB_PASSWORD=test_password - DB_HOST=postgres diff --git a/features/fixtures/rails3/app/config/initializers/bugsnag.rb b/features/fixtures/rails3/app/config/initializers/bugsnag.rb index ca5b93ca7..c044fb40e 100644 --- a/features/fixtures/rails3/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails3/app/config/initializers/bugsnag.rb @@ -1,7 +1,7 @@ Bugsnag.configure do |config| config.api_key = ENV["BUGSNAG_API_KEY"] || ENV["BUGSNAG_API_KEY"] config.endpoint = ENV["BUGSNAG_ENDPOINT"] || ENV["BUGSNAG_ENDPOINT"] - config.session_endpoint = ENV["BUGSNAG_ENDPOINT"] || ENV["BUGSNAG_ENDPOINT"] + config.session_endpoint = ENV["BUGSNAG_SESSION_ENDPOINT"] || ENV["BUGSNAG_SESSION_ENDPOINT"] config.app_type = ENV["BUGSNAG_APP_TYPE"] if ENV.include? "BUGSNAG_APP_TYPE" config.app_version = ENV["BUGSNAG_APP_VERSION"] if ENV.include? "BUGSNAG_APP_VERSION" config.auto_notify = ENV["BUGSNAG_AUTO_NOTIFY"] != "false" diff --git a/features/fixtures/rails4/app/config/initializers/bugsnag.rb b/features/fixtures/rails4/app/config/initializers/bugsnag.rb index ca5b93ca7..c044fb40e 100644 --- a/features/fixtures/rails4/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails4/app/config/initializers/bugsnag.rb @@ -1,7 +1,7 @@ Bugsnag.configure do |config| config.api_key = ENV["BUGSNAG_API_KEY"] || ENV["BUGSNAG_API_KEY"] config.endpoint = ENV["BUGSNAG_ENDPOINT"] || ENV["BUGSNAG_ENDPOINT"] - config.session_endpoint = ENV["BUGSNAG_ENDPOINT"] || ENV["BUGSNAG_ENDPOINT"] + config.session_endpoint = ENV["BUGSNAG_SESSION_ENDPOINT"] || ENV["BUGSNAG_SESSION_ENDPOINT"] config.app_type = ENV["BUGSNAG_APP_TYPE"] if ENV.include? "BUGSNAG_APP_TYPE" config.app_version = ENV["BUGSNAG_APP_VERSION"] if ENV.include? "BUGSNAG_APP_VERSION" config.auto_notify = ENV["BUGSNAG_AUTO_NOTIFY"] != "false" diff --git a/features/fixtures/rails5/app/config/initializers/bugsnag.rb b/features/fixtures/rails5/app/config/initializers/bugsnag.rb index 258d13b59..c92eb03eb 100644 --- a/features/fixtures/rails5/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails5/app/config/initializers/bugsnag.rb @@ -1,7 +1,7 @@ Bugsnag.configure do |config| config.api_key = ENV["BUGSNAG_API_KEY"] || ENV["BUGSNAG_API_KEY"] config.endpoint = ENV["BUGSNAG_ENDPOINT"] || ENV["BUGSNAG_ENDPOINT"] - config.session_endpoint = ENV["BUGSNAG_ENDPOINT"] || ENV["BUGSNAG_ENDPOINT"] + config.session_endpoint = ENV["BUGSNAG_SESSION_ENDPOINT"] || ENV["BUGSNAG_SESSION_ENDPOINT"] config.app_type = ENV["BUGSNAG_APP_TYPE"] if ENV.include? "BUGSNAG_APP_TYPE" config.app_version = ENV["BUGSNAG_APP_VERSION"] if ENV.include? "BUGSNAG_APP_VERSION" config.auto_notify = ENV["BUGSNAG_AUTO_NOTIFY"] != "false" diff --git a/features/fixtures/rails6/app/config/initializers/bugsnag.rb b/features/fixtures/rails6/app/config/initializers/bugsnag.rb index ca5b93ca7..c044fb40e 100644 --- a/features/fixtures/rails6/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails6/app/config/initializers/bugsnag.rb @@ -1,7 +1,7 @@ Bugsnag.configure do |config| config.api_key = ENV["BUGSNAG_API_KEY"] || ENV["BUGSNAG_API_KEY"] config.endpoint = ENV["BUGSNAG_ENDPOINT"] || ENV["BUGSNAG_ENDPOINT"] - config.session_endpoint = ENV["BUGSNAG_ENDPOINT"] || ENV["BUGSNAG_ENDPOINT"] + config.session_endpoint = ENV["BUGSNAG_SESSION_ENDPOINT"] || ENV["BUGSNAG_SESSION_ENDPOINT"] config.app_type = ENV["BUGSNAG_APP_TYPE"] if ENV.include? "BUGSNAG_APP_TYPE" config.app_version = ENV["BUGSNAG_APP_VERSION"] if ENV.include? "BUGSNAG_APP_VERSION" config.auto_notify = ENV["BUGSNAG_AUTO_NOTIFY"] != "false" diff --git a/features/fixtures/rails7/app/config/initializers/bugsnag.rb b/features/fixtures/rails7/app/config/initializers/bugsnag.rb index e66cd1714..4c2b76588 100644 --- a/features/fixtures/rails7/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails7/app/config/initializers/bugsnag.rb @@ -1,7 +1,7 @@ Bugsnag.configure do |config| config.api_key = ENV["BUGSNAG_API_KEY"] config.endpoint = ENV["BUGSNAG_ENDPOINT"] - config.session_endpoint = ENV["BUGSNAG_ENDPOINT"] + config.session_endpoint = ENV["BUGSNAG_SESSION_ENDPOINT"] config.app_type = ENV["BUGSNAG_APP_TYPE"] if ENV.include? "BUGSNAG_APP_TYPE" config.app_version = ENV["BUGSNAG_APP_VERSION"] if ENV.include? "BUGSNAG_APP_VERSION" config.auto_notify = ENV["BUGSNAG_AUTO_NOTIFY"] != "false" diff --git a/features/fixtures/rails_integrations/app/config/initializers/bugsnag.rb b/features/fixtures/rails_integrations/app/config/initializers/bugsnag.rb index 38eeba564..d0b4d3107 100644 --- a/features/fixtures/rails_integrations/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails_integrations/app/config/initializers/bugsnag.rb @@ -1,7 +1,7 @@ Bugsnag.configure do |config| config.api_key = ENV['BUGSNAG_API_KEY'] config.endpoint = ENV['BUGSNAG_ENDPOINT'] - config.session_endpoint = ENV['BUGSNAG_ENDPOINT'] + config.session_endpoint = ENV['BUGSNAG_SESSION_ENDPOINT'] config.add_on_error(proc do |report| report.add_tab(:config, { diff --git a/features/support/env.rb b/features/support/env.rb index 6f805ece3..3fda13727 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -63,11 +63,10 @@ def using_maze_runner_v7? Maze::Runner.environment["BUGSNAG_API_KEY"] = $api_key - if running_in_docker? - Maze::Runner.environment["BUGSNAG_ENDPOINT"] = "http://maze-runner:#{Maze.config.port}/notify" - else - Maze::Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{current_ip}:#{Maze.config.port}/notify" - end + host = running_in_docker? ? "maze-runner" : current_ip + + Maze::Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{host}:#{Maze.config.port}/notify" + Maze::Runner.environment["BUGSNAG_SESSION_ENDPOINT"] = "http://#{host}:#{Maze.config.port}/sessions" end else AfterConfiguration do |config| @@ -79,10 +78,9 @@ def using_maze_runner_v7? Runner.environment.clear Runner.environment["BUGSNAG_API_KEY"] = $api_key - if running_in_docker? - Runner.environment["BUGSNAG_ENDPOINT"] = "http://maze-runner:#{MOCK_API_PORT}" - else - Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{current_ip}:#{MOCK_API_PORT}" - end + host = running_in_docker? ? "maze-runner" : current_ip + + Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{host}:#{MOCK_API_PORT}" + Runner.environment["BUGSNAG_SESSION_ENDPOINT"] = "http://#{host}:#{MOCK_API_PORT}" end end From d92d9153095a3889a70658f2f571e4de7caa0f12 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 15:47:13 +0000 Subject: [PATCH 28/61] Run Rails 3-7 tests with Maze Runner v7 --- .github/workflows/maze-runner.yml | 23 +++++++++++++++---- features/rails_features/active_job.feature | 12 +++++----- features/rails_features/active_record.feature | 8 +++---- features/rails_features/api_key.feature | 8 +++---- features/rails_features/app_type.feature | 16 ++++++------- features/rails_features/app_version.feature | 12 +++++----- .../auto_capture_sessions.feature | 13 +++++------ features/rails_features/auto_notify.feature | 10 ++++---- features/rails_features/before_notify.feature | 12 +++++----- features/rails_features/breadcrumbs.feature | 20 ++++++++-------- features/rails_features/handled.feature | 12 +++++----- .../rails_features/meta_data_filters.feature | 4 ++-- .../rails_features/mongo_breadcrumbs.feature | 12 +++++----- features/rails_features/on_error.feature | 8 +++---- features/rails_features/project_root.feature | 12 +++++----- features/rails_features/release_stage.feature | 12 +++++----- features/rails_features/request.feature | 8 +++---- features/rails_features/send_code.feature | 8 +++---- .../rails_features/send_environment.feature | 4 ++-- features/rails_features/unhandled.feature | 4 ++-- features/rails_features/user_info.feature | 12 +++++----- features/steps/ruby_notifier_steps.rb | 11 +++++++-- 22 files changed, 129 insertions(+), 112 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 930fa5c00..cf2c9e9da 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -132,27 +132,40 @@ jobs: features: features/rails_features/ --tags @rails${{ matrix.rails-version }} ruby-version: ${{ matrix.ruby-version }} rails-version: ${{ matrix.rails-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 - rails-6-7-integrations-maze-runner: + rails-6-7-maze-runner: strategy: fail-fast: false matrix: ruby-version: ['2.7', '3.0', '3.1'] - rails-version: ['6', '7', '_integrations'] + rails-version: ['6', '7'] include: - ruby-version: '2.5' rails-version: '6' - ruby-version: '2.6' rails-version: '6' - exclude: - - ruby-version: '3.1' - rails-version: '_integrations' uses: ./.github/workflows/run-maze-runner.yml with: features: features/rails_features/ --tags @rails${{ matrix.rails-version }} ruby-version: ${{ matrix.ruby-version }} rails-version: ${{ matrix.rails-version }} + # temporary while we have some tests on Maze Runner v7 and some on v3 + gemfile: Gemfile-maze-runner-v7 + + rails-integrations-maze-runner: + strategy: + fail-fast: false + matrix: + ruby-version: ['2.7', '3.0'] + + uses: ./.github/workflows/run-maze-runner.yml + with: + features: features/rails_features/ --tags @rails_integrations + ruby-version: ${{ matrix.ruby-version }} + rails-version: _integrations plain-maze-runner: strategy: diff --git a/features/rails_features/active_job.feature b/features/rails_features/active_job.feature index baef4ee03..49669fe59 100644 --- a/features/rails_features/active_job.feature +++ b/features/rails_features/active_job.feature @@ -4,8 +4,8 @@ Feature: Active Job Scenario: A handled error will be delivered Given I start the rails service When I navigate to the route "/active_job/handled" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the event "severity" equals "warning" And the event "context" equals "NotifyJob@default" @@ -30,8 +30,8 @@ Scenario: A handled error will be delivered Scenario: An unhandled error will be delivered Given I start the rails service When I navigate to the route "/active_job/unhandled" on the rails app - And I wait to receive 2 requests - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive 2 errors + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "context" equals "UnhandledJob@default" @@ -51,8 +51,8 @@ Scenario: An unhandled error will be delivered And in Rails versions ">=" 5 the event "metaData.active_job.executions" equals 1 And in Rails versions ">=" 6 the event "metaData.active_job.timezone" equals "UTC" And in Rails versions ">=" 6 the event "metaData.active_job.enqueued_at" is a timestamp - When I discard the oldest request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + When I discard the oldest error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "context" equals "UnhandledJob@default" diff --git a/features/rails_features/active_record.feature b/features/rails_features/active_record.feature index e7931f7bc..bed21bd41 100644 --- a/features/rails_features/active_record.feature +++ b/features/rails_features/active_record.feature @@ -4,8 +4,8 @@ Feature: Active Record Scenario: An unhandled error in a transaction callback will be delivered Given I start the rails service When I navigate to the route "/unhandled/error_in_active_record_callback" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the exception "errorClass" equals "RuntimeError" And the exception "message" equals "Oh no!" @@ -19,8 +19,8 @@ Scenario: An unhandled error in a transaction callback will be delivered when ra Given I set environment variable "RAISE_IN_TRANSACTIONAL_CALLBACKS" to "false" And I start the rails service When I navigate to the route "/unhandled/error_in_active_record_callback" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the exception "errorClass" equals "RuntimeError" And the exception "message" equals "Oh no!" diff --git a/features/rails_features/api_key.feature b/features/rails_features/api_key.feature index c4e2d62e6..5cf629fde 100644 --- a/features/rails_features/api_key.feature +++ b/features/rails_features/api_key.feature @@ -4,12 +4,12 @@ Feature: API key Scenario: Setting api_key in environment variable works Given I start the rails service When I navigate to the route "/api_key/environment" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier @rails3 @rails4 @rails5 @rails6 @rails7 Scenario Outline: Changing api_key after initializer works Given I start the rails service When I navigate to the route "/api_key/changing?api_key=c35a2a72bd230ac0aa0f52715bbdc6ac" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier with the apiKey "c35a2a72bd230ac0aa0f52715bbdc6ac" diff --git a/features/rails_features/app_type.feature b/features/rails_features/app_type.feature index a17cef519..d08822c40 100644 --- a/features/rails_features/app_type.feature +++ b/features/rails_features/app_type.feature @@ -5,8 +5,8 @@ Scenario: Setting app_type in initializer works Given I set environment variable "BUGSNAG_APP_TYPE" to "custom_app_type" And I start the rails service When I navigate to the route "/app_type/initializer" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "RuntimeError" And the exception "message" starts with "handled string" And the event "metaData.request.url" ends with "/app_type/initializer" @@ -16,8 +16,8 @@ Scenario: Setting app_type in initializer works Scenario: Changing app_type after initializer works Given I start the rails service When I navigate to the route "/app_type/after?type=maze_after_initializer" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "RuntimeError" And the exception "message" starts with "handled string" And the event "metaData.request.url" ends with "/app_type/after?type=maze_after_initializer" @@ -27,8 +27,8 @@ Scenario: Changing app_type after initializer works Scenario: Should default to "rails" for handled errors Given I start the rails service When I navigate to the route "/app_type/handled" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.request.url" ends with "/app_type/handled" And the event "app.type" equals "rails" @@ -36,7 +36,7 @@ Scenario: Should default to "rails" for handled errors Scenario: Should default to "rails" for unhandled errors Given I start the rails service When I navigate to the route "/app_type/unhandled" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "metaData.request.url" ends with "/app_type/unhandled" And the event "app.type" equals "rails" diff --git a/features/rails_features/app_version.feature b/features/rails_features/app_version.feature index 224828d96..ac1c612b3 100644 --- a/features/rails_features/app_version.feature +++ b/features/rails_features/app_version.feature @@ -4,8 +4,8 @@ Feature: App version configuration Scenario: App_version is nil by default Given I start the rails service When I navigate to the route "/app_version/default" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "app.version" is null @rails3 @rails4 @rails5 @rails6 @rails7 @@ -13,14 +13,14 @@ Scenario: Setting app_version in initializer works Given I set environment variable "BUGSNAG_APP_VERSION" to "1.0.0" And I start the rails service When I navigate to the route "/app_version/initializer" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "app.version" equals "1.0.0" @rails3 @rails4 @rails5 @rails6 @rails7 Scenario: Setting app_version after initializer works Given I start the rails service When I navigate to the route "/app_version/after?version=1.1.0" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "app.version" equals "1.1.0" diff --git a/features/rails_features/auto_capture_sessions.feature b/features/rails_features/auto_capture_sessions.feature index 4cf5410de..01a4d2399 100644 --- a/features/rails_features/auto_capture_sessions.feature +++ b/features/rails_features/auto_capture_sessions.feature @@ -5,15 +5,14 @@ Scenario: Auto_capture_sessions defaults to true Given I set environment variable "USE_DEFAULT_AUTO_CAPTURE_SESSIONS" to "true" And I start the rails service When I navigate to the route "/session_tracking/initializer" on the rails app - And I wait to receive a request - Then the request is valid for the session reporting API version "1.0" for the "Ruby Bugsnag Notifier" notifier + And I wait to receive a session + Then the session is valid for the session reporting API version "1.0" for the "Ruby Bugsnag Notifier" notifier @rails3 @rails4 @rails5 @rails6 @rails7 Scenario: Auto_capture_sessions can be set to false in the initializer Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "false" And I start the rails service When I navigate to the route "/session_tracking/initializer" on the rails app - And I wait for 3 seconds Then I should receive no requests @rails3 @rails4 @rails5 @rails6 @rails7 @@ -21,14 +20,14 @@ Scenario: Manual sessions are still sent if Auto_capture_sessions is false Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "false" And I start the rails service When I navigate to the route "/session_tracking/manual" on the rails app - And I wait to receive a request - Then the request is valid for the session reporting API version "1.0" for the "Ruby Bugsnag Notifier" notifier + And I wait to receive a session + Then the session is valid for the session reporting API version "1.0" for the "Ruby Bugsnag Notifier" notifier @rails3 @rails4 @rails5 @rails6 @rails7 Scenario: 100 session calls results in 100 sessions Given I set environment variable "BUGSNAG_AUTO_CAPTURE_SESSIONS" to "false" And I start the rails service When I navigate to the route "/session_tracking/multi_sessions" on the rails app - And I wait to receive a request - Then the request is valid for the session reporting API version "1.0" for the "Ruby Bugsnag Notifier" notifier + And I wait to receive a session + Then the session is valid for the session reporting API version "1.0" for the "Ruby Bugsnag Notifier" notifier And the total sessionStarted count equals 100 diff --git a/features/rails_features/auto_notify.feature b/features/rails_features/auto_notify.feature index f56f8af84..8ff9ca2bf 100644 --- a/features/rails_features/auto_notify.feature +++ b/features/rails_features/auto_notify.feature @@ -5,7 +5,6 @@ Scenario: Auto_notify set to false in the initializer prevents unhandled error s Given I set environment variable "BUGSNAG_AUTO_NOTIFY" to "false" And I start the rails service When I navigate to the route "/auto_notify/unhandled" on the rails app - And I wait for 3 seconds Then I should receive no requests @rails3 @rails4 @rails5 @rails6 @rails7 @@ -13,8 +12,8 @@ Scenario: Auto_notify set to false in the initializer still sends handled errors Given I set environment variable "BUGSNAG_AUTO_NOTIFY" to "false" And I start the rails service When I navigate to the route "/auto_notify/handled" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is false And the exception "errorClass" equals "RuntimeError" And the exception "message" starts with "handled string" @@ -25,15 +24,14 @@ Scenario: Auto_notify set to false in the initializer still sends handled errors Scenario: Auto_notify set to false after the initializer prevents unhandled error sending Given I start the rails service When I navigate to the route "/auto_notify/unhandled_after" on the rails app - And I wait for 3 seconds Then I should receive no requests @rails3 @rails4 @rails5 @rails6 @rails7 Scenario: Auto_notify set to false after the initializer still sends handled errors Given I start the rails service When I navigate to the route "/auto_notify/handled_after" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "RuntimeError" And the exception "message" starts with "handled string" And the event "unhandled" is false diff --git a/features/rails_features/before_notify.feature b/features/rails_features/before_notify.feature index 8ab377000..99249b823 100644 --- a/features/rails_features/before_notify.feature +++ b/features/rails_features/before_notify.feature @@ -4,8 +4,8 @@ Feature: Before notify callbacks Scenario: Rails before_notify controller method works on handled errors Given I start the rails service When I navigate to the route "/before_notify/handled" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "RuntimeError" And the exception "message" starts with "handled string" And the event "unhandled" is false @@ -18,8 +18,8 @@ Scenario: Rails before_notify controller method works on handled errors Scenario: Rails before_notify controller method works on unhandled errors Given I start the rails service When I navigate to the route "/before_notify/unhandled" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the exception "errorClass" equals "NameError" And the exception "message" starts with "undefined local variable or method `generate_unhandled_error' for #?email=testtest@test.test" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "user.email" equals "testtest@test.test" And the event "user.name" equals "Warden User" And the event "user.first_name" equals "Warden" @@ -22,8 +22,8 @@ Scenario Outline: Devise user information is sent Given I start the rails service When I navigate to the route "/devise/create" on the rails app And I navigate to the route "/devise/" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "user.email" equals "test+test@test.test" And the event "user.name" equals "Devise User" And the event "user.first_name" equals "Devise" @@ -39,8 +39,8 @@ Scenario Outline: Clearance user information is sent Given I start the rails service When I navigate to the route "/clearance/create" on the rails app And I navigate to the route "/clearance/" on the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "user.email" equals "testtest@test.test" And the event "user.name" equals "Clearance User" And the event "user.first_name" equals "Clearance" diff --git a/features/steps/ruby_notifier_steps.rb b/features/steps/ruby_notifier_steps.rb index 7fe2e80ee..531d53915 100644 --- a/features/steps/ruby_notifier_steps.rb +++ b/features/steps/ruby_notifier_steps.rb @@ -18,8 +18,15 @@ end Then(/^the total sessionStarted count equals (\d+)$/) do |value| - session_counts = read_key_path(Server.current_request[:body], "sessionCounts") - total_count = session_counts.inject(0) { |count, session| count += session["sessionsStarted"] } + if using_maze_runner_v7? + body = Maze::Server.sessions.current[:body] + session_counts = Maze::Helper.read_key_path(body, "sessionCounts") + else + body = Server.current_request[:body] + session_counts = read_key_path(body, "sessionCounts") + end + + total_count = session_counts.sum { |session| session["sessionsStarted"] } assert_equal(value, total_count) end From 2ba55c7419ad4d264ad383d777207b270f7a32f5 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 17:17:17 +0000 Subject: [PATCH 29/61] Run Rails integrations tests with Maze Runner v7 --- .github/workflows/maze-runner.yml | 19 +--- features/rails_features/integrations.feature | 106 +++++++++---------- 2 files changed, 56 insertions(+), 69 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index cf2c9e9da..5a61ad633 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -135,17 +135,20 @@ jobs: # temporary while we have some tests on Maze Runner v7 and some on v3 gemfile: Gemfile-maze-runner-v7 - rails-6-7-maze-runner: + rails-6-7-integrations-maze-runner: strategy: fail-fast: false matrix: ruby-version: ['2.7', '3.0', '3.1'] - rails-version: ['6', '7'] + rails-version: ['6', '7', '_integrations'] include: - ruby-version: '2.5' rails-version: '6' - ruby-version: '2.6' rails-version: '6' + exclude: + - ruby-version: '3.1' + rails-version: '_integrations' uses: ./.github/workflows/run-maze-runner.yml with: @@ -155,18 +158,6 @@ jobs: # temporary while we have some tests on Maze Runner v7 and some on v3 gemfile: Gemfile-maze-runner-v7 - rails-integrations-maze-runner: - strategy: - fail-fast: false - matrix: - ruby-version: ['2.7', '3.0'] - - uses: ./.github/workflows/run-maze-runner.yml - with: - features: features/rails_features/ --tags @rails_integrations - ruby-version: ${{ matrix.ruby-version }} - rails-version: _integrations - plain-maze-runner: strategy: fail-fast: false diff --git a/features/rails_features/integrations.feature b/features/rails_features/integrations.feature index 7dc942ba1..bd8c9d40e 100644 --- a/features/rails_features/integrations.feature +++ b/features/rails_features/integrations.feature @@ -7,10 +7,10 @@ Background: @rails_integrations Scenario: Delayed job - When I run the "jobs:work" rake task in the rails app - And I run "User.new.delay.raise_the_roof" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + When I run "User.new.delay.raise_the_roof" with the rails runner + And I run the "jobs:workoff" rake task in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "context" equals "User#raise_the_roof" @@ -28,8 +28,8 @@ Scenario: Delayed job @rails_integrations Scenario: Mailman When I run "./run_mailman" in the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "context" is null And the event "severity" equals "error" @@ -41,10 +41,10 @@ Scenario: Mailman @rails_integrations Scenario: Que - When I run "bundle exec que ./config/environment.rb" in the rails app - And I run "QueJob.enqueue" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + When I run "QueJob.enqueue" with the rails runner + And I run "timeout 5 bundle exec que ./config/environment.rb" in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledExceptionMiddleware" @@ -57,8 +57,8 @@ Scenario: Que @rails_integrations Scenario: Rake When I run the "rake_task:raise" rake task in the rails app - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "severity" equals "error" And the event "severityReason.type" equals "unhandledExceptionMiddleware" @@ -71,10 +71,10 @@ Scenario: Rake @rails_integrations Scenario: Resque (no on_exit hooks) - When I run "bundle exec rake resque:work" in the rails app - And I run "Resque.enqueue(ResqueWorker, 123, %(abc), x: true, y: false)" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + When I run "Resque.enqueue(ResqueWorker, 123, %(abc), x: true, y: false)" with the rails runner + And I run "timeout --signal QUIT 5 bundle exec rake resque:work" in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "context" equals "ResqueWorker@crash" And the event "severity" equals "error" @@ -96,10 +96,10 @@ Scenario: Resque (no on_exit hooks) @rails_integrations Scenario: Resque (with on_exit hooks) Given I set environment variable "RUN_AT_EXIT_HOOKS" to "1" - When I run "bundle exec rake resque:work" in the rails app - And I run "Resque.enqueue(ResqueWorker, %(xyz), [7, 8, 9], a: 4, b: 5)" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + When I run "Resque.enqueue(ResqueWorker, %(xyz), [7, 8, 9], a: 4, b: 5)" with the rails runner + And I run "timeout --signal QUIT 5 bundle exec rake resque:work" in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "context" equals "ResqueWorker@crash" And the event "severity" equals "error" @@ -122,10 +122,10 @@ Scenario: Resque (with on_exit hooks) @rails_integrations Scenario: Sidekiq - When I run "bundle exec sidekiq" in the rails app - And I run "SidekiqWorker.perform_async" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + When I run "SidekiqWorker.perform_async" with the rails runner + And I run "timeout 5 bundle exec sidekiq" in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "context" equals "SidekiqWorker@default" And the event "severity" equals "error" @@ -139,11 +139,11 @@ Scenario: Sidekiq @rails_integrations Scenario: Using Sidekiq as the Active Job queue adapter for a job that raises - When I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "sidekiq" - And I run "bundle exec sidekiq" in the rails app - And I run "UnhandledJob.perform_later(1, yes: true)" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + Given I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "sidekiq" + When I run "UnhandledJob.perform_later(1, yes: true)" with the rails runner + And I run "timeout 5 bundle exec sidekiq" in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "context" equals "UnhandledJob@default" And the event "severity" equals "error" @@ -161,11 +161,11 @@ Scenario: Using Sidekiq as the Active Job queue adapter for a job that raises @rails_integrations Scenario: Using Resque as the Active Job queue adapter for a job that raises - When I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "resque" - And I run "bundle exec rake resque:work" in the rails app - And I run "UnhandledJob.perform_later(1, yes: true)" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + Given I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "resque" + When I run "UnhandledJob.perform_later(1, yes: true)" with the rails runner + And I run "timeout --signal QUIT 5 bundle exec rake resque:work" in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "context" equals "UnhandledJob@default" And the event "severity" equals "error" @@ -182,11 +182,11 @@ Scenario: Using Resque as the Active Job queue adapter for a job that raises @rails_integrations Scenario: Using Que as the Active Job queue adapter for a job that raises - When I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "que" - And I run "bundle exec que -q default ./config/environment.rb" in the rails app - And I run "UnhandledJob.perform_later(1, yes: true)" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + Given I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "que" + When I run "UnhandledJob.perform_later(1, yes: true)" with the rails runner + And I run "timeout 5 bundle exec que -q default ./config/environment.rb" in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "context" is null And the event "severity" equals "error" @@ -203,11 +203,11 @@ Scenario: Using Que as the Active Job queue adapter for a job that raises @rails_integrations Scenario: Using Delayed Job as the Active Job queue adapter for a job that raises - When I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "delayed_job" - And I run the "jobs:work" rake task in the rails app + Given I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "delayed_job" And I run "UnhandledJob.perform_later(1, yes: true)" with the rails runner - And I wait to receive a request - Then the request is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" + And I run the "jobs:workoff" rake task in the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event "unhandled" is true And the event "context" equals "UnhandledJob@default" And the event "severity" equals "error" @@ -224,32 +224,28 @@ Scenario: Using Delayed Job as the Active Job queue adapter for a job that raise @rails_integrations Scenario: Using Sidekiq as the Active Job queue adapter for a job that works - When I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "sidekiq" - And I run "bundle exec sidekiq" in the rails app + Given I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "sidekiq" And I run "WorkingJob.perform_later" with the rails runner - And I wait for 10 seconds + And I run "timeout 5 bundle exec sidekiq" in the rails app Then I should receive no requests @rails_integrations Scenario: Using Resque as the Active Job queue adapter for a job that works - When I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "resque" - And I run "bundle exec rake resque:work" in the rails app + Given I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "resque" And I run "WorkingJob.perform_later" with the rails runner - And I wait for 10 seconds + And I run "timeout --signal QUIT 5 bundle exec rake resque:work" in the rails app Then I should receive no requests @rails_integrations Scenario: Using Que as the Active Job queue adapter for a job that works - When I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "que" - And I run "bundle exec que -q default ./config/environment.rb" in the rails app + Given I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "que" And I run "WorkingJob.perform_later" with the rails runner - And I wait for 10 seconds + And I run "timeout 5 bundle exec que -q default ./config/environment.rb" in the rails app Then I should receive no requests @rails_integrations Scenario: Using Delayed Job as the Active Job queue adapter for a job that works - When I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "delayed_job" - And I run the "jobs:work" rake task in the rails app + Given I set environment variable "ACTIVE_JOB_QUEUE_ADAPTER" to "delayed_job" And I run "WorkingJob.perform_later" with the rails runner - And I wait for 10 seconds + And I run the "jobs:workoff" rake task in the rails app Then I should receive no requests From 928f5a32bed3224a103b9068b88fe717839a0902 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Fri, 11 Nov 2022 17:34:58 +0000 Subject: [PATCH 30/61] Remove Maze Runner v3 compatibility code --- .github/workflows/maze-runner.yml | 18 --------- .github/workflows/run-maze-runner.yml | 7 +--- Gemfile-maze-runner | 2 +- Gemfile-maze-runner-v7 | 3 -- features/steps/ruby_notifier_steps.rb | 51 ++++--------------------- features/support/env.rb | 55 ++++++++------------------- 6 files changed, 25 insertions(+), 111 deletions(-) delete mode 100644 Gemfile-maze-runner-v7 diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 5a61ad633..803c529fe 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -13,8 +13,6 @@ jobs: with: features: features/rake.feature ruby-version: ${{ matrix.ruby-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 mailman-maze-runner: strategy: @@ -26,8 +24,6 @@ jobs: with: features: features/mailman.feature ruby-version: ${{ matrix.ruby-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 rack-maze-runner: strategy: @@ -50,8 +46,6 @@ jobs: features: features/rack.feature ruby-version: ${{ matrix.ruby-version }} rack-version: ${{ matrix.rack-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 que-maze-runner: strategy: @@ -82,8 +76,6 @@ jobs: features: features/que.feature ruby-version: ${{ matrix.ruby-version }} que-version: ${{ matrix.que-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 sidekiq-maze-runner: strategy: @@ -97,8 +89,6 @@ jobs: features: features/sidekiq.feature ruby-version: ${{ matrix.ruby-version }} sidekiq-version: ${{ matrix.sidekiq-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 delayed-job-maze-runner: strategy: @@ -110,8 +100,6 @@ jobs: with: features: features/delayed_job.feature ruby-version: ${{ matrix.ruby-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 rails-3-4-5-maze-runner: strategy: @@ -132,8 +120,6 @@ jobs: features: features/rails_features/ --tags @rails${{ matrix.rails-version }} ruby-version: ${{ matrix.ruby-version }} rails-version: ${{ matrix.rails-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 rails-6-7-integrations-maze-runner: strategy: @@ -155,8 +141,6 @@ jobs: features: features/rails_features/ --tags @rails${{ matrix.rails-version }} ruby-version: ${{ matrix.ruby-version }} rails-version: ${{ matrix.rails-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 plain-maze-runner: strategy: @@ -168,5 +152,3 @@ jobs: with: features: features/plain_features/ ruby-version: ${{ matrix.ruby-version }} - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: Gemfile-maze-runner-v7 diff --git a/.github/workflows/run-maze-runner.yml b/.github/workflows/run-maze-runner.yml index 4158c19e5..9a64c5030 100644 --- a/.github/workflows/run-maze-runner.yml +++ b/.github/workflows/run-maze-runner.yml @@ -21,18 +21,13 @@ on: sidekiq-version: required: false type: string - # temporary while we have some tests on Maze Runner v7 and some on v3 - gemfile: - required: false - type: string - default: Gemfile-maze-runner jobs: maze-runner: runs-on: ubuntu-latest env: - BUNDLE_GEMFILE: ${{ inputs.gemfile }} + BUNDLE_GEMFILE: Gemfile-maze-runner steps: - uses: actions/checkout@v3 diff --git a/Gemfile-maze-runner b/Gemfile-maze-runner index 5ee44f80d..05bb7a68b 100644 --- a/Gemfile-maze-runner +++ b/Gemfile-maze-runner @@ -1,3 +1,3 @@ source "https://rubygems.org" -gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v3.0.2' +gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.6.0' diff --git a/Gemfile-maze-runner-v7 b/Gemfile-maze-runner-v7 deleted file mode 100644 index f3b8a82d0..000000000 --- a/Gemfile-maze-runner-v7 +++ /dev/null @@ -1,3 +0,0 @@ -source "https://rubygems.org" - -gem 'bugsnag-maze-runner', git: 'https://github.com/bugsnag/maze-runner', tag: 'v7.5.1' diff --git a/features/steps/ruby_notifier_steps.rb b/features/steps/ruby_notifier_steps.rb index 531d53915..6c2640790 100644 --- a/features/steps/ruby_notifier_steps.rb +++ b/features/steps/ruby_notifier_steps.rb @@ -2,13 +2,8 @@ require "net/http" Then(/^the "(.+)" of the top non-bugsnag stackframe equals (\d+|".+")$/) do |element, value| - if using_maze_runner_v7? - body = Maze::Server.errors.current[:body] - stacktrace = Maze::Helper.read_key_path(body, 'events.0.exceptions.0.stacktrace') - else - body = Server.current_request[:body] - stacktrace = read_key_path(body, 'events.0.exceptions.0.stacktrace') - end + body = Maze::Server.errors.current[:body] + stacktrace = Maze::Helper.read_key_path(body, 'events.0.exceptions.0.stacktrace') frame_index = stacktrace.find_index { |frame| ! /.*lib\/bugsnag.*\.rb/.match(frame["file"]) } @@ -18,42 +13,13 @@ end Then(/^the total sessionStarted count equals (\d+)$/) do |value| - if using_maze_runner_v7? - body = Maze::Server.sessions.current[:body] - session_counts = Maze::Helper.read_key_path(body, "sessionCounts") - else - body = Server.current_request[:body] - session_counts = read_key_path(body, "sessionCounts") - end + body = Maze::Server.sessions.current[:body] + session_counts = Maze::Helper.read_key_path(body, "sessionCounts") total_count = session_counts.sum { |session| session["sessionsStarted"] } assert_equal(value, total_count) end -# Due to an ongoing discussion on whether the `payload_version` needs to be present within the headers -# and body of the payload, this step is a local replacement for the similar step present in the main -# maze-runner library. Once the discussion is resolved this step should be removed and replaced in scenarios -# with the main library version. -Then("the request is valid for the error reporting API version {string} for the {string}") do |payload_version, notifier_name| - steps %Q{ - Then the "Bugsnag-Api-Key" header equals "#{$api_key}" - And the payload field "apiKey" equals "#{$api_key}" - And the "Bugsnag-Payload-Version" header equals "#{payload_version}" - And the "Content-Type" header equals "application/json" - And the "Bugsnag-Sent-At" header is a timestamp - - And the payload field "notifier.name" equals "#{notifier_name}" - And the payload field "notifier.url" is not null - And the payload field "notifier.version" is not null - And the payload field "events" is a non-empty array - - And each element in payload field "events" has "severity" - And each element in payload field "events" has "severityReason.type" - And each element in payload field "events" has "unhandled" - And each element in payload field "events" has "exceptions" - } -end - Given("I start the rails service") do steps %Q{ When I start the service "#{RAILS_FIXTURE.docker_service}" @@ -187,11 +153,10 @@ Given("I configure the BUGSNAG_PROXY environment variables") do host = running_in_docker? ? "maze-runner" : current_ip - port = using_maze_runner_v7? ? Maze.config.port : MOCK_API_PORT steps %Q{ When I set environment variable "BUGSNAG_PROXY_HOST" to "#{host}" - And I set environment variable "BUGSNAG_PROXY_PORT" to "#{port}" + And I set environment variable "BUGSNAG_PROXY_PORT" to "#{Maze.config.port}" And I set environment variable "BUGSNAG_PROXY_USER" to "tester" And I set environment variable "BUGSNAG_PROXY_PASSWORD" to "testpass" } @@ -199,18 +164,16 @@ Given("I configure the http_proxy environment variable") do host = running_in_docker? ? "maze-runner" : current_ip - port = using_maze_runner_v7? ? Maze.config.port : MOCK_API_PORT steps %Q{ - Given I set environment variable "http_proxy" to "http://tester:testpass@#{host}:#{port}" + Given I set environment variable "http_proxy" to "http://tester:testpass@#{host}:#{Maze.config.port}" } end Given("I configure the https_proxy environment variable") do host = running_in_docker? ? "maze-runner" : current_ip - port = using_maze_runner_v7? ? Maze.config.port : MOCK_API_PORT steps %Q{ - Given I set environment variable "https_proxy" to "https://tester:testpass@#{host}:#{port}" + Given I set environment variable "https_proxy" to "https://tester:testpass@#{host}:#{Maze.config.port}" } end diff --git a/features/support/env.rb b/features/support/env.rb index 3fda13727..56a0ef384 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -38,49 +38,26 @@ def current_ip ip_list.captures.first end -def using_maze_runner_v7? - defined?(Maze) -end - -if using_maze_runner_v7? - Maze.hooks.before_all do - install_fixture_gems - - # log to console, not a file - Maze.config.file_log = false - Maze.config.log_requests = true - - # don't wait so long for requests/not to receive requests - Maze.config.receive_requests_wait = 10 - Maze.config.receive_no_requests_wait = 10 - - # bugsnag-ruby doesn't need to send the integrity header - Maze.config.enforce_bugsnag_integrity = false - end +Maze.hooks.before_all do + install_fixture_gems - Maze.hooks.before do - # Maze::Docker.compose_project_name = "#{rand.to_s}:#{Time.new.strftime("%s")}" + # log to console, not a file + Maze.config.file_log = false + Maze.config.log_requests = true - Maze::Runner.environment["BUGSNAG_API_KEY"] = $api_key + # don't wait so long for requests/not to receive requests + Maze.config.receive_requests_wait = 10 + Maze.config.receive_no_requests_wait = 10 - host = running_in_docker? ? "maze-runner" : current_ip - - Maze::Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{host}:#{Maze.config.port}/notify" - Maze::Runner.environment["BUGSNAG_SESSION_ENDPOINT"] = "http://#{host}:#{Maze.config.port}/sessions" - end -else - AfterConfiguration do |config| - install_fixture_gems - end + # bugsnag-ruby doesn't need to send the integrity header + Maze.config.enforce_bugsnag_integrity = false +end - Before do - Docker.compose_project_name = "#{rand.to_s}:#{Time.new.strftime("%s")}" - Runner.environment.clear - Runner.environment["BUGSNAG_API_KEY"] = $api_key +Maze.hooks.before do + Maze::Runner.environment["BUGSNAG_API_KEY"] = $api_key - host = running_in_docker? ? "maze-runner" : current_ip + host = running_in_docker? ? "maze-runner" : current_ip - Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{host}:#{MOCK_API_PORT}" - Runner.environment["BUGSNAG_SESSION_ENDPOINT"] = "http://#{host}:#{MOCK_API_PORT}" - end + Maze::Runner.environment["BUGSNAG_ENDPOINT"] = "http://#{host}:#{Maze.config.port}/notify" + Maze::Runner.environment["BUGSNAG_SESSION_ENDPOINT"] = "http://#{host}:#{Maze.config.port}/sessions" end From 3d631fdf4f4a2b8547627f5b28c603d031684be8 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 14 Nov 2022 15:20:02 +0000 Subject: [PATCH 31/61] Use the default Maze Runner request timeouts on CI --- features/support/env.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/features/support/env.rb b/features/support/env.rb index 56a0ef384..52e7b5158 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -45,9 +45,11 @@ def current_ip Maze.config.file_log = false Maze.config.log_requests = true - # don't wait so long for requests/not to receive requests - Maze.config.receive_requests_wait = 10 - Maze.config.receive_no_requests_wait = 10 + # don't wait so long for requests/not to receive requests locally + unless ENV["CI"] + Maze.config.receive_requests_wait = 10 + Maze.config.receive_no_requests_wait = 10 + end # bugsnag-ruby doesn't need to send the integrity header Maze.config.enforce_bugsnag_integrity = false From fbf028a6eb7745291cfff28ac77e6fd31c1e2aae Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Tue, 15 Nov 2022 12:58:06 +0000 Subject: [PATCH 32/61] Reduce Maze Runner matrices to highest & lowest --- .github/workflows/maze-runner.yml | 53 +++++++++++++------------------ 1 file changed, 22 insertions(+), 31 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 803c529fe..12222b2d5 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['1.9', '2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] + ruby-version: ['1.9', '3.1'] uses: ./.github/workflows/run-maze-runner.yml with: @@ -18,7 +18,7 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0'] + ruby-version: ['2.0', '3.0'] uses: ./.github/workflows/run-maze-runner.yml with: @@ -29,16 +29,14 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['1.9', '2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] - rack-version: ['1', '2'] - exclude: - - ruby-version: '3.1' - rack-version: '1' + include: - ruby-version: '1.9' + rack-version: '1' + - ruby-version: '3.0' + rack-version: '1' + - ruby-version: '2.2' rack-version: '2' - - ruby-version: '2.0' - rack-version: '2' - - ruby-version: '2.1' + - ruby-version: '3.1' rack-version: '2' uses: ./.github/workflows/run-maze-runner.yml @@ -51,24 +49,14 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['2.0', '2.1', '2.2', '2.3', '2.4', '2.5', '2.6', '2.7', '3.0', '3.1'] - que-version: ['0.14', '1'] - exclude: + include: + - ruby-version: '2.0' + que-version: '0.14' - ruby-version: '3.1' que-version: '0.14' - - ruby-version: '2.0' - que-version: '1' - - ruby-version: '2.1' - que-version: '1' - - ruby-version: '2.2' - que-version: '1' - - ruby-version: '2.3' - que-version: '1' - - ruby-version: '2.4' - que-version: '1' - - ruby-version: '3.0' + - ruby-version: '2.5' que-version: '1' - - ruby-version: '3.1' + - ruby-version: '2.7' que-version: '1' uses: ./.github/workflows/run-maze-runner.yml @@ -105,15 +93,18 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['2.2', '2.3', '2.4', '2.5'] + ruby-version: ['2.2', '2.5'] rails-version: ['3', '4', '5'] include: - ruby-version: '2.0' rails-version: '3' - - ruby-version: '2.1' - rails-version: '3' - ruby-version: '2.6' rails-version: '5' + exclude: + - ruby-version: '2.2' + rails-version: '3' + - ruby-version: '2.5' + rails-version: '5' uses: ./.github/workflows/run-maze-runner.yml with: @@ -125,14 +116,14 @@ jobs: strategy: fail-fast: false matrix: - ruby-version: ['2.7', '3.0', '3.1'] + ruby-version: ['2.7', '3.1'] rails-version: ['6', '7', '_integrations'] include: - ruby-version: '2.5' rails-version: '6' - - ruby-version: '2.6' - rails-version: '6' exclude: + - ruby-version: '2.7' + rails-version: '6' - ruby-version: '3.1' rails-version: '_integrations' From 36cb092012ac0ba6e73d1b8134df35bb6c55d577 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 16 Nov 2022 10:02:35 +0000 Subject: [PATCH 33/61] Simplify Rack fixture setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously we had two identical apps for running Rack 1 vs Rack 2 — the only difference was the major version in the Gemfile This commit switches to the same setup we use for Sidekiq, where there is one fixture that dynamically requires a certain version of Rack in its Gemfile --- features/fixtures/docker-compose.yml | 19 ++--------- .../fixtures/{rack1 => rack}/.dockerignore | 0 features/fixtures/{rack1 => rack}/Dockerfile | 4 +++ features/fixtures/{rack1 => rack}/app/Gemfile | 2 +- features/fixtures/{rack1 => rack}/app/app.rb | 0 features/fixtures/rack2/.dockerignore | 3 -- features/fixtures/rack2/Dockerfile | 13 ------- features/fixtures/rack2/app/Gemfile | 5 --- features/fixtures/rack2/app/app.rb | 34 ------------------- features/lib/fixture.rb | 4 ++- features/support/env.rb | 2 +- 11 files changed, 12 insertions(+), 74 deletions(-) rename features/fixtures/{rack1 => rack}/.dockerignore (100%) rename features/fixtures/{rack1 => rack}/Dockerfile (82%) rename features/fixtures/{rack1 => rack}/app/Gemfile (78%) rename features/fixtures/{rack1 => rack}/app/app.rb (100%) delete mode 100644 features/fixtures/rack2/.dockerignore delete mode 100644 features/fixtures/rack2/Dockerfile delete mode 100644 features/fixtures/rack2/app/Gemfile delete mode 100644 features/fixtures/rack2/app/app.rb diff --git a/features/fixtures/docker-compose.yml b/features/fixtures/docker-compose.yml index e39120361..ab99ac022 100644 --- a/features/fixtures/docker-compose.yml +++ b/features/fixtures/docker-compose.yml @@ -78,11 +78,12 @@ services: - CALLBACK_INITIATOR restart: "no" - rack1: + rack: build: - context: rack1 + context: rack args: - RUBY_TEST_VERSION + - RACK_VERSION environment: - BUGSNAG_API_KEY - BUGSNAG_ENDPOINT @@ -92,20 +93,6 @@ services: - target: 3000 published: 7251 - rack2: - build: - context: rack2 - args: - - RUBY_TEST_VERSION - environment: - - BUGSNAG_API_KEY - - BUGSNAG_ENDPOINT - - BUGSNAG_METADATA_FILTERS - restart: "no" - ports: - - target: 3000 - published: 7252 - rails3: build: context: rails3 diff --git a/features/fixtures/rack1/.dockerignore b/features/fixtures/rack/.dockerignore similarity index 100% rename from features/fixtures/rack1/.dockerignore rename to features/fixtures/rack/.dockerignore diff --git a/features/fixtures/rack1/Dockerfile b/features/fixtures/rack/Dockerfile similarity index 82% rename from features/fixtures/rack1/Dockerfile rename to features/fixtures/rack/Dockerfile index a485c56c1..5083cde99 100644 --- a/features/fixtures/rack1/Dockerfile +++ b/features/fixtures/rack/Dockerfile @@ -6,6 +6,10 @@ COPY temp-bugsnag-lib ./ WORKDIR /usr/src/app COPY app/Gemfile /usr/src/app/ + +ARG RACK_VERSION +ENV RACK_VERSION $RACK_VERSION + RUN bundle install COPY app/ /usr/src/app diff --git a/features/fixtures/rack1/app/Gemfile b/features/fixtures/rack/app/Gemfile similarity index 78% rename from features/fixtures/rack1/app/Gemfile rename to features/fixtures/rack/app/Gemfile index 58bbd2883..fac1ee7e9 100644 --- a/features/fixtures/rack1/app/Gemfile +++ b/features/fixtures/rack/app/Gemfile @@ -1,5 +1,5 @@ source 'https://rubygems.org' gem 'bugsnag', path: '/bugsnag' -gem 'rack', '~> 1' +gem 'rack', "~> #{ENV['RACK_VERSION']}" gem 'webrick' if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('3.0.0') diff --git a/features/fixtures/rack1/app/app.rb b/features/fixtures/rack/app/app.rb similarity index 100% rename from features/fixtures/rack1/app/app.rb rename to features/fixtures/rack/app/app.rb diff --git a/features/fixtures/rack2/.dockerignore b/features/fixtures/rack2/.dockerignore deleted file mode 100644 index 87a58ba13..000000000 --- a/features/fixtures/rack2/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -# Ignore the lock file so that we always use an up to date version. This won't -# exist unless the fixtures are run manually anyway. -app/Gemfile.lock diff --git a/features/fixtures/rack2/Dockerfile b/features/fixtures/rack2/Dockerfile deleted file mode 100644 index a485c56c1..000000000 --- a/features/fixtures/rack2/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -ARG RUBY_TEST_VERSION -FROM ruby:$RUBY_TEST_VERSION - -WORKDIR /bugsnag -COPY temp-bugsnag-lib ./ - -WORKDIR /usr/src/app -COPY app/Gemfile /usr/src/app/ -RUN bundle install - -COPY app/ /usr/src/app - -CMD ["bundle", "exec", "ruby", "app.rb"] diff --git a/features/fixtures/rack2/app/Gemfile b/features/fixtures/rack2/app/Gemfile deleted file mode 100644 index 3d2908859..000000000 --- a/features/fixtures/rack2/app/Gemfile +++ /dev/null @@ -1,5 +0,0 @@ -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/rack2/app/app.rb b/features/fixtures/rack2/app/app.rb deleted file mode 100644 index 41d0f2eca..000000000 --- a/features/fixtures/rack2/app/app.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'bugsnag' -require 'rack' -require 'json' - -Bugsnag.configure do |config| - config.api_key = ENV['BUGSNAG_API_KEY'] - config.endpoint = ENV['BUGSNAG_ENDPOINT'] - - if ENV.key?('BUGSNAG_METADATA_FILTERS') - config.meta_data_filters = JSON.parse(ENV['BUGSNAG_METADATA_FILTERS']) - end -end - -class BugsnagTests - def call(env) - req = Rack::Request.new(env) - - case req.env['REQUEST_PATH'] - when '/unhandled' - raise 'Unhandled error' - when '/handled' - begin - raise 'Handled error' - rescue StandardError => e - Bugsnag.notify(e) - end - end - - res = Rack::Response.new - res.finish - end -end - -Rack::Server.start(app: Bugsnag::Rack.new(BugsnagTests.new), Host: '0.0.0.0', Port: 3000) diff --git a/features/lib/fixture.rb b/features/lib/fixture.rb index 10acd4453..645b9ec8f 100644 --- a/features/lib/fixture.rb +++ b/features/lib/fixture.rb @@ -1,5 +1,5 @@ class Fixture - def initialize(name, version) + def initialize(name, version = nil) @name = name @version = version end @@ -21,6 +21,8 @@ def port # bind to port 3000 even when running outside of docker if running_in_docker? || @version == "_integrations" "3000" + elsif @name == "rack" + "7251" else "725#{@version}" end diff --git a/features/support/env.rb b/features/support/env.rb index 52e7b5158..9dee7b3a1 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -2,7 +2,7 @@ require 'fileutils' require_relative "./../lib/fixture" -RACK_FIXTURE = Fixture.new("rack", ENV["RACK_VERSION"]) +RACK_FIXTURE = Fixture.new("rack") RAILS_FIXTURE = Fixture.new("rails", ENV["RAILS_VERSION"]) def running_in_docker? From 6fa511358a7bec1ec122b63a2c1f77c6cf342058 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 16 Nov 2022 12:20:19 +0000 Subject: [PATCH 34/61] Run tests against Rack 3 --- .github/workflows/maze-runner.yml | 4 ++++ features/fixtures/rack/app/Gemfile | 6 ++++++ features/rack.feature | 6 ------ 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/maze-runner.yml b/.github/workflows/maze-runner.yml index 12222b2d5..979e2d265 100644 --- a/.github/workflows/maze-runner.yml +++ b/.github/workflows/maze-runner.yml @@ -38,6 +38,10 @@ jobs: rack-version: '2' - ruby-version: '3.1' rack-version: '2' + - ruby-version: '2.4' + rack-version: '3' + - ruby-version: '3.1' + rack-version: '3' uses: ./.github/workflows/run-maze-runner.yml with: diff --git a/features/fixtures/rack/app/Gemfile b/features/fixtures/rack/app/Gemfile index fac1ee7e9..1f1fa5fa3 100644 --- a/features/fixtures/rack/app/Gemfile +++ b/features/fixtures/rack/app/Gemfile @@ -3,3 +3,9 @@ source 'https://rubygems.org' gem 'bugsnag', path: '/bugsnag' gem 'rack', "~> #{ENV['RACK_VERSION']}" gem 'webrick' if Gem::Version.new(RUBY_VERSION.dup) >= Gem::Version.new('3.0.0') + +# Some functionality provided by Rack was moved to the 'rackup' gem in Rack v3 +# Specifically the test app uses Rack::Server, which is now Rackup::Server +if ENV['RACK_VERSION'] == '3' + gem 'rackup', '~> 0.2.3' +end diff --git a/features/rack.feature b/features/rack.feature index 5bb05027d..e38e1090a 100644 --- a/features/rack.feature +++ b/features/rack.feature @@ -16,7 +16,6 @@ Scenario: An unhandled RuntimeError sends a report And the event "metaData.request.cookies" is null And the event "metaData.request.headers.Host" is not null And the event "metaData.request.headers.User-Agent" is not null - And the event "metaData.request.headers.Version" is not null And the event "metaData.request.httpMethod" equals "GET" And the event "metaData.request.httpVersion" matches "^HTTP/\d\.\d$" And the event "metaData.request.params.a" equals "123" @@ -39,7 +38,6 @@ Scenario: A handled RuntimeError sends a report And the event "metaData.request.cookies" is null And the event "metaData.request.headers.Host" is not null And the event "metaData.request.headers.User-Agent" is not null - And the event "metaData.request.headers.Version" is not null And the event "metaData.request.httpMethod" equals "GET" And the event "metaData.request.httpVersion" matches "^HTTP/\d\.\d$" And the event "metaData.request.params.a" equals "123" @@ -62,7 +60,6 @@ Scenario: A POST request with form data sends a report with the parsed request b And the event "metaData.request.cookies" is null And the event "metaData.request.headers.Host" is not null And the event "metaData.request.headers.User-Agent" is not null - And the event "metaData.request.headers.Version" is not null And the event "metaData.request.httpMethod" equals "POST" And the event "metaData.request.httpVersion" matches "^HTTP/\d\.\d$" And the event "metaData.request.params.a" equals "123" @@ -85,7 +82,6 @@ Scenario: A POST request with JSON sends a report with the parsed request body a And the event "metaData.request.cookies" is null And the event "metaData.request.headers.Host" is not null And the event "metaData.request.headers.User-Agent" is not null - And the event "metaData.request.headers.Version" is not null And the event "metaData.request.httpMethod" equals "POST" And the event "metaData.request.httpVersion" matches "^HTTP/\d\.\d$" And the event "metaData.request.params.a" equals "123" @@ -106,7 +102,6 @@ Scenario: A request with cookies will be filtered out by default And the event "metaData.request.clientIp" is not null And the event "metaData.request.headers.Host" is not null And the event "metaData.request.headers.User-Agent" is not null - And the event "metaData.request.headers.Version" is not null And the event "metaData.request.httpMethod" equals "GET" And the event "metaData.request.httpVersion" matches "^HTTP/\d\.\d$" And the event "metaData.request.params.a" equals "123" @@ -130,7 +125,6 @@ Scenario: A request with cookies and no matching filter will set cookies in meta And the event "metaData.request.clientIp" is not null And the event "metaData.request.headers.Host" is not null And the event "metaData.request.headers.User-Agent" is not null - And the event "metaData.request.headers.Version" is not null And the event "metaData.request.httpMethod" equals "GET" And the event "metaData.request.httpVersion" matches "^HTTP/\d\.\d$" And the event "metaData.request.params.a" equals "123" From db71d78134716e71640cd315918c1076e0ac16a9 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 30 Nov 2022 08:44:15 +0000 Subject: [PATCH 35/61] Bump Nokogiri version --- spec/fixtures/apps/rails-initializer-config/Gemfile | 2 +- spec/fixtures/apps/rails-invalid-initializer-config/Gemfile | 2 +- spec/fixtures/apps/rails-no-config/Gemfile | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/fixtures/apps/rails-initializer-config/Gemfile b/spec/fixtures/apps/rails-initializer-config/Gemfile index a7a2ca6c3..22958a757 100644 --- a/spec/fixtures/apps/rails-initializer-config/Gemfile +++ b/spec/fixtures/apps/rails-initializer-config/Gemfile @@ -5,5 +5,5 @@ 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.14.0' -gem 'nokogiri', '1.6.8' +gem 'nokogiri', ruby_version < Gem::Version.new('2.6') ? '1.6.8' : '~> 1.13.9' gem 'bugsnag', path: '../../../..' diff --git a/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile b/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile index a7a2ca6c3..22958a757 100644 --- a/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile +++ b/spec/fixtures/apps/rails-invalid-initializer-config/Gemfile @@ -5,5 +5,5 @@ 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.14.0' -gem 'nokogiri', '1.6.8' +gem 'nokogiri', ruby_version < Gem::Version.new('2.6') ? '1.6.8' : '~> 1.13.9' gem 'bugsnag', path: '../../../..' diff --git a/spec/fixtures/apps/rails-no-config/Gemfile b/spec/fixtures/apps/rails-no-config/Gemfile index a7a2ca6c3..22958a757 100644 --- a/spec/fixtures/apps/rails-no-config/Gemfile +++ b/spec/fixtures/apps/rails-no-config/Gemfile @@ -5,5 +5,5 @@ 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.14.0' -gem 'nokogiri', '1.6.8' +gem 'nokogiri', ruby_version < Gem::Version.new('2.6') ? '1.6.8' : '~> 1.13.9' gem 'bugsnag', path: '../../../..' From fd37682ff9500926d209ae2e37b95b24efd97a7d Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 14 Nov 2022 14:24:16 +0000 Subject: [PATCH 36/61] Add FeatureFlag class --- lib/bugsnag.rb | 2 + lib/bugsnag/feature_flag.rb | 64 +++++++++++++++++++ spec/feature_flag_spec.rb | 121 ++++++++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 lib/bugsnag/feature_flag.rb create mode 100644 spec/feature_flag_spec.rb diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index 4d94ce8c8..f1c76722a 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -14,6 +14,8 @@ require "bugsnag/delivery/synchronous" require "bugsnag/delivery/thread_queue" +require "bugsnag/feature_flag" + # Rack is not bundled with the other integrations # as it doesn't auto-configure when loaded require "bugsnag/integrations/rack" diff --git a/lib/bugsnag/feature_flag.rb b/lib/bugsnag/feature_flag.rb new file mode 100644 index 000000000..0cfb5ba36 --- /dev/null +++ b/lib/bugsnag/feature_flag.rb @@ -0,0 +1,64 @@ +module Bugsnag + class FeatureFlag + # Get the name of this feature flag + # + # @return [String] + attr_reader :name + + # Get the variant of this feature flag + # + # @return [String, nil] + attr_reader :variant + + # @param name [String] The name of this feature flags + # @param variant [String, nil] An optional variant for this flag + def initialize(name, variant = nil) + @name = name + @variant = coerce_variant(variant) + end + + def ==(other) + self.class == other.class && @name == other.name && @variant == other.variant + end + + def hash + [@name, @variant].hash + end + + # Convert this flag to a hash + # + # @example With no variant + # { "featureFlag" => "name" } + # + # @example With a variant + # { "featureFlag" => "name", "variant" => "variant" } + # + # @return [Hash{String => String}] + def to_h + if @variant.nil? + { "featureFlag" => @name } + else + { "featureFlag" => @name, "variant" => @variant } + end + end + + private + + # Coerce this variant into a valid value (String or nil) + # + # If the variant is not already a string or nil, we use #to_s to coerce it. + # If #to_s raises, the variant will be set to nil + # + # @param variant [Object] + # @return [String, nil] + def coerce_variant(variant) + if variant.nil? || variant.is_a?(String) + variant + else + variant.to_s + end + rescue StandardError + nil + end + end +end diff --git a/spec/feature_flag_spec.rb b/spec/feature_flag_spec.rb new file mode 100644 index 000000000..370ffbbce --- /dev/null +++ b/spec/feature_flag_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +describe Bugsnag::FeatureFlag do + it "has a name" do + flag = Bugsnag::FeatureFlag.new("abc") + + expect(flag.name).to eq("abc") + expect(flag.variant).to be_nil + end + + it "has an optional variant" do + flag = Bugsnag::FeatureFlag.new("abc", "xyz") + + expect(flag.name).to eq("abc") + expect(flag.variant).to eq("xyz") + end + + [ + [123, "123"], + [true, "true"], + [false, "false"], + [[1, 2, 3], "[1, 2, 3]"], + [{ a: 1, b: 2 }, "{:a=>1, :b=>2}"], + ].each do |variant, expected| + it "converts the variant to a string if given '#{variant.class}'" do + flag = Bugsnag::FeatureFlag.new("abc", variant) + + expect(flag.name).to eq("abc") + expect(flag.variant).to eq(expected) + end + end + + it "sets variant to 'nil' if variant cannot be converted to a string" do + class StringRaiser + def to_s + raise 'Oh no you do not!' + end + end + + flag = Bugsnag::FeatureFlag.new("abc", StringRaiser.new) + + expect(flag.name).to eq("abc") + expect(flag.variant).to be_nil + end + + + it "is immutable" do + flag = Bugsnag::FeatureFlag.new("abc", "xyz") + + expect(flag).not_to respond_to(:name=) + expect(flag).not_to respond_to(:variant=) + end + + describe "#to_h" do + it "converts the flag to a hash when no variant is given" do + flag = Bugsnag::FeatureFlag.new("xyz") + + expect(flag.to_h).to eq({ "featureFlag" => "xyz" }) + end + + it "converts the flag to a hash when variant is given" do + flag = Bugsnag::FeatureFlag.new("xyz", "1234") + + expect(flag.to_h).to eq({ "featureFlag" => "xyz", "variant" => "1234" }) + end + end + + describe "#==" do + it "is equal to other instances with the same name when neither have a variant" do + flag1 = Bugsnag::FeatureFlag.new("xyz") + flag2 = Bugsnag::FeatureFlag.new("xyz") + + expect(flag1).to eq(flag2) + expect(flag2).to eq(flag1) + + expect(flag1).not_to be(flag2) + end + + it "is equal to other instances with the same name and variant" do + flag1 = Bugsnag::FeatureFlag.new("xyz", "1234") + flag2 = Bugsnag::FeatureFlag.new("xyz", "1234") + + expect(flag1).to eq(flag2) + expect(flag2).to eq(flag1) + + expect(flag1).not_to be(flag2) + end + + it "is not equal to other instances with the same name but a different variant" do + flag1 = Bugsnag::FeatureFlag.new("xyz", "1234") + flag2 = Bugsnag::FeatureFlag.new("xyz", "9876") + + expect(flag1).not_to eq(flag2) + expect(flag2).not_to eq(flag1) + end + + it "is not equal to other instances with the same name when only one has a variant" do + flag1 = Bugsnag::FeatureFlag.new("xyz") + flag2 = Bugsnag::FeatureFlag.new("xyz", "9876") + + expect(flag1).not_to eq(flag2) + expect(flag2).not_to eq(flag1) + end + + it "is not equal to other instances with a different name but the same variant" do + flag1 = Bugsnag::FeatureFlag.new("xyz", "1234") + flag2 = Bugsnag::FeatureFlag.new("abc", "1234") + + expect(flag1).not_to eq(flag2) + expect(flag2).not_to eq(flag1) + end + + it "is not equal to other instances with a different name and variant" do + flag1 = Bugsnag::FeatureFlag.new("xyz", "1234") + flag2 = Bugsnag::FeatureFlag.new("abc", "9876") + + expect(flag1).not_to eq(flag2) + expect(flag2).not_to eq(flag1) + end + end +end From e4ca5e0e4f963776987eae18b49c6c09a77687da Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 14 Nov 2022 14:24:32 +0000 Subject: [PATCH 37/61] Add FeatureFlagDelegate class --- lib/bugsnag.rb | 1 + lib/bugsnag/utility/feature_flag_delegate.rb | 77 ++++++ spec/utility/feature_flag_delegate_spec.rb | 254 +++++++++++++++++++ 3 files changed, 332 insertions(+) create mode 100644 lib/bugsnag/utility/feature_flag_delegate.rb create mode 100644 spec/utility/feature_flag_delegate_spec.rb diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index f1c76722a..3738b3f3c 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -38,6 +38,7 @@ require "bugsnag/utility/duplicator" require "bugsnag/utility/metadata_delegate" +require "bugsnag/utility/feature_flag_delegate" # rubocop:todo Metrics/ModuleLength module Bugsnag diff --git a/lib/bugsnag/utility/feature_flag_delegate.rb b/lib/bugsnag/utility/feature_flag_delegate.rb new file mode 100644 index 000000000..b59ca13e7 --- /dev/null +++ b/lib/bugsnag/utility/feature_flag_delegate.rb @@ -0,0 +1,77 @@ +module Bugsnag::Utility + # @api private + class FeatureFlagDelegate + def initialize + # feature flags are stored internally in a hash of "name" => + # we don't use a Set because new feature flags should overwrite old ones + # that share a name, but FeatureFlag equality also uses the variant + @storage = {} + end + + # Add a feature flag with the given name & variant + # + # @param name [String] + # @param variant [String, nil] + # @return [void] + def add(name, variant) + @storage[name] = Bugsnag::FeatureFlag.new(name, variant) + end + + # Merge the given array of FeatureFlag instances into the stored feature + # flags + # + # New flags will be appended to the array. Flags with the same name will be + # overwritten, but their position in the array will not change + # + # @param feature_flags [Array] + # @return [void] + def merge(feature_flags) + feature_flags.each do |flag| + next unless flag.is_a?(Bugsnag::FeatureFlag) + + @storage[flag.name] = flag + end + end + + # Remove the stored flag with the given name + # + # @param name [String] + # @return [void] + def remove(name) + @storage.delete(name) + end + + # Remove all the stored flags + # + # @return [void] + def clear + @storage.clear + end + + # Get an array of FeatureFlag instances + # + # @example + # [ + # <#Bugsnag::FeatureFlag>, + # <#Bugsnag::FeatureFlag>, + # ] + # + # @return [Array] + def to_a + @storage.values + end + + # Get the feature flags in their JSON representation + # + # @example + # [ + # { "featureFlag" => "name", "variant" => "variant" }, + # { "featureFlag" => "another name" }, + # ] + # + # @return [Array String}>] + def as_json + to_a.map(&:to_h) + end + end +end diff --git a/spec/utility/feature_flag_delegate_spec.rb b/spec/utility/feature_flag_delegate_spec.rb new file mode 100644 index 000000000..71a0bc55e --- /dev/null +++ b/spec/utility/feature_flag_delegate_spec.rb @@ -0,0 +1,254 @@ +require 'spec_helper' + +describe Bugsnag::Utility::FeatureFlagDelegate do + it "contains no flags by default" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + expect(delegate.as_json).to eq([]) + end + + describe "#add" do + it "can add flags individually" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "xyz") + delegate.add("another", nil) + delegate.add("a third one", "1234") + + expect(delegate.as_json).to eq([ + { "featureFlag" => "abc", "variant" => "xyz" }, + { "featureFlag" => "another" }, + { "featureFlag" => "a third one", "variant" => "1234" }, + ]) + end + + it "replaces flags by name when the original has no variant" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", nil) + delegate.add("another", nil) + delegate.add("abc", "123") + + expect(delegate.as_json).to eq([ + { "featureFlag" => "abc", "variant" => "123" }, + { "featureFlag" => "another" }, + ]) + end + + it "replaces flags by name when the replacement has no variant" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "123") + delegate.add("another", nil) + delegate.add("abc", nil) + + expect(delegate.as_json).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "another" }, + ]) + end + + it "replaces flags by name when both have variants" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "123") + delegate.add("another", nil) + delegate.add("abc", "987") + + expect(delegate.as_json).to eq([ + { "featureFlag" => "abc", "variant" => "987" }, + { "featureFlag" => "another" }, + ]) + end + end + + describe "#merge" do + it "can add multiple flags at once" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.merge([ + Bugsnag::FeatureFlag.new("a", "xyz"), + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("c", "111"), + Bugsnag::FeatureFlag.new("d"), + ]) + + expect(delegate.as_json).to eq([ + { "featureFlag" => "a", "variant" => "xyz" }, + { "featureFlag" => "b" }, + { "featureFlag" => "c", "variant" => "111" }, + { "featureFlag" => "d" }, + ]) + end + + it "replaces flags by name when the original has no variant" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("a", nil) + + delegate.merge([ + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("a", "123"), + ]) + + expect(delegate.as_json).to eq([ + { "featureFlag" => "a", "variant" => "123" }, + { "featureFlag" => "b" }, + ]) + end + + it "replaces flags by name when the replacement has no variant" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("a", "123") + + delegate.merge([ + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("a"), + ]) + + expect(delegate.as_json).to eq([ + { "featureFlag" => "a" }, + { "featureFlag" => "b" }, + ]) + end + + it "replaces flags by name when both have variants" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("a", "987") + + delegate.merge([ + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("a", "123"), + ]) + + expect(delegate.as_json).to eq([ + { "featureFlag" => "a", "variant" => "123" }, + { "featureFlag" => "b" }, + ]) + end + + it "ignores anything that isn't a FeatureFlag instance" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.merge([ + Bugsnag::FeatureFlag.new("a", "xyz"), + 1234, + Bugsnag::FeatureFlag.new("b"), + "hello", + Bugsnag::FeatureFlag.new("c", "111"), + RuntimeError.new("xyz"), + Bugsnag::FeatureFlag.new("d"), + nil, + ]) + + expect(delegate.as_json).to eq([ + { "featureFlag" => "a", "variant" => "xyz" }, + { "featureFlag" => "b" }, + { "featureFlag" => "c", "variant" => "111" }, + { "featureFlag" => "d" }, + ]) + end + end + + describe "#remove" do + it "can remove flags by name" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "xyz") + delegate.add("another", nil) + delegate.add("a third one", "1234") + + delegate.remove("abc") + delegate.remove("a third one") + + expect(delegate.as_json).to eq([ + { "featureFlag" => "another" }, + ]) + end + + it "does nothing when no flag exists with the given name" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "xyz") + delegate.remove("xyz") + + expect(delegate.as_json).to eq([ + { "featureFlag" => "abc", "variant" => "xyz" }, + ]) + end + end + + describe "#clear" do + it "can remove all flags at once" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "xyz") + delegate.add("another", nil) + delegate.add("a third one", "1234") + + delegate.clear + + expect(delegate.as_json).to eq([]) + end + + it "does nothing when there are no flags" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.clear + + expect(delegate.as_json).to eq([]) + end + end + + describe "#to_a" do + it "returns an empty array when there are no feature flags" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + expect(delegate.to_a).to eq([]) + end + + it "returns an array of feature flags" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "xyz") + delegate.add("another", nil) + delegate.add("a third one", "1234") + + expect(delegate.to_a).to eq([ + Bugsnag::FeatureFlag.new("abc", "xyz"), + Bugsnag::FeatureFlag.new("another"), + Bugsnag::FeatureFlag.new("a third one", "1234"), + ]) + end + + it "can be mutated without affecting the internal storage" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "xyz") + delegate.add("another", nil) + delegate.add("a third one", "1234") + + flags = delegate.to_a + + expected = [ + Bugsnag::FeatureFlag.new("abc", "xyz"), + Bugsnag::FeatureFlag.new("another"), + Bugsnag::FeatureFlag.new("a third one", "1234"), + ] + + expect(flags).to eq(expected) + + flags.pop + flags.pop + flags.push(1234) + + expect(delegate.to_a).to eq(expected) + expect(flags).to eq([ + Bugsnag::FeatureFlag.new("abc", "xyz"), + 1234, + ]) + end + end +end From 9fcc5c8e346ece74b79434dec598eaf54faa4405 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Tue, 15 Nov 2022 12:28:22 +0000 Subject: [PATCH 38/61] Add feature flags to events --- lib/bugsnag/report.rb | 45 +++++++++++++++ spec/report_spec.rb | 125 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 2b92f363a..8d88bddb1 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -143,6 +143,7 @@ def initialize(exception, passed_configuration, auto_notify=false) self.user = {} @metadata_delegate = Utility::MetadataDelegate.new + @feature_flags = Utility::FeatureFlagDelegate.new end ## @@ -220,6 +221,7 @@ def as_json time: @created_at }, exceptions: exceptions, + featureFlags: @feature_flags.as_json, groupingHash: grouping_hash, metaData: meta_data, session: session, @@ -358,6 +360,49 @@ def clear_metadata(section, *args) @metadata_delegate.clear_metadata(@meta_data, section, *args) end + # Get the array of stored feature flags + # + # @return [Array] + def feature_flags + @feature_flags.to_a + end + + # Add a feature flag with the given name & variant + # + # @param name [String] + # @param variant [String, nil] + # @return [void] + def add_feature_flag(name, variant = nil) + @feature_flags.add(name, variant) + end + + # Merge the given array of FeatureFlag instances into the stored feature + # flags + # + # New flags will be appended to the array. Flags with the same name will be + # overwritten, but their position in the array will not change + # + # @param feature_flags [Array] + # @return [void] + def add_feature_flags(feature_flags) + @feature_flags.merge(feature_flags) + end + + # Remove the stored flag with the given name + # + # @param name [String] + # @return [void] + def clear_feature_flag(name) + @feature_flags.remove(name) + end + + # Remove all the stored flags + # + # @return [void] + def clear_feature_flags + @feature_flags.clear + end + ## # Set information about the current user # diff --git a/spec/report_spec.rb b/spec/report_spec.rb index 27ff6d658..c1ed44c06 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -2098,5 +2098,130 @@ def to_s expect(payload["payloadVersion"]).to eq("4.0") }) end + + describe "feature flags" do + it "includes no feature flags by default" do + Bugsnag.notify(BugsnagTestException.new("It crashed")) + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([]) + } + end + + it "can add individual feature flags to the payload" do + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + event.add_feature_flag("flag 1") + event.add_feature_flag("flag 2", "1234") + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([ + { "featureFlag" => "flag 1" }, + { "featureFlag" => "flag 2", "variant" => "1234" }, + ]) + } + end + + it "can add multiple feature flags to the payload in one go" do + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + flags = [ + Bugsnag::FeatureFlag.new("a"), + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("c", "1"), + Bugsnag::FeatureFlag.new("d", "2"), + ] + + event.add_feature_flags(flags) + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([ + { "featureFlag" => "a" }, + { "featureFlag" => "b" }, + { "featureFlag" => "c", "variant" => "1" }, + { "featureFlag" => "d", "variant" => "2" }, + ]) + } + end + + it "can remove a feature flag from the payload" do + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + flags = [ + Bugsnag::FeatureFlag.new("a"), + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("c", "1"), + Bugsnag::FeatureFlag.new("d", "2"), + ] + + event.add_feature_flags(flags) + event.add_feature_flag("e") + + event.clear_feature_flag("b") + event.clear_feature_flag("d") + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([ + { "featureFlag" => "a" }, + { "featureFlag" => "c", "variant" => "1" }, + { "featureFlag" => "e" }, + ]) + } + end + + it "can remove all feature flags from the payload" do + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + flags = [ + Bugsnag::FeatureFlag.new("a"), + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("c", "1"), + Bugsnag::FeatureFlag.new("d", "2"), + ] + + event.add_feature_flags(flags) + event.add_feature_flag("e") + + event.clear_feature_flags + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([]) + } + end + + it "can get feature flags from the event" do + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + flags = [ + Bugsnag::FeatureFlag.new("a"), + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("c", "1"), + Bugsnag::FeatureFlag.new("d", "2"), + ] + + event.add_feature_flags(flags) + event.add_feature_flag("e") + + expect(event.feature_flags).to eq([ + Bugsnag::FeatureFlag.new("a"), + Bugsnag::FeatureFlag.new("b"), + Bugsnag::FeatureFlag.new("c", "1"), + Bugsnag::FeatureFlag.new("d", "2"), + Bugsnag::FeatureFlag.new("e"), + ]) + + event.clear_feature_flags + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([]) + } + end + end end # rubocop:enable Metrics/BlockLength From 92c895048945d37da6f871379467e75a7abe266e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 16 Nov 2022 11:35:24 +0000 Subject: [PATCH 39/61] Add FeatureFlag#valid? This returns true if: - name is a string with at least 1 character - variant is nil OR variant is a string --- lib/bugsnag/feature_flag.rb | 10 +++++++++ spec/feature_flag_spec.rb | 43 +++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/lib/bugsnag/feature_flag.rb b/lib/bugsnag/feature_flag.rb index 0cfb5ba36..623a01b8f 100644 --- a/lib/bugsnag/feature_flag.rb +++ b/lib/bugsnag/feature_flag.rb @@ -42,6 +42,16 @@ def to_h end end + # Check if this flag is valid, i.e. has a name that's a String and a variant + # that's either nil or a String + # + # @return [Boolean] + def valid? + @name.is_a?(String) && + !@name.empty? && + (@variant.nil? || @variant.is_a?(String)) + end + private # Coerce this variant into a valid value (String or nil) diff --git a/spec/feature_flag_spec.rb b/spec/feature_flag_spec.rb index 370ffbbce..4fdf5c22f 100644 --- a/spec/feature_flag_spec.rb +++ b/spec/feature_flag_spec.rb @@ -118,4 +118,47 @@ def to_s expect(flag2).not_to eq(flag1) end end + + describe "#valid?" do + [ + nil, + true, + false, + 1234, + [1, 2, 3], + { a: 1, b: 2 }, + :abc, + "", + ].each do |name| + it "returns false when name is '#{name.inspect}' with no variant" do + flag = Bugsnag::FeatureFlag.new(name) + + expect(flag.valid?).to be(false) + end + + it "returns false when name is '#{name.inspect}' and variant is present" do + flag = Bugsnag::FeatureFlag.new(name, name) + + expect(flag.valid?).to be(false) + end + + it "returns true when name is a string and variant is '#{name.inspect}'" do + flag = Bugsnag::FeatureFlag.new("a name", name) + + expect(flag.valid?).to be(true) + end + end + + it "returns true when name is a string with no variant" do + flag = Bugsnag::FeatureFlag.new("a name") + + expect(flag.valid?).to be(true) + end + + it "returns true when name and variant are strings" do + flag = Bugsnag::FeatureFlag.new("a name", "a variant") + + expect(flag.valid?).to be(true) + end + end end From 5e256135b06d334fc31029103073121e65b94034 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 16 Nov 2022 11:36:15 +0000 Subject: [PATCH 40/61] Drop feature flags that are invalid The FeatureFlagDelegate now ensures flags are valid before storing them, so we no longer deliver flags with invalid names --- lib/bugsnag/utility/feature_flag_delegate.rb | 7 +++- spec/utility/feature_flag_delegate_spec.rb | 42 ++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/bugsnag/utility/feature_flag_delegate.rb b/lib/bugsnag/utility/feature_flag_delegate.rb index b59ca13e7..41fc5ddb8 100644 --- a/lib/bugsnag/utility/feature_flag_delegate.rb +++ b/lib/bugsnag/utility/feature_flag_delegate.rb @@ -14,7 +14,11 @@ def initialize # @param variant [String, nil] # @return [void] def add(name, variant) - @storage[name] = Bugsnag::FeatureFlag.new(name, variant) + flag = Bugsnag::FeatureFlag.new(name, variant) + + return unless flag.valid? + + @storage[flag.name] = flag end # Merge the given array of FeatureFlag instances into the stored feature @@ -28,6 +32,7 @@ def add(name, variant) def merge(feature_flags) feature_flags.each do |flag| next unless flag.is_a?(Bugsnag::FeatureFlag) + next unless flag.valid? @storage[flag.name] = flag end diff --git a/spec/utility/feature_flag_delegate_spec.rb b/spec/utility/feature_flag_delegate_spec.rb index 71a0bc55e..7816cab13 100644 --- a/spec/utility/feature_flag_delegate_spec.rb +++ b/spec/utility/feature_flag_delegate_spec.rb @@ -1,6 +1,16 @@ require 'spec_helper' describe Bugsnag::Utility::FeatureFlagDelegate do + invalid_names = [ + nil, + true, + false, + 1234, + [1, 2, 3], + { a: 1, b: 2 }, + "", + ] + it "contains no flags by default" do delegate = Bugsnag::Utility::FeatureFlagDelegate.new @@ -60,6 +70,21 @@ { "featureFlag" => "another" }, ]) end + + invalid_names.each do |name| + it "drops flags when name is '#{name.inspect}'" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.add("abc", "123") + delegate.add(name, nil) + delegate.add("xyz", "987") + + expect(delegate.as_json).to eq([ + { "featureFlag" => "abc", "variant" => "123" }, + { "featureFlag" => "xyz", "variant" => "987" }, + ]) + end + end end describe "#merge" do @@ -150,6 +175,23 @@ { "featureFlag" => "d" }, ]) end + + invalid_names.each do |name| + it "drops flag when name is '#{name.inspect}'" do + delegate = Bugsnag::Utility::FeatureFlagDelegate.new + + delegate.merge([ + Bugsnag::FeatureFlag.new("abc", "123"), + Bugsnag::FeatureFlag.new(name, "456"), + Bugsnag::FeatureFlag.new("xyz", "789"), + ]) + + expect(delegate.as_json).to eq([ + { "featureFlag" => "abc", "variant" => "123" }, + { "featureFlag" => "xyz", "variant" => "789" }, + ]) + end + end end describe "#remove" do From 21b7edb785179e2504770bb0a21f179e89437eb9 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 16 Nov 2022 09:49:10 +0000 Subject: [PATCH 41/61] Add tests for feature flags in Rack apps --- features/fixtures/rack/app/app.rb | 30 +++++++++++++++++++++++++ features/rack.feature | 37 +++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/features/fixtures/rack/app/app.rb b/features/fixtures/rack/app/app.rb index 41d0f2eca..1220c564d 100644 --- a/features/fixtures/rack/app/app.rb +++ b/features/fixtures/rack/app/app.rb @@ -24,6 +24,36 @@ def call(env) rescue StandardError => e Bugsnag.notify(e) end + when '/feature-flags/unhandled' + Bugsnag.add_on_error(proc do |event| + event.add_feature_flag('a', '1') + + event.add_feature_flags([ + Bugsnag::FeatureFlag.new('b'), + Bugsnag::FeatureFlag.new('c', '3'), + ]) + + event.add_feature_flag('d') + + if req.params["clear_all_flags"] + event.clear_feature_flags + end + end) + + raise 'Unhandled error' + when '/feature-flags/handled' + Bugsnag.notify(RuntimeError.new('oh no')) do |event| + event.add_feature_flag('x') + + event.add_feature_flags([ + Bugsnag::FeatureFlag.new('y', '1234'), + Bugsnag::FeatureFlag.new('z'), + ]) + + if req.params["clear_all_flags"] + event.clear_feature_flags + end + end end res = Rack::Response.new diff --git a/features/rack.feature b/features/rack.feature index e38e1090a..84c1ebe39 100644 --- a/features/rack.feature +++ b/features/rack.feature @@ -131,3 +131,40 @@ Scenario: A request with cookies and no matching filter will set cookies in meta And the event "metaData.request.params.b" equals "456" And the event "metaData.request.referer" is null And the event "metaData.request.url" ends with "/unhandled?a=123&b=456" + +Scenario: adding feature flags for an unhandled error + Given I start the rack service + When I navigate to the route "/feature-flags/unhandled" on the rack app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event contains the following feature flags: + | featureFlag | variant | + | a | 1 | + | b | | + | c | 3 | + | d | | + + Scenario: adding feature flags for a handled error + Given I start the rack service + When I navigate to the route "/feature-flags/handled" on the rack app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event contains the following feature flags: + | featureFlag | variant | + | x | | + | y | 1234 | + | z | | + +Scenario: clearing feature flags for an unhandled error + Given I start the rack service + When I navigate to the route "/feature-flags/unhandled?clear_all_flags=1" on the rack app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event has no feature flags + + Scenario: clearing feature flags for a handled error + Given I start the rack service + When I navigate to the route "/feature-flags/handled?clear_all_flags=1" on the rack app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event has no feature flags From 0e2171aad4c3a3d9a4fb13ad3c31d81cf2c33ee9 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 11:09:24 +0000 Subject: [PATCH 42/61] Test feature flags in Rails 7 --- .../controllers/feature_flags_controller.rb | 25 +++++++ features/fixtures/rails7/app/config/routes.rb | 3 + features/rails_features/feature_flags.feature | 71 +++++++++++++++++++ 3 files changed, 99 insertions(+) create mode 100644 features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb create mode 100644 features/rails_features/feature_flags.feature diff --git a/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb new file mode 100644 index 000000000..ffb4007bf --- /dev/null +++ b/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb @@ -0,0 +1,25 @@ +class FeatureFlagsController < ActionController::Base + protect_from_forgery + + before_bugsnag_notify :add_feature_flags + + def unhandled + raise 'oh no' + end + + def handled + Bugsnag.notify(RuntimeError.new('ahhh')) + end + + private + + def add_feature_flags(event) + params['flags'].each do |key, value| + event.add_feature_flag(key, value) + end + + if params.key?('clear_all_flags') + event.clear_feature_flags + end + end +end diff --git a/features/fixtures/rails7/app/config/routes.rb b/features/fixtures/rails7/app/config/routes.rb index f153a9eda..3c0f75436 100644 --- a/features/fixtures/rails7/app/config/routes.rb +++ b/features/fixtures/rails7/app/config/routes.rb @@ -65,4 +65,7 @@ get 'active_job/handled', to: 'active_job#handled' get 'active_job/unhandled', to: 'active_job#unhandled' + + get 'features/handled', to: 'feature_flags#handled' + get 'features/unhandled', to: 'feature_flags#unhandled' end diff --git a/features/rails_features/feature_flags.feature b/features/rails_features/feature_flags.feature new file mode 100644 index 000000000..bb00d27be --- /dev/null +++ b/features/rails_features/feature_flags.feature @@ -0,0 +1,71 @@ +Feature: feature flags + +@rails7 +Scenario: adding feature flags for an unhandled error + Given I start the rails service + When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4" on the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event "unhandled" is true + And the event "severity" equals "error" + And the exception "errorClass" equals "RuntimeError" + And the exception "message" equals "oh no" + And the event contains the following feature flags: + | featureFlag | variant | + | a | 1 | + | b | | + | c | 3 | + | d | 4 | + # ensure each request can have its own set of feature flags + When I discard the oldest error + And I navigate to the route "/features/unhandled?flags[x]=9&flags[y]&flags[z]=7" on the rails app + And I wait to receive an error + And the event contains the following feature flags: + | featureFlag | variant | + | x | 9 | + | y | | + | z | 7 | + +@rails7 +Scenario: adding feature flags for a handled error + Given I start the rails service + When I navigate to the route "/features/handled?flags[ab]=12&flags[cd]=34" on the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event "unhandled" is false + And the event "severity" equals "warning" + And the exception "errorClass" equals "RuntimeError" + And the exception "message" equals "ahhh" + And the event contains the following feature flags: + | featureFlag | variant | + | ab | 12 | + | cd | 34 | + # ensure each request can have its own set of feature flags + When I discard the oldest error + And I navigate to the route "/features/unhandled?flags[e]=h&flags[f]=i&flags[g]" on the rails app + And I wait to receive an error + And the event contains the following feature flags: + | featureFlag | variant | + | e | h | + | f | i | + | g | | + +@rails7 +Scenario: clearing all feature flags doesn't affect subsequent requests + Given I start the rails service + When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4&clear_all_flags" on the rails app + And I wait to receive an error + Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier + And the event "unhandled" is true + And the event "severity" equals "error" + And the exception "errorClass" equals "RuntimeError" + And the exception "message" equals "oh no" + And the event has no feature flags + When I discard the oldest error + And I navigate to the route "/features/unhandled?flags[x]=9&flags[y]&flags[z]=7" on the rails app + And I wait to receive an error + And the event contains the following feature flags: + | featureFlag | variant | + | x | 9 | + | y | | + | z | 7 | From eca29f48261f3d5aa4091df6e14338cee350b1c9 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 11:16:04 +0000 Subject: [PATCH 43/61] Test feature flags in Rails 6 --- .../controllers/feature_flags_controller.rb | 25 +++++++++++++++++++ features/fixtures/rails6/app/config/routes.rb | 3 +++ features/rails_features/feature_flags.feature | 6 ++--- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb diff --git a/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb new file mode 100644 index 000000000..ffb4007bf --- /dev/null +++ b/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb @@ -0,0 +1,25 @@ +class FeatureFlagsController < ActionController::Base + protect_from_forgery + + before_bugsnag_notify :add_feature_flags + + def unhandled + raise 'oh no' + end + + def handled + Bugsnag.notify(RuntimeError.new('ahhh')) + end + + private + + def add_feature_flags(event) + params['flags'].each do |key, value| + event.add_feature_flag(key, value) + end + + if params.key?('clear_all_flags') + event.clear_feature_flags + end + end +end diff --git a/features/fixtures/rails6/app/config/routes.rb b/features/fixtures/rails6/app/config/routes.rb index f153a9eda..3c0f75436 100644 --- a/features/fixtures/rails6/app/config/routes.rb +++ b/features/fixtures/rails6/app/config/routes.rb @@ -65,4 +65,7 @@ get 'active_job/handled', to: 'active_job#handled' get 'active_job/unhandled', to: 'active_job#unhandled' + + get 'features/handled', to: 'feature_flags#handled' + get 'features/unhandled', to: 'feature_flags#unhandled' end diff --git a/features/rails_features/feature_flags.feature b/features/rails_features/feature_flags.feature index bb00d27be..874f2d1f9 100644 --- a/features/rails_features/feature_flags.feature +++ b/features/rails_features/feature_flags.feature @@ -1,6 +1,6 @@ Feature: feature flags -@rails7 +@rails6 @rails7 Scenario: adding feature flags for an unhandled error Given I start the rails service When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4" on the rails app @@ -26,7 +26,7 @@ Scenario: adding feature flags for an unhandled error | y | | | z | 7 | -@rails7 +@rails6 @rails7 Scenario: adding feature flags for a handled error Given I start the rails service When I navigate to the route "/features/handled?flags[ab]=12&flags[cd]=34" on the rails app @@ -50,7 +50,7 @@ Scenario: adding feature flags for a handled error | f | i | | g | | -@rails7 +@rails6 @rails7 Scenario: clearing all feature flags doesn't affect subsequent requests Given I start the rails service When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4&clear_all_flags" on the rails app From 9c4506f38adf5fee156e4bc19226bf23157e1a77 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 11:22:07 +0000 Subject: [PATCH 44/61] Test feature flags in Rails 5 --- .../controllers/feature_flags_controller.rb | 25 +++++++++++++++++++ features/fixtures/rails5/app/config/routes.rb | 3 +++ features/rails_features/feature_flags.feature | 6 ++--- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb diff --git a/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb new file mode 100644 index 000000000..ffb4007bf --- /dev/null +++ b/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb @@ -0,0 +1,25 @@ +class FeatureFlagsController < ActionController::Base + protect_from_forgery + + before_bugsnag_notify :add_feature_flags + + def unhandled + raise 'oh no' + end + + def handled + Bugsnag.notify(RuntimeError.new('ahhh')) + end + + private + + def add_feature_flags(event) + params['flags'].each do |key, value| + event.add_feature_flag(key, value) + end + + if params.key?('clear_all_flags') + event.clear_feature_flags + end + end +end diff --git a/features/fixtures/rails5/app/config/routes.rb b/features/fixtures/rails5/app/config/routes.rb index f153a9eda..3c0f75436 100644 --- a/features/fixtures/rails5/app/config/routes.rb +++ b/features/fixtures/rails5/app/config/routes.rb @@ -65,4 +65,7 @@ get 'active_job/handled', to: 'active_job#handled' get 'active_job/unhandled', to: 'active_job#unhandled' + + get 'features/handled', to: 'feature_flags#handled' + get 'features/unhandled', to: 'feature_flags#unhandled' end diff --git a/features/rails_features/feature_flags.feature b/features/rails_features/feature_flags.feature index 874f2d1f9..3f3a632a1 100644 --- a/features/rails_features/feature_flags.feature +++ b/features/rails_features/feature_flags.feature @@ -1,6 +1,6 @@ Feature: feature flags -@rails6 @rails7 +@rails5 @rails6 @rails7 Scenario: adding feature flags for an unhandled error Given I start the rails service When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4" on the rails app @@ -26,7 +26,7 @@ Scenario: adding feature flags for an unhandled error | y | | | z | 7 | -@rails6 @rails7 +@rails5 @rails6 @rails7 Scenario: adding feature flags for a handled error Given I start the rails service When I navigate to the route "/features/handled?flags[ab]=12&flags[cd]=34" on the rails app @@ -50,7 +50,7 @@ Scenario: adding feature flags for a handled error | f | i | | g | | -@rails6 @rails7 +@rails5 @rails6 @rails7 Scenario: clearing all feature flags doesn't affect subsequent requests Given I start the rails service When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4&clear_all_flags" on the rails app From ff88b6c921c4a4c7546b388fb1848fb8a4ad23d7 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 11:27:41 +0000 Subject: [PATCH 45/61] Test feature flags in Rails 4 --- .../controllers/feature_flags_controller.rb | 27 +++++++++++++++++++ features/fixtures/rails4/app/config/routes.rb | 1 + features/rails_features/feature_flags.feature | 6 ++--- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb diff --git a/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb new file mode 100644 index 000000000..d5e37750a --- /dev/null +++ b/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb @@ -0,0 +1,27 @@ +class FeatureFlagsController < ActionController::Base + protect_from_forgery + + before_bugsnag_notify :add_feature_flags + + def unhandled + raise 'oh no' + end + + def handled + Bugsnag.notify(RuntimeError.new('ahhh')) + + render json: {} + end + + private + + def add_feature_flags(event) + params['flags'].each do |key, value| + event.add_feature_flag(key, value) + end + + if params.key?('clear_all_flags') + event.clear_feature_flags + end + end +end diff --git a/features/fixtures/rails4/app/config/routes.rb b/features/fixtures/rails4/app/config/routes.rb index 8fee25c36..970a02a67 100644 --- a/features/fixtures/rails4/app/config/routes.rb +++ b/features/fixtures/rails4/app/config/routes.rb @@ -19,4 +19,5 @@ get "/breadcrumbs/(:action)", controller: 'breadcrumbs' get "/mongo/(:action)", controller: 'mongo' get "/active_job/(:action)", controller: 'active_job' + get "/features/(:action)", controller: 'feature_flags' end diff --git a/features/rails_features/feature_flags.feature b/features/rails_features/feature_flags.feature index 3f3a632a1..f33000fe2 100644 --- a/features/rails_features/feature_flags.feature +++ b/features/rails_features/feature_flags.feature @@ -1,6 +1,6 @@ Feature: feature flags -@rails5 @rails6 @rails7 +@rails4 @rails5 @rails6 @rails7 Scenario: adding feature flags for an unhandled error Given I start the rails service When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4" on the rails app @@ -26,7 +26,7 @@ Scenario: adding feature flags for an unhandled error | y | | | z | 7 | -@rails5 @rails6 @rails7 +@rails4 @rails5 @rails6 @rails7 Scenario: adding feature flags for a handled error Given I start the rails service When I navigate to the route "/features/handled?flags[ab]=12&flags[cd]=34" on the rails app @@ -50,7 +50,7 @@ Scenario: adding feature flags for a handled error | f | i | | g | | -@rails5 @rails6 @rails7 +@rails4 @rails5 @rails6 @rails7 Scenario: clearing all feature flags doesn't affect subsequent requests Given I start the rails service When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4&clear_all_flags" on the rails app From 748f5567999ac984ffe3b8e5e5becf4da5b3a2ce Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 11:30:30 +0000 Subject: [PATCH 46/61] Test feature flags in Rails 3 --- .../controllers/feature_flags_controller.rb | 27 +++++++++++++++++++ features/fixtures/rails3/app/config/routes.rb | 1 + features/rails_features/feature_flags.feature | 6 ++--- 3 files changed, 31 insertions(+), 3 deletions(-) create mode 100644 features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb diff --git a/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb new file mode 100644 index 000000000..d5e37750a --- /dev/null +++ b/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb @@ -0,0 +1,27 @@ +class FeatureFlagsController < ActionController::Base + protect_from_forgery + + before_bugsnag_notify :add_feature_flags + + def unhandled + raise 'oh no' + end + + def handled + Bugsnag.notify(RuntimeError.new('ahhh')) + + render json: {} + end + + private + + def add_feature_flags(event) + params['flags'].each do |key, value| + event.add_feature_flag(key, value) + end + + if params.key?('clear_all_flags') + event.clear_feature_flags + end + end +end diff --git a/features/fixtures/rails3/app/config/routes.rb b/features/fixtures/rails3/app/config/routes.rb index 4c1e0dfee..8b2c8769e 100644 --- a/features/fixtures/rails3/app/config/routes.rb +++ b/features/fixtures/rails3/app/config/routes.rb @@ -15,5 +15,6 @@ get "/send_environment/(:action)", controller: 'send_environment' get "/warden/(:action)", controller: 'warden' get "/breadcrumbs/(:action)", controller: 'breadcrumbs' + get "/features/(:action)", controller: 'feature_flags' get "/(:action)", controller: 'application' end diff --git a/features/rails_features/feature_flags.feature b/features/rails_features/feature_flags.feature index f33000fe2..af7c01061 100644 --- a/features/rails_features/feature_flags.feature +++ b/features/rails_features/feature_flags.feature @@ -1,6 +1,6 @@ Feature: feature flags -@rails4 @rails5 @rails6 @rails7 +@rails3 @rails4 @rails5 @rails6 @rails7 Scenario: adding feature flags for an unhandled error Given I start the rails service When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4" on the rails app @@ -26,7 +26,7 @@ Scenario: adding feature flags for an unhandled error | y | | | z | 7 | -@rails4 @rails5 @rails6 @rails7 +@rails3 @rails4 @rails5 @rails6 @rails7 Scenario: adding feature flags for a handled error Given I start the rails service When I navigate to the route "/features/handled?flags[ab]=12&flags[cd]=34" on the rails app @@ -50,7 +50,7 @@ Scenario: adding feature flags for a handled error | f | i | | g | | -@rails4 @rails5 @rails6 @rails7 +@rails3 @rails4 @rails5 @rails6 @rails7 Scenario: clearing all feature flags doesn't affect subsequent requests Given I start the rails service When I navigate to the route "/features/unhandled?flags[a]=1&flags[b]&flags[c]=3&flags[d]=4&clear_all_flags" on the rails app From 4562158bc8e376b9cf2c44eef0d0cfe09179e486 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 12:19:28 +0000 Subject: [PATCH 47/61] Allow storing feature flags in Configuration --- lib/bugsnag/configuration.rb | 51 +++++++++++++++++ spec/configuration_spec.rb | 103 +++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index 36dda53d8..0dc50caf7 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -186,6 +186,11 @@ class Configuration # @return [Breadcrumbs::OnBreadcrumbCallbackList] attr_reader :on_breadcrumb_callbacks + # Expose feature_flag_delegate internally for creating new events + # @api private + # @return [FeatureFlagDelegate] + attr_reader :feature_flag_delegate + API_KEY_REGEX = /[0-9a-f]{32}/i THREAD_LOCAL_NAME = "bugsnag_req_data" @@ -289,6 +294,8 @@ def initialize self.middleware = Bugsnag::MiddlewareStack.new self.middleware.use Bugsnag::Middleware::Callbacks + + @feature_flag_delegate = Bugsnag::Utility::FeatureFlagDelegate.new end ## @@ -664,6 +671,50 @@ def clear_metadata(section, *args) end end + # Add a feature flag with the given name & variant + # + # @param name [String] + # @param variant [String, nil] + # @return [void] + def add_feature_flag(name, variant = nil) + @mutex.synchronize do + @feature_flag_delegate.add(name, variant) + end + end + + # Merge the given array of FeatureFlag instances into the stored feature + # flags + # + # New flags will be appended to the array. Flags with the same name will be + # overwritten, but their position in the array will not change + # + # @param feature_flags [Array] + # @return [void] + def add_feature_flags(feature_flags) + @mutex.synchronize do + @feature_flag_delegate.merge(feature_flags) + end + end + + # Remove the stored flag with the given name + # + # @param name [String] + # @return [void] + def clear_feature_flag(name) + @mutex.synchronize do + @feature_flag_delegate.remove(name) + end + end + + # Remove all the stored flags + # + # @return [void] + def clear_feature_flags + @mutex.synchronize do + @feature_flag_delegate.clear + end + end + ## # Has the context been explicitly set? # diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index df0567ea2..73e412102 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -712,4 +712,107 @@ def output_lines end end end + + describe "feature flags" do + describe "#feature_flag_delegate" do + it "is initially empty" do + expect(subject.feature_flag_delegate.to_a).to be_empty + end + + it "cannot be reassigned" do + expect(subject).not_to respond_to(:feature_flag_delegate=) + end + + it "reflects changes in add/clear feature flags" do + subject.add_feature_flag('abc') + subject.add_feature_flags([ + Bugsnag::FeatureFlag.new('1'), + Bugsnag::FeatureFlag.new('2', 'z'), + Bugsnag::FeatureFlag.new('3'), + ]) + subject.add_feature_flag('xyz', '1234') + + subject.clear_feature_flag('3') + + expect(subject.feature_flag_delegate.to_a).to eq([ + Bugsnag::FeatureFlag.new('abc'), + Bugsnag::FeatureFlag.new('1'), + Bugsnag::FeatureFlag.new('2', 'z'), + Bugsnag::FeatureFlag.new('xyz', '1234'), + ]) + + subject.clear_feature_flags + + expect(subject.feature_flag_delegate.to_a).to be_empty + end + end + + describe "concurrent access" do + it "can handle multiple threads adding feature flags" do + configuration = Bugsnag::Configuration.new + + threads = 5.times.map do |i| + Thread.new do + configuration.add_feature_flag("thread_#{i} flag 1", i) + end + end + + threads += 5.times.map do |i| + Thread.new do + configuration.add_feature_flags([ + Bugsnag::FeatureFlag.new("thread_#{i} flag 2", i * 100), + Bugsnag::FeatureFlag.new("thread_#{i} flag 3", i * 100 + 1), + ]) + end + end + + threads.shuffle.map(&:join) + + flags = configuration.feature_flag_delegate.to_a.sort do |a, b| + a.name <=> b.name + end + + expect(flags).to eq([ + Bugsnag::FeatureFlag.new('thread_0 flag 1', 0), + Bugsnag::FeatureFlag.new('thread_0 flag 2', 0), + Bugsnag::FeatureFlag.new('thread_0 flag 3', 1), + Bugsnag::FeatureFlag.new('thread_1 flag 1', 1), + Bugsnag::FeatureFlag.new('thread_1 flag 2', 100), + Bugsnag::FeatureFlag.new('thread_1 flag 3', 101), + Bugsnag::FeatureFlag.new('thread_2 flag 1', 2), + Bugsnag::FeatureFlag.new('thread_2 flag 2', 200), + Bugsnag::FeatureFlag.new('thread_2 flag 3', 201), + Bugsnag::FeatureFlag.new('thread_3 flag 1', 3), + Bugsnag::FeatureFlag.new('thread_3 flag 2', 300), + Bugsnag::FeatureFlag.new('thread_3 flag 3', 301), + Bugsnag::FeatureFlag.new('thread_4 flag 1', 4), + Bugsnag::FeatureFlag.new('thread_4 flag 2', 400), + Bugsnag::FeatureFlag.new('thread_4 flag 3', 401), + ]) + end + + it "can handle multiple threads clearing feature flags" do + configuration = Bugsnag::Configuration.new + + configuration.add_feature_flag('abc') + configuration.add_feature_flag('xyz') + + 5.times do |i| + configuration.add_feature_flag("thread_#{i}", i) + end + + threads = 5.times.map do |i| + Thread.new do + configuration.clear_feature_flag("thread_#{i}") + configuration.clear_feature_flag('abc') + configuration.clear_feature_flag('xyz') + end + end + + threads.shuffle.map(&:join) + + expect(configuration.feature_flag_delegate.to_a).to be_empty + end + end + end end From 3901a4858423379be54c9357749df8390bd9da5e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 12:19:05 +0000 Subject: [PATCH 48/61] Allow FeatureFlagDelegate instances to be dup-ed --- lib/bugsnag/utility/feature_flag_delegate.rb | 7 +++++ spec/utility/feature_flag_delegate_spec.rb | 31 ++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/bugsnag/utility/feature_flag_delegate.rb b/lib/bugsnag/utility/feature_flag_delegate.rb index 41fc5ddb8..c90980f33 100644 --- a/lib/bugsnag/utility/feature_flag_delegate.rb +++ b/lib/bugsnag/utility/feature_flag_delegate.rb @@ -8,6 +8,13 @@ def initialize @storage = {} end + def initialize_dup(original) + super + + # copy the internal storage when 'dup' is called + @storage = @storage.dup + end + # Add a feature flag with the given name & variant # # @param name [String] diff --git a/spec/utility/feature_flag_delegate_spec.rb b/spec/utility/feature_flag_delegate_spec.rb index 7816cab13..5e93ddfa4 100644 --- a/spec/utility/feature_flag_delegate_spec.rb +++ b/spec/utility/feature_flag_delegate_spec.rb @@ -17,6 +17,37 @@ expect(delegate.as_json).to eq([]) end + it "does not get mutated after being duplicated" do + delegate1 = Bugsnag::Utility::FeatureFlagDelegate.new + delegate1.add('abc', '123') + + delegate2 = delegate1.dup + delegate2.add('xyz', '987') + + expect(delegate1.to_a).to eq([ + Bugsnag::FeatureFlag.new('abc', '123'), + ]) + + expect(delegate2.to_a).to eq([ + Bugsnag::FeatureFlag.new('abc', '123'), + Bugsnag::FeatureFlag.new('xyz', '987'), + ]) + + delegate3 = delegate2.dup + delegate3.clear + + expect(delegate1.to_a).to eq([ + Bugsnag::FeatureFlag.new('abc', '123'), + ]) + + expect(delegate2.to_a).to eq([ + Bugsnag::FeatureFlag.new('abc', '123'), + Bugsnag::FeatureFlag.new('xyz', '987'), + ]) + + expect(delegate3.to_a).to be_empty + end + describe "#add" do it "can add flags individually" do delegate = Bugsnag::Utility::FeatureFlagDelegate.new From 33cc5e6012a3e94ceb28da8d79f8aad5d3dab8dd Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 12:24:59 +0000 Subject: [PATCH 49/61] Include configuration feature flags in events --- lib/bugsnag/report.rb | 2 +- spec/report_spec.rb | 57 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 8d88bddb1..02f7a765f 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -143,7 +143,7 @@ def initialize(exception, passed_configuration, auto_notify=false) self.user = {} @metadata_delegate = Utility::MetadataDelegate.new - @feature_flags = Utility::FeatureFlagDelegate.new + @feature_flags = configuration.feature_flag_delegate.dup end ## diff --git a/spec/report_spec.rb b/spec/report_spec.rb index c1ed44c06..4c1bc3d04 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -2109,6 +2109,63 @@ def to_s } end + it "includes the configuration's feature flags if present" do + Bugsnag.configuration.add_feature_flag('abc') + Bugsnag.configuration.add_feature_flag('xyz', '123') + + Bugsnag.notify(BugsnagTestException.new("It crashed")) + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + ]) + } + end + + it "does not mutate the configuration's feature flags if more flags are added" do + Bugsnag.configuration.add_feature_flag('abc') + Bugsnag.configuration.add_feature_flag('xyz', '123') + + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + event.add_feature_flag('another one') + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + { "featureFlag" => "another one" }, + ]) + + expect(Bugsnag.configuration.feature_flag_delegate.as_json).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + ]) + } + end + + it "does not mutate the configuration's feature flags if flags are removed" do + Bugsnag.configuration.add_feature_flag('abc') + Bugsnag.configuration.add_feature_flag('xyz', '123') + + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + event.clear_feature_flags + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to be_empty + + expect(Bugsnag.configuration.feature_flag_delegate.as_json).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + ]) + } + end + it "can add individual feature flags to the payload" do Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| event.add_feature_flag("flag 1") From 97352473fba1f1dd94b005a1ec060edfbea689bf Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 17 Nov 2022 12:45:33 +0000 Subject: [PATCH 50/61] Forward feature flags API calls to configuration --- lib/bugsnag.rb | 36 ++++++++++++++++++++ spec/bugsnag_spec.rb | 80 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 116 insertions(+) diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index 3738b3f3c..2cbec407c 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -429,6 +429,42 @@ def clear_metadata(section, *args) configuration.clear_metadata(section, *args) end + # Add a feature flag with the given name & variant + # + # @param name [String] + # @param variant [String, nil] + # @return [void] + def add_feature_flag(name, variant = nil) + configuration.add_feature_flag(name, variant) + end + + # Merge the given array of FeatureFlag instances into the stored feature + # flags + # + # New flags will be appended to the array. Flags with the same name will be + # overwritten, but their position in the array will not change + # + # @param feature_flags [Array] + # @return [void] + def add_feature_flags(feature_flags) + configuration.add_feature_flags(feature_flags) + end + + # Remove the stored flag with the given name + # + # @param name [String] + # @return [void] + def clear_feature_flag(name) + configuration.clear_feature_flag(name) + end + + # Remove all the stored flags + # + # @return [void] + def clear_feature_flags + configuration.clear_feature_flags + end + private def should_deliver_notification?(exception, auto_notify) diff --git a/spec/bugsnag_spec.rb b/spec/bugsnag_spec.rb index 6f5af3b46..4d27eee47 100644 --- a/spec/bugsnag_spec.rb +++ b/spec/bugsnag_spec.rb @@ -1127,4 +1127,84 @@ module Kernel }) end end + + describe "feature flags" do + it "is added to the payload" do + Bugsnag.add_feature_flag('abc') + Bugsnag.add_feature_flag('xyz', '123') + + Bugsnag.notify(BugsnagTestException.new("It crashed")) + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + ]) + } + end + + it "does not mutate the global feature flags if more flags are added" do + Bugsnag.add_feature_flag('abc') + Bugsnag.add_feature_flag('xyz', '123') + + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + event.add_feature_flag('another one') + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + { "featureFlag" => "another one" }, + ]) + + expect(Bugsnag.configuration.feature_flag_delegate.as_json).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + ]) + } + end + + it "does not mutate the global feature flags if flags are removed" do + Bugsnag.add_feature_flag('abc') + Bugsnag.add_feature_flag('xyz', '123') + + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + event.clear_feature_flags + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to be_empty + + expect(Bugsnag.configuration.feature_flag_delegate.as_json).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + ]) + } + end + + it "does not mutate the event's feature flags if global flags are removed" do + Bugsnag.add_feature_flags([ + Bugsnag::FeatureFlag.new('abc'), + Bugsnag::FeatureFlag.new('xyz', 123), + ]) + + Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| + Bugsnag.clear_feature_flags + end + + expect(Bugsnag).to have_sent_notification { |payload, headers| + event = get_event_from_payload(payload) + expect(event["featureFlags"]).to eq([ + { "featureFlag" => "abc" }, + { "featureFlag" => "xyz", "variant" => "123" }, + ]) + + expect(Bugsnag.configuration.feature_flag_delegate.as_json).to be_empty + } + end + end end From 6abf4ba40f417ee09757b6f5f04c3fc13ab4ce0d Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 21 Nov 2022 10:07:42 +0000 Subject: [PATCH 51/61] Add flags to configuration in Rack tests --- features/fixtures/rack/app/app.rb | 10 ++++++++++ features/rack.feature | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/features/fixtures/rack/app/app.rb b/features/fixtures/rack/app/app.rb index 1220c564d..9b9e63b18 100644 --- a/features/fixtures/rack/app/app.rb +++ b/features/fixtures/rack/app/app.rb @@ -9,6 +9,12 @@ if ENV.key?('BUGSNAG_METADATA_FILTERS') config.meta_data_filters = JSON.parse(ENV['BUGSNAG_METADATA_FILTERS']) end + + config.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + Bugsnag::FeatureFlag.new('should be removed!'), + ]) end class BugsnagTests @@ -35,6 +41,8 @@ def call(env) event.add_feature_flag('d') + event.clear_feature_flag('should be removed!') + if req.params["clear_all_flags"] event.clear_feature_flags end @@ -50,6 +58,8 @@ def call(env) Bugsnag::FeatureFlag.new('z'), ]) + event.clear_feature_flag('should be removed!') + if req.params["clear_all_flags"] event.clear_feature_flags end diff --git a/features/rack.feature b/features/rack.feature index 84c1ebe39..3827dae8c 100644 --- a/features/rack.feature +++ b/features/rack.feature @@ -139,6 +139,8 @@ Scenario: adding feature flags for an unhandled error Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event contains the following feature flags: | featureFlag | variant | + | from config 1 | | + | from config 2 | abc xyz | | a | 1 | | b | | | c | 3 | @@ -151,6 +153,8 @@ Scenario: adding feature flags for an unhandled error Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event contains the following feature flags: | featureFlag | variant | + | from config 1 | | + | from config 2 | abc xyz | | x | | | y | 1234 | | z | | From f1f416e3b640e8a83e64ec2b248ce3e7dcb2800f Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 21 Nov 2022 10:24:00 +0000 Subject: [PATCH 52/61] Add global flag in Rack tests --- features/fixtures/rack/app/app.rb | 2 ++ features/rack.feature | 2 ++ 2 files changed, 4 insertions(+) diff --git a/features/fixtures/rack/app/app.rb b/features/fixtures/rack/app/app.rb index 9b9e63b18..e606b9bc3 100644 --- a/features/fixtures/rack/app/app.rb +++ b/features/fixtures/rack/app/app.rb @@ -17,6 +17,8 @@ ]) end +Bugsnag.add_feature_flag('from global', '123') + class BugsnagTests def call(env) req = Rack::Request.new(env) diff --git a/features/rack.feature b/features/rack.feature index 3827dae8c..79172bdac 100644 --- a/features/rack.feature +++ b/features/rack.feature @@ -139,6 +139,7 @@ Scenario: adding feature flags for an unhandled error Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event contains the following feature flags: | featureFlag | variant | + | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | a | 1 | @@ -153,6 +154,7 @@ Scenario: adding feature flags for an unhandled error Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event contains the following feature flags: | featureFlag | variant | + | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | x | | From 7ff633c22e19e9fe5e49c55edb618825150e9ce1 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 21 Nov 2022 10:25:30 +0000 Subject: [PATCH 53/61] Add flags to configuration in Rails tests --- .../app/app/controllers/feature_flags_controller.rb | 2 ++ .../fixtures/rails3/app/config/initializers/bugsnag.rb | 6 ++++++ .../app/app/controllers/feature_flags_controller.rb | 2 ++ .../fixtures/rails4/app/config/initializers/bugsnag.rb | 6 ++++++ .../app/app/controllers/feature_flags_controller.rb | 2 ++ .../fixtures/rails5/app/config/initializers/bugsnag.rb | 6 ++++++ .../app/app/controllers/feature_flags_controller.rb | 2 ++ .../fixtures/rails6/app/config/initializers/bugsnag.rb | 6 ++++++ .../app/app/controllers/feature_flags_controller.rb | 2 ++ .../fixtures/rails7/app/config/initializers/bugsnag.rb | 6 ++++++ features/rails_features/feature_flags.feature | 10 ++++++++++ 11 files changed, 50 insertions(+) diff --git a/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb index d5e37750a..c58c0279e 100644 --- a/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb @@ -22,6 +22,8 @@ def add_feature_flags(event) if params.key?('clear_all_flags') event.clear_feature_flags + else + event.clear_feature_flag('should be removed!') end end end diff --git a/features/fixtures/rails3/app/config/initializers/bugsnag.rb b/features/fixtures/rails3/app/config/initializers/bugsnag.rb index c044fb40e..03ada9ac1 100644 --- a/features/fixtures/rails3/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails3/app/config/initializers/bugsnag.rb @@ -32,4 +32,10 @@ report.request[:params][:another_thing] = "hi" end) end + + config.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + Bugsnag::FeatureFlag.new('should be removed!'), + ]) end diff --git a/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb index d5e37750a..c58c0279e 100644 --- a/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb @@ -22,6 +22,8 @@ def add_feature_flags(event) if params.key?('clear_all_flags') event.clear_feature_flags + else + event.clear_feature_flag('should be removed!') end end end diff --git a/features/fixtures/rails4/app/config/initializers/bugsnag.rb b/features/fixtures/rails4/app/config/initializers/bugsnag.rb index c044fb40e..03ada9ac1 100644 --- a/features/fixtures/rails4/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails4/app/config/initializers/bugsnag.rb @@ -32,4 +32,10 @@ report.request[:params][:another_thing] = "hi" end) end + + config.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + Bugsnag::FeatureFlag.new('should be removed!'), + ]) end diff --git a/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb index ffb4007bf..5d7947b4c 100644 --- a/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb @@ -20,6 +20,8 @@ def add_feature_flags(event) if params.key?('clear_all_flags') event.clear_feature_flags + else + event.clear_feature_flag('should be removed!') end end end diff --git a/features/fixtures/rails5/app/config/initializers/bugsnag.rb b/features/fixtures/rails5/app/config/initializers/bugsnag.rb index c92eb03eb..7fa941f73 100644 --- a/features/fixtures/rails5/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails5/app/config/initializers/bugsnag.rb @@ -32,4 +32,10 @@ report.request[:params][:another_thing] = "hi" end) end + + config.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + Bugsnag::FeatureFlag.new('should be removed!'), + ]) end diff --git a/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb index ffb4007bf..5d7947b4c 100644 --- a/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb @@ -20,6 +20,8 @@ def add_feature_flags(event) if params.key?('clear_all_flags') event.clear_feature_flags + else + event.clear_feature_flag('should be removed!') end end end diff --git a/features/fixtures/rails6/app/config/initializers/bugsnag.rb b/features/fixtures/rails6/app/config/initializers/bugsnag.rb index c044fb40e..03ada9ac1 100644 --- a/features/fixtures/rails6/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails6/app/config/initializers/bugsnag.rb @@ -32,4 +32,10 @@ report.request[:params][:another_thing] = "hi" end) end + + config.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + Bugsnag::FeatureFlag.new('should be removed!'), + ]) end diff --git a/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb index ffb4007bf..5d7947b4c 100644 --- a/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb @@ -20,6 +20,8 @@ def add_feature_flags(event) if params.key?('clear_all_flags') event.clear_feature_flags + else + event.clear_feature_flag('should be removed!') end end end diff --git a/features/fixtures/rails7/app/config/initializers/bugsnag.rb b/features/fixtures/rails7/app/config/initializers/bugsnag.rb index 4c2b76588..0a373c4b9 100644 --- a/features/fixtures/rails7/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails7/app/config/initializers/bugsnag.rb @@ -32,4 +32,10 @@ report.request[:params][:another_thing] = "hi" end) end + + config.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + Bugsnag::FeatureFlag.new('should be removed!'), + ]) end diff --git a/features/rails_features/feature_flags.feature b/features/rails_features/feature_flags.feature index af7c01061..c17ffeeba 100644 --- a/features/rails_features/feature_flags.feature +++ b/features/rails_features/feature_flags.feature @@ -12,6 +12,8 @@ Scenario: adding feature flags for an unhandled error And the exception "message" equals "oh no" And the event contains the following feature flags: | featureFlag | variant | + | from config 1 | | + | from config 2 | abc xyz | | a | 1 | | b | | | c | 3 | @@ -22,6 +24,8 @@ Scenario: adding feature flags for an unhandled error And I wait to receive an error And the event contains the following feature flags: | featureFlag | variant | + | from config 1 | | + | from config 2 | abc xyz | | x | 9 | | y | | | z | 7 | @@ -38,6 +42,8 @@ Scenario: adding feature flags for a handled error And the exception "message" equals "ahhh" And the event contains the following feature flags: | featureFlag | variant | + | from config 1 | | + | from config 2 | abc xyz | | ab | 12 | | cd | 34 | # ensure each request can have its own set of feature flags @@ -46,6 +52,8 @@ Scenario: adding feature flags for a handled error And I wait to receive an error And the event contains the following feature flags: | featureFlag | variant | + | from config 1 | | + | from config 2 | abc xyz | | e | h | | f | i | | g | | @@ -66,6 +74,8 @@ Scenario: clearing all feature flags doesn't affect subsequent requests And I wait to receive an error And the event contains the following feature flags: | featureFlag | variant | + | from config 1 | | + | from config 2 | abc xyz | | x | 9 | | y | | | z | 7 | From d32fe0944f74e35e3a69a406aa462b358c5b7849 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Mon, 21 Nov 2022 10:26:36 +0000 Subject: [PATCH 54/61] Add global flag in Rails tests --- features/fixtures/rails3/app/config/initializers/bugsnag.rb | 2 ++ features/fixtures/rails4/app/config/initializers/bugsnag.rb | 2 ++ features/fixtures/rails5/app/config/initializers/bugsnag.rb | 2 ++ features/fixtures/rails6/app/config/initializers/bugsnag.rb | 2 ++ features/fixtures/rails7/app/config/initializers/bugsnag.rb | 2 ++ features/rails_features/feature_flags.feature | 5 +++++ 6 files changed, 15 insertions(+) diff --git a/features/fixtures/rails3/app/config/initializers/bugsnag.rb b/features/fixtures/rails3/app/config/initializers/bugsnag.rb index 03ada9ac1..7ef606dc6 100644 --- a/features/fixtures/rails3/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails3/app/config/initializers/bugsnag.rb @@ -39,3 +39,5 @@ Bugsnag::FeatureFlag.new('should be removed!'), ]) end + +Bugsnag.add_feature_flag('from global', '123') diff --git a/features/fixtures/rails4/app/config/initializers/bugsnag.rb b/features/fixtures/rails4/app/config/initializers/bugsnag.rb index 03ada9ac1..7ef606dc6 100644 --- a/features/fixtures/rails4/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails4/app/config/initializers/bugsnag.rb @@ -39,3 +39,5 @@ Bugsnag::FeatureFlag.new('should be removed!'), ]) end + +Bugsnag.add_feature_flag('from global', '123') diff --git a/features/fixtures/rails5/app/config/initializers/bugsnag.rb b/features/fixtures/rails5/app/config/initializers/bugsnag.rb index 7fa941f73..ebb7ea391 100644 --- a/features/fixtures/rails5/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails5/app/config/initializers/bugsnag.rb @@ -39,3 +39,5 @@ Bugsnag::FeatureFlag.new('should be removed!'), ]) end + +Bugsnag.add_feature_flag('from global', '123') diff --git a/features/fixtures/rails6/app/config/initializers/bugsnag.rb b/features/fixtures/rails6/app/config/initializers/bugsnag.rb index 03ada9ac1..7ef606dc6 100644 --- a/features/fixtures/rails6/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails6/app/config/initializers/bugsnag.rb @@ -39,3 +39,5 @@ Bugsnag::FeatureFlag.new('should be removed!'), ]) end + +Bugsnag.add_feature_flag('from global', '123') diff --git a/features/fixtures/rails7/app/config/initializers/bugsnag.rb b/features/fixtures/rails7/app/config/initializers/bugsnag.rb index 0a373c4b9..2fc79edb4 100644 --- a/features/fixtures/rails7/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails7/app/config/initializers/bugsnag.rb @@ -39,3 +39,5 @@ Bugsnag::FeatureFlag.new('should be removed!'), ]) end + +Bugsnag.add_feature_flag('from global', '123') diff --git a/features/rails_features/feature_flags.feature b/features/rails_features/feature_flags.feature index c17ffeeba..5c3a1962c 100644 --- a/features/rails_features/feature_flags.feature +++ b/features/rails_features/feature_flags.feature @@ -12,6 +12,7 @@ Scenario: adding feature flags for an unhandled error And the exception "message" equals "oh no" And the event contains the following feature flags: | featureFlag | variant | + | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | a | 1 | @@ -24,6 +25,7 @@ Scenario: adding feature flags for an unhandled error And I wait to receive an error And the event contains the following feature flags: | featureFlag | variant | + | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | x | 9 | @@ -42,6 +44,7 @@ Scenario: adding feature flags for a handled error And the exception "message" equals "ahhh" And the event contains the following feature flags: | featureFlag | variant | + | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | ab | 12 | @@ -52,6 +55,7 @@ Scenario: adding feature flags for a handled error And I wait to receive an error And the event contains the following feature flags: | featureFlag | variant | + | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | e | h | @@ -74,6 +78,7 @@ Scenario: clearing all feature flags doesn't affect subsequent requests And I wait to receive an error And the event contains the following feature flags: | featureFlag | variant | + | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | x | 9 | From 7d943a8bd8049795a775e58fbf48e4f3884e9b52 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Tue, 22 Nov 2022 15:39:21 +0000 Subject: [PATCH 55/61] Store feature flags per-request This allows users to add feature flags to Bugsnag whenever they interact with their feature flag service, e.g. ```ruby flag = get_flag_from_service('new-login-page') if flag.enabled? Bugsnag.add_feature_flag('new-login-page') # render new login page else Bugsnag.clear_feature_flag('new-login-page') # render old login page end ``` The previous implementation relied on event's already being request specific, but meant feature flags would usually have to be added in an on_error callback --- features/fixtures/rack/app/app.rb | 67 ++++++++-------- .../controllers/feature_flags_controller.rb | 6 +- .../rails3/app/config/initializers/bugsnag.rb | 17 ++-- .../controllers/feature_flags_controller.rb | 6 +- .../rails4/app/config/initializers/bugsnag.rb | 17 ++-- .../controllers/feature_flags_controller.rb | 6 +- .../rails5/app/config/initializers/bugsnag.rb | 17 ++-- .../controllers/feature_flags_controller.rb | 6 +- .../rails6/app/config/initializers/bugsnag.rb | 17 ++-- .../controllers/feature_flags_controller.rb | 6 +- .../rails7/app/config/initializers/bugsnag.rb | 17 ++-- features/rack.feature | 2 - features/rails_features/feature_flags.feature | 18 ++--- lib/bugsnag/configuration.rb | 31 +++----- spec/configuration_spec.rb | 79 ++++++------------- 15 files changed, 155 insertions(+), 157 deletions(-) diff --git a/features/fixtures/rack/app/app.rb b/features/fixtures/rack/app/app.rb index e606b9bc3..68963bdd1 100644 --- a/features/fixtures/rack/app/app.rb +++ b/features/fixtures/rack/app/app.rb @@ -2,6 +2,8 @@ require 'rack' require 'json' +$clear_all_flags = false + Bugsnag.configure do |config| config.api_key = ENV['BUGSNAG_API_KEY'] config.endpoint = ENV['BUGSNAG_ENDPOINT'] @@ -10,19 +12,31 @@ config.meta_data_filters = JSON.parse(ENV['BUGSNAG_METADATA_FILTERS']) end - config.add_feature_flags([ - Bugsnag::FeatureFlag.new('from config 1'), - Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), - Bugsnag::FeatureFlag.new('should be removed!'), - ]) -end + config.add_feature_flag( + 'this flag is added outside of a request and so should never normally be visible', + 'if an event was reported in the global scope, this flag would be attached to it' + ) + + config.add_on_error(proc do |event| + event.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + ]) -Bugsnag.add_feature_flag('from global', '123') + event.clear_feature_flag('should be removed!') + + if $clear_all_flags + event.clear_feature_flags + end + end) +end class BugsnagTests def call(env) req = Rack::Request.new(env) + $clear_all_flags = !!req.params['clear_all_flags'] + case req.env['REQUEST_PATH'] when '/unhandled' raise 'Unhandled error' @@ -33,39 +47,28 @@ def call(env) Bugsnag.notify(e) end when '/feature-flags/unhandled' - Bugsnag.add_on_error(proc do |event| - event.add_feature_flag('a', '1') - - event.add_feature_flags([ - Bugsnag::FeatureFlag.new('b'), - Bugsnag::FeatureFlag.new('c', '3'), - ]) + Bugsnag.add_feature_flag('a', '1') - event.add_feature_flag('d') + Bugsnag.add_feature_flags([ + Bugsnag::FeatureFlag.new('b'), + Bugsnag::FeatureFlag.new('c', '3'), + ]) - event.clear_feature_flag('should be removed!') - - if req.params["clear_all_flags"] - event.clear_feature_flags - end - end) + Bugsnag.add_feature_flag('d') + Bugsnag.add_feature_flag('should be removed!') raise 'Unhandled error' when '/feature-flags/handled' - Bugsnag.notify(RuntimeError.new('oh no')) do |event| - event.add_feature_flag('x') + Bugsnag.add_feature_flag('x') - event.add_feature_flags([ - Bugsnag::FeatureFlag.new('y', '1234'), - Bugsnag::FeatureFlag.new('z'), - ]) + Bugsnag.add_feature_flags([ + Bugsnag::FeatureFlag.new('y', '1234'), + Bugsnag::FeatureFlag.new('z'), + ]) - event.clear_feature_flag('should be removed!') + Bugsnag.add_feature_flag('should be removed!') - if req.params["clear_all_flags"] - event.clear_feature_flags - end - end + Bugsnag.notify(RuntimeError.new('oh no')) end res = Rack::Response.new diff --git a/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb index c58c0279e..eaab63b1e 100644 --- a/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails3/app/app/controllers/feature_flags_controller.rb @@ -4,10 +4,14 @@ class FeatureFlagsController < ActionController::Base before_bugsnag_notify :add_feature_flags def unhandled + Bugsnag.add_feature_flag('unhandled') + raise 'oh no' end def handled + Bugsnag.add_feature_flag('handled') + Bugsnag.notify(RuntimeError.new('ahhh')) render json: {} @@ -21,7 +25,7 @@ def add_feature_flags(event) end if params.key?('clear_all_flags') - event.clear_feature_flags + event.add_metadata(:clear_all_flags, :a, 1) else event.clear_feature_flag('should be removed!') end diff --git a/features/fixtures/rails3/app/config/initializers/bugsnag.rb b/features/fixtures/rails3/app/config/initializers/bugsnag.rb index 7ef606dc6..0aabfa4c8 100644 --- a/features/fixtures/rails3/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails3/app/config/initializers/bugsnag.rb @@ -33,11 +33,14 @@ end) end - config.add_feature_flags([ - Bugsnag::FeatureFlag.new('from config 1'), - Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), - Bugsnag::FeatureFlag.new('should be removed!'), - ]) -end + config.add_on_error(proc do |event| + event.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + ]) -Bugsnag.add_feature_flag('from global', '123') + if event.metadata.key?(:clear_all_flags) + event.clear_feature_flags + end + end) +end diff --git a/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb index c58c0279e..eaab63b1e 100644 --- a/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails4/app/app/controllers/feature_flags_controller.rb @@ -4,10 +4,14 @@ class FeatureFlagsController < ActionController::Base before_bugsnag_notify :add_feature_flags def unhandled + Bugsnag.add_feature_flag('unhandled') + raise 'oh no' end def handled + Bugsnag.add_feature_flag('handled') + Bugsnag.notify(RuntimeError.new('ahhh')) render json: {} @@ -21,7 +25,7 @@ def add_feature_flags(event) end if params.key?('clear_all_flags') - event.clear_feature_flags + event.add_metadata(:clear_all_flags, :a, 1) else event.clear_feature_flag('should be removed!') end diff --git a/features/fixtures/rails4/app/config/initializers/bugsnag.rb b/features/fixtures/rails4/app/config/initializers/bugsnag.rb index 7ef606dc6..0aabfa4c8 100644 --- a/features/fixtures/rails4/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails4/app/config/initializers/bugsnag.rb @@ -33,11 +33,14 @@ end) end - config.add_feature_flags([ - Bugsnag::FeatureFlag.new('from config 1'), - Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), - Bugsnag::FeatureFlag.new('should be removed!'), - ]) -end + config.add_on_error(proc do |event| + event.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + ]) -Bugsnag.add_feature_flag('from global', '123') + if event.metadata.key?(:clear_all_flags) + event.clear_feature_flags + end + end) +end diff --git a/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb index 5d7947b4c..1433d5867 100644 --- a/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails5/app/app/controllers/feature_flags_controller.rb @@ -4,10 +4,14 @@ class FeatureFlagsController < ActionController::Base before_bugsnag_notify :add_feature_flags def unhandled + Bugsnag.add_feature_flag('unhandled') + raise 'oh no' end def handled + Bugsnag.add_feature_flag('handled') + Bugsnag.notify(RuntimeError.new('ahhh')) end @@ -19,7 +23,7 @@ def add_feature_flags(event) end if params.key?('clear_all_flags') - event.clear_feature_flags + event.add_metadata(:clear_all_flags, :a, 1) else event.clear_feature_flag('should be removed!') end diff --git a/features/fixtures/rails5/app/config/initializers/bugsnag.rb b/features/fixtures/rails5/app/config/initializers/bugsnag.rb index ebb7ea391..5d26472f9 100644 --- a/features/fixtures/rails5/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails5/app/config/initializers/bugsnag.rb @@ -33,11 +33,14 @@ end) end - config.add_feature_flags([ - Bugsnag::FeatureFlag.new('from config 1'), - Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), - Bugsnag::FeatureFlag.new('should be removed!'), - ]) -end + config.add_on_error(proc do |event| + event.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + ]) -Bugsnag.add_feature_flag('from global', '123') + if event.metadata.key?(:clear_all_flags) + event.clear_feature_flags + end + end) +end diff --git a/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb index 5d7947b4c..1433d5867 100644 --- a/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails6/app/app/controllers/feature_flags_controller.rb @@ -4,10 +4,14 @@ class FeatureFlagsController < ActionController::Base before_bugsnag_notify :add_feature_flags def unhandled + Bugsnag.add_feature_flag('unhandled') + raise 'oh no' end def handled + Bugsnag.add_feature_flag('handled') + Bugsnag.notify(RuntimeError.new('ahhh')) end @@ -19,7 +23,7 @@ def add_feature_flags(event) end if params.key?('clear_all_flags') - event.clear_feature_flags + event.add_metadata(:clear_all_flags, :a, 1) else event.clear_feature_flag('should be removed!') end diff --git a/features/fixtures/rails6/app/config/initializers/bugsnag.rb b/features/fixtures/rails6/app/config/initializers/bugsnag.rb index 7ef606dc6..0aabfa4c8 100644 --- a/features/fixtures/rails6/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails6/app/config/initializers/bugsnag.rb @@ -33,11 +33,14 @@ end) end - config.add_feature_flags([ - Bugsnag::FeatureFlag.new('from config 1'), - Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), - Bugsnag::FeatureFlag.new('should be removed!'), - ]) -end + config.add_on_error(proc do |event| + event.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + ]) -Bugsnag.add_feature_flag('from global', '123') + if event.metadata.key?(:clear_all_flags) + event.clear_feature_flags + end + end) +end diff --git a/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb b/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb index 5d7947b4c..1433d5867 100644 --- a/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb +++ b/features/fixtures/rails7/app/app/controllers/feature_flags_controller.rb @@ -4,10 +4,14 @@ class FeatureFlagsController < ActionController::Base before_bugsnag_notify :add_feature_flags def unhandled + Bugsnag.add_feature_flag('unhandled') + raise 'oh no' end def handled + Bugsnag.add_feature_flag('handled') + Bugsnag.notify(RuntimeError.new('ahhh')) end @@ -19,7 +23,7 @@ def add_feature_flags(event) end if params.key?('clear_all_flags') - event.clear_feature_flags + event.add_metadata(:clear_all_flags, :a, 1) else event.clear_feature_flag('should be removed!') end diff --git a/features/fixtures/rails7/app/config/initializers/bugsnag.rb b/features/fixtures/rails7/app/config/initializers/bugsnag.rb index 2fc79edb4..a7079f0ad 100644 --- a/features/fixtures/rails7/app/config/initializers/bugsnag.rb +++ b/features/fixtures/rails7/app/config/initializers/bugsnag.rb @@ -33,11 +33,14 @@ end) end - config.add_feature_flags([ - Bugsnag::FeatureFlag.new('from config 1'), - Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), - Bugsnag::FeatureFlag.new('should be removed!'), - ]) -end + config.add_on_error(proc do |event| + event.add_feature_flags([ + Bugsnag::FeatureFlag.new('from config 1'), + Bugsnag::FeatureFlag.new('from config 2', 'abc xyz'), + ]) -Bugsnag.add_feature_flag('from global', '123') + if event.metadata.key?(:clear_all_flags) + event.clear_feature_flags + end + end) +end diff --git a/features/rack.feature b/features/rack.feature index 79172bdac..3827dae8c 100644 --- a/features/rack.feature +++ b/features/rack.feature @@ -139,7 +139,6 @@ Scenario: adding feature flags for an unhandled error Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event contains the following feature flags: | featureFlag | variant | - | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | a | 1 | @@ -154,7 +153,6 @@ Scenario: adding feature flags for an unhandled error Then the error is valid for the error reporting API version "4.0" for the "Ruby Bugsnag Notifier" notifier And the event contains the following feature flags: | featureFlag | variant | - | from global | 123 | | from config 1 | | | from config 2 | abc xyz | | x | | diff --git a/features/rails_features/feature_flags.feature b/features/rails_features/feature_flags.feature index 5c3a1962c..3e18c2663 100644 --- a/features/rails_features/feature_flags.feature +++ b/features/rails_features/feature_flags.feature @@ -12,7 +12,7 @@ Scenario: adding feature flags for an unhandled error And the exception "message" equals "oh no" And the event contains the following feature flags: | featureFlag | variant | - | from global | 123 | + | unhandled | | | from config 1 | | | from config 2 | abc xyz | | a | 1 | @@ -23,9 +23,9 @@ Scenario: adding feature flags for an unhandled error When I discard the oldest error And I navigate to the route "/features/unhandled?flags[x]=9&flags[y]&flags[z]=7" on the rails app And I wait to receive an error - And the event contains the following feature flags: + Then the event contains the following feature flags: | featureFlag | variant | - | from global | 123 | + | unhandled | | | from config 1 | | | from config 2 | abc xyz | | x | 9 | @@ -44,18 +44,18 @@ Scenario: adding feature flags for a handled error And the exception "message" equals "ahhh" And the event contains the following feature flags: | featureFlag | variant | - | from global | 123 | + | handled | | | from config 1 | | | from config 2 | abc xyz | | ab | 12 | | cd | 34 | # ensure each request can have its own set of feature flags When I discard the oldest error - And I navigate to the route "/features/unhandled?flags[e]=h&flags[f]=i&flags[g]" on the rails app + And I navigate to the route "/features/handled?flags[e]=h&flags[f]=i&flags[g]" on the rails app And I wait to receive an error - And the event contains the following feature flags: + Then the event contains the following feature flags: | featureFlag | variant | - | from global | 123 | + | handled | | | from config 1 | | | from config 2 | abc xyz | | e | h | @@ -76,9 +76,9 @@ Scenario: clearing all feature flags doesn't affect subsequent requests When I discard the oldest error And I navigate to the route "/features/unhandled?flags[x]=9&flags[y]&flags[z]=7" on the rails app And I wait to receive an error - And the event contains the following feature flags: + Then the event contains the following feature flags: | featureFlag | variant | - | from global | 123 | + | unhandled | | | from config 1 | | | from config 2 | abc xyz | | x | 9 | diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index 0dc50caf7..2e71d1361 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -186,11 +186,6 @@ class Configuration # @return [Breadcrumbs::OnBreadcrumbCallbackList] attr_reader :on_breadcrumb_callbacks - # Expose feature_flag_delegate internally for creating new events - # @api private - # @return [FeatureFlagDelegate] - attr_reader :feature_flag_delegate - API_KEY_REGEX = /[0-9a-f]{32}/i THREAD_LOCAL_NAME = "bugsnag_req_data" @@ -294,8 +289,6 @@ def initialize self.middleware = Bugsnag::MiddlewareStack.new self.middleware.use Bugsnag::Middleware::Callbacks - - @feature_flag_delegate = Bugsnag::Utility::FeatureFlagDelegate.new end ## @@ -671,15 +664,21 @@ def clear_metadata(section, *args) end end + # Expose the feature flag delegate internally for use when creating new Events + # + # @return [Bugsnag::Utility::FeatureFlagDelegate] + # @api private + def feature_flag_delegate + request_data[:feature_flag_delegate] ||= Bugsnag::Utility::FeatureFlagDelegate.new + end + # Add a feature flag with the given name & variant # # @param name [String] # @param variant [String, nil] # @return [void] def add_feature_flag(name, variant = nil) - @mutex.synchronize do - @feature_flag_delegate.add(name, variant) - end + feature_flag_delegate.add(name, variant) end # Merge the given array of FeatureFlag instances into the stored feature @@ -691,9 +690,7 @@ def add_feature_flag(name, variant = nil) # @param feature_flags [Array] # @return [void] def add_feature_flags(feature_flags) - @mutex.synchronize do - @feature_flag_delegate.merge(feature_flags) - end + feature_flag_delegate.merge(feature_flags) end # Remove the stored flag with the given name @@ -701,18 +698,14 @@ def add_feature_flags(feature_flags) # @param name [String] # @return [void] def clear_feature_flag(name) - @mutex.synchronize do - @feature_flag_delegate.remove(name) - end + feature_flag_delegate.remove(name) end # Remove all the stored flags # # @return [void] def clear_feature_flags - @mutex.synchronize do - @feature_flag_delegate.clear - end + feature_flag_delegate.clear end ## diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index 73e412102..b78a6c12b 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -747,72 +747,41 @@ def output_lines end end - describe "concurrent access" do - it "can handle multiple threads adding feature flags" do - configuration = Bugsnag::Configuration.new - - threads = 5.times.map do |i| - Thread.new do - configuration.add_feature_flag("thread_#{i} flag 1", i) - end - end + it "has a set of feature flags per-thread" do + configuration = Bugsnag::Configuration.new - threads += 5.times.map do |i| - Thread.new do - configuration.add_feature_flags([ - Bugsnag::FeatureFlag.new("thread_#{i} flag 2", i * 100), - Bugsnag::FeatureFlag.new("thread_#{i} flag 3", i * 100 + 1), - ]) - end - end + threads = 5.times.map do |i| + Thread.new do + expect(configuration.feature_flag_delegate.to_a).to be_empty - threads.shuffle.map(&:join) + configuration.add_feature_flag("thread_#{i} flag 1", i) - flags = configuration.feature_flag_delegate.to_a.sort do |a, b| - a.name <=> b.name + expect(configuration.feature_flag_delegate.to_a).to eq([ + Bugsnag::FeatureFlag.new("thread_#{i} flag 1", i), + ]) end - - expect(flags).to eq([ - Bugsnag::FeatureFlag.new('thread_0 flag 1', 0), - Bugsnag::FeatureFlag.new('thread_0 flag 2', 0), - Bugsnag::FeatureFlag.new('thread_0 flag 3', 1), - Bugsnag::FeatureFlag.new('thread_1 flag 1', 1), - Bugsnag::FeatureFlag.new('thread_1 flag 2', 100), - Bugsnag::FeatureFlag.new('thread_1 flag 3', 101), - Bugsnag::FeatureFlag.new('thread_2 flag 1', 2), - Bugsnag::FeatureFlag.new('thread_2 flag 2', 200), - Bugsnag::FeatureFlag.new('thread_2 flag 3', 201), - Bugsnag::FeatureFlag.new('thread_3 flag 1', 3), - Bugsnag::FeatureFlag.new('thread_3 flag 2', 300), - Bugsnag::FeatureFlag.new('thread_3 flag 3', 301), - Bugsnag::FeatureFlag.new('thread_4 flag 1', 4), - Bugsnag::FeatureFlag.new('thread_4 flag 2', 400), - Bugsnag::FeatureFlag.new('thread_4 flag 3', 401), - ]) end - it "can handle multiple threads clearing feature flags" do - configuration = Bugsnag::Configuration.new - - configuration.add_feature_flag('abc') - configuration.add_feature_flag('xyz') + threads += 5.times.map do |i| + Thread.new do + expect(configuration.feature_flag_delegate.to_a).to be_empty - 5.times do |i| - configuration.add_feature_flag("thread_#{i}", i) - end + configuration.add_feature_flags([ + Bugsnag::FeatureFlag.new("thread_#{i} flag 2", i * 100), + Bugsnag::FeatureFlag.new("thread_#{i} flag 3", i * 100 + 1), + ]) - threads = 5.times.map do |i| - Thread.new do - configuration.clear_feature_flag("thread_#{i}") - configuration.clear_feature_flag('abc') - configuration.clear_feature_flag('xyz') - end + expect(configuration.feature_flag_delegate.to_a).to eq([ + Bugsnag::FeatureFlag.new("thread_#{i} flag 2", i * 100), + Bugsnag::FeatureFlag.new("thread_#{i} flag 3", i * 100 + 1), + ]) end + end - threads.shuffle.map(&:join) + threads.shuffle.map(&:join) - expect(configuration.feature_flag_delegate.to_a).to be_empty - end + # we added no flags in this thread, so there should be none + expect(configuration.feature_flag_delegate.to_a).to be_empty end end end From b18801e0f6c29ecfe90a4c41cc44916c9101c7a7 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 23 Nov 2022 11:51:06 +0000 Subject: [PATCH 56/61] Refactor feature flag methods into a new module Now that the implementations of add_/clear_feature_flag(s) are the same, we can use a module to implement these methods and save us from having copy pasted methods/docs in 3 places --- lib/bugsnag.rb | 46 +++++------------------ lib/bugsnag/configuration.rb | 38 +------------------ lib/bugsnag/report.rb | 46 ++++------------------- lib/bugsnag/utility/feature_data_store.rb | 41 ++++++++++++++++++++ 4 files changed, 60 insertions(+), 111 deletions(-) create mode 100644 lib/bugsnag/utility/feature_data_store.rb diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index 2cbec407c..574712651 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -2,6 +2,7 @@ require "thread" require "bugsnag/version" +require "bugsnag/utility/feature_data_store" require "bugsnag/configuration" require "bugsnag/meta_data" require "bugsnag/report" @@ -48,6 +49,8 @@ module Bugsnag NIL_EXCEPTION_DESCRIPTION = "'nil' was notified as an exception" class << self + include Utility::FeatureDataStore + ## # Configure the Bugsnag notifier application-wide settings. # @@ -429,42 +432,6 @@ def clear_metadata(section, *args) configuration.clear_metadata(section, *args) end - # Add a feature flag with the given name & variant - # - # @param name [String] - # @param variant [String, nil] - # @return [void] - def add_feature_flag(name, variant = nil) - configuration.add_feature_flag(name, variant) - end - - # Merge the given array of FeatureFlag instances into the stored feature - # flags - # - # New flags will be appended to the array. Flags with the same name will be - # overwritten, but their position in the array will not change - # - # @param feature_flags [Array] - # @return [void] - def add_feature_flags(feature_flags) - configuration.add_feature_flags(feature_flags) - end - - # Remove the stored flag with the given name - # - # @param name [String] - # @return [void] - def clear_feature_flag(name) - configuration.clear_feature_flag(name) - end - - # Remove all the stored flags - # - # @return [void] - def clear_feature_flags - configuration.clear_feature_flags - end - private def should_deliver_notification?(exception, auto_notify) @@ -595,6 +562,13 @@ def unwrap_bundler_exception(exception) unwrapped.cause end + + # Expose the feature flag delegate for {Bugsnag::Utility::FeatureDataStore} + # + # @return [Bugsnag::Utility::FeatureFlagDelegate] + def feature_flag_delegate + configuration.feature_flag_delegate + end end end # rubocop:enable Metrics/ModuleLength diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index 2e71d1361..f54d520b7 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -18,6 +18,8 @@ module Bugsnag class Configuration + include Utility::FeatureDataStore + # Your Integration API Key # @return [String, nil] attr_accessor :api_key @@ -672,42 +674,6 @@ def feature_flag_delegate request_data[:feature_flag_delegate] ||= Bugsnag::Utility::FeatureFlagDelegate.new end - # Add a feature flag with the given name & variant - # - # @param name [String] - # @param variant [String, nil] - # @return [void] - def add_feature_flag(name, variant = nil) - feature_flag_delegate.add(name, variant) - end - - # Merge the given array of FeatureFlag instances into the stored feature - # flags - # - # New flags will be appended to the array. Flags with the same name will be - # overwritten, but their position in the array will not change - # - # @param feature_flags [Array] - # @return [void] - def add_feature_flags(feature_flags) - feature_flag_delegate.merge(feature_flags) - end - - # Remove the stored flag with the given name - # - # @param name [String] - # @return [void] - def clear_feature_flag(name) - feature_flag_delegate.remove(name) - end - - # Remove all the stored flags - # - # @return [void] - def clear_feature_flags - feature_flag_delegate.clear - end - ## # Has the context been explicitly set? # diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 02f7a765f..8af18dd14 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -6,6 +6,8 @@ module Bugsnag # rubocop:todo Metrics/ClassLength class Report + include Utility::FeatureDataStore + NOTIFIER_NAME = "Ruby Bugsnag Notifier" NOTIFIER_VERSION = Bugsnag::VERSION NOTIFIER_URL = "https://www.bugsnag.com" @@ -143,7 +145,7 @@ def initialize(exception, passed_configuration, auto_notify=false) self.user = {} @metadata_delegate = Utility::MetadataDelegate.new - @feature_flags = configuration.feature_flag_delegate.dup + @feature_flag_delegate = configuration.feature_flag_delegate.dup end ## @@ -221,7 +223,7 @@ def as_json time: @created_at }, exceptions: exceptions, - featureFlags: @feature_flags.as_json, + featureFlags: @feature_flag_delegate.as_json, groupingHash: grouping_hash, metaData: meta_data, session: session, @@ -364,43 +366,7 @@ def clear_metadata(section, *args) # # @return [Array] def feature_flags - @feature_flags.to_a - end - - # Add a feature flag with the given name & variant - # - # @param name [String] - # @param variant [String, nil] - # @return [void] - def add_feature_flag(name, variant = nil) - @feature_flags.add(name, variant) - end - - # Merge the given array of FeatureFlag instances into the stored feature - # flags - # - # New flags will be appended to the array. Flags with the same name will be - # overwritten, but their position in the array will not change - # - # @param feature_flags [Array] - # @return [void] - def add_feature_flags(feature_flags) - @feature_flags.merge(feature_flags) - end - - # Remove the stored flag with the given name - # - # @param name [String] - # @return [void] - def clear_feature_flag(name) - @feature_flags.remove(name) - end - - # Remove all the stored flags - # - # @return [void] - def clear_feature_flags - @feature_flags.clear + @feature_flag_delegate.to_a end ## @@ -439,6 +405,8 @@ def unhandled_overridden? private + attr_reader :feature_flag_delegate + def update_handled_counts(is_unhandled, was_unhandled) # do nothing if there is no session to update return if @session.nil? diff --git a/lib/bugsnag/utility/feature_data_store.rb b/lib/bugsnag/utility/feature_data_store.rb new file mode 100644 index 000000000..c72939cf8 --- /dev/null +++ b/lib/bugsnag/utility/feature_data_store.rb @@ -0,0 +1,41 @@ +module Bugsnag::Utility + # @abstract Requires a #feature_flag_delegate method returning a + # {Bugsnag::Utility::FeatureFlagDelegate} + module FeatureDataStore + # Add a feature flag with the given name & variant + # + # @param name [String] + # @param variant [String, nil] + # @return [void] + def add_feature_flag(name, variant = nil) + feature_flag_delegate.add(name, variant) + end + + # Merge the given array of FeatureFlag instances into the stored feature + # flags + # + # New flags will be appended to the array. Flags with the same name will be + # overwritten, but their position in the array will not change + # + # @param feature_flags [Array] + # @return [void] + def add_feature_flags(feature_flags) + feature_flag_delegate.merge(feature_flags) + end + + # Remove the stored flag with the given name + # + # @param name [String] + # @return [void] + def clear_feature_flag(name) + feature_flag_delegate.remove(name) + end + + # Remove all the stored flags + # + # @return [void] + def clear_feature_flags + feature_flag_delegate.clear + end + end +end From 08b3be4236563ed9447a5e7b45975f9afe3da53c Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 23 Nov 2022 11:52:38 +0000 Subject: [PATCH 57/61] Enable YARD's 'embed-mixins' option This embeds methods from included modules directly into the class' docs, rather than linking to them separately This makes the docs a lot clearer when a class includes some methods from a module as they are now listed next to every other method --- .yardopts | 1 + 1 file changed, 1 insertion(+) diff --git a/.yardopts b/.yardopts index b1c9cc2b6..b8575d80c 100644 --- a/.yardopts +++ b/.yardopts @@ -4,6 +4,7 @@ --no-private --protected --title "bugsnag-ruby API Documentation" +--embed-mixins lib/**/*.rb - README.md From 92d2c5c6b186659e883483a1f17382274e2f7d7e Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 24 Nov 2022 11:42:48 +0000 Subject: [PATCH 58/61] Remove feature flag API from Configuration --- features/fixtures/rack/app/app.rb | 5 --- lib/bugsnag.rb | 17 +++++--- lib/bugsnag/configuration.rb | 10 ----- lib/bugsnag/report.rb | 2 +- spec/bugsnag_spec.rb | 47 +++++++++++++++++--- spec/configuration_spec.rb | 72 ------------------------------- spec/report_spec.rb | 22 +++++----- 7 files changed, 63 insertions(+), 112 deletions(-) diff --git a/features/fixtures/rack/app/app.rb b/features/fixtures/rack/app/app.rb index 68963bdd1..847c7435f 100644 --- a/features/fixtures/rack/app/app.rb +++ b/features/fixtures/rack/app/app.rb @@ -12,11 +12,6 @@ config.meta_data_filters = JSON.parse(ENV['BUGSNAG_METADATA_FILTERS']) end - config.add_feature_flag( - 'this flag is added outside of a request and so should never normally be visible', - 'if an event was reported in the global scope, this flag would be attached to it' - ) - config.add_on_error(proc do |event| event.add_feature_flags([ Bugsnag::FeatureFlag.new('from config 1'), diff --git a/lib/bugsnag.rb b/lib/bugsnag.rb index 574712651..a25ed6b49 100644 --- a/lib/bugsnag.rb +++ b/lib/bugsnag.rb @@ -432,6 +432,16 @@ def clear_metadata(section, *args) configuration.clear_metadata(section, *args) end + # Expose the feature flag delegate internally for use when creating new Events + # + # The Bugsnag module's feature_flag_delegate is request-specific + # + # @return [Bugsnag::Utility::FeatureFlagDelegate] + # @api private + def feature_flag_delegate + configuration.request_data[:feature_flag_delegate] ||= Utility::FeatureFlagDelegate.new + end + private def should_deliver_notification?(exception, auto_notify) @@ -562,13 +572,6 @@ def unwrap_bundler_exception(exception) unwrapped.cause end - - # Expose the feature flag delegate for {Bugsnag::Utility::FeatureDataStore} - # - # @return [Bugsnag::Utility::FeatureFlagDelegate] - def feature_flag_delegate - configuration.feature_flag_delegate - end end end # rubocop:enable Metrics/ModuleLength diff --git a/lib/bugsnag/configuration.rb b/lib/bugsnag/configuration.rb index f54d520b7..36dda53d8 100644 --- a/lib/bugsnag/configuration.rb +++ b/lib/bugsnag/configuration.rb @@ -18,8 +18,6 @@ module Bugsnag class Configuration - include Utility::FeatureDataStore - # Your Integration API Key # @return [String, nil] attr_accessor :api_key @@ -666,14 +664,6 @@ def clear_metadata(section, *args) end end - # Expose the feature flag delegate internally for use when creating new Events - # - # @return [Bugsnag::Utility::FeatureFlagDelegate] - # @api private - def feature_flag_delegate - request_data[:feature_flag_delegate] ||= Bugsnag::Utility::FeatureFlagDelegate.new - end - ## # Has the context been explicitly set? # diff --git a/lib/bugsnag/report.rb b/lib/bugsnag/report.rb index 8af18dd14..ca6353950 100644 --- a/lib/bugsnag/report.rb +++ b/lib/bugsnag/report.rb @@ -145,7 +145,7 @@ def initialize(exception, passed_configuration, auto_notify=false) self.user = {} @metadata_delegate = Utility::MetadataDelegate.new - @feature_flag_delegate = configuration.feature_flag_delegate.dup + @feature_flag_delegate = Bugsnag.feature_flag_delegate.dup end ## diff --git a/spec/bugsnag_spec.rb b/spec/bugsnag_spec.rb index 4d27eee47..9487b9a86 100644 --- a/spec/bugsnag_spec.rb +++ b/spec/bugsnag_spec.rb @@ -1144,7 +1144,7 @@ module Kernel } end - it "does not mutate the global feature flags if more flags are added" do + it "does not mutate the Bugsnag module's feature flags if more flags are added" do Bugsnag.add_feature_flag('abc') Bugsnag.add_feature_flag('xyz', '123') @@ -1160,14 +1160,14 @@ module Kernel { "featureFlag" => "another one" }, ]) - expect(Bugsnag.configuration.feature_flag_delegate.as_json).to eq([ + expect(Bugsnag.feature_flag_delegate.as_json).to eq([ { "featureFlag" => "abc" }, { "featureFlag" => "xyz", "variant" => "123" }, ]) } end - it "does not mutate the global feature flags if flags are removed" do + it "does not mutate the Bugsnag module's feature flags if flags are removed" do Bugsnag.add_feature_flag('abc') Bugsnag.add_feature_flag('xyz', '123') @@ -1179,14 +1179,14 @@ module Kernel event = get_event_from_payload(payload) expect(event["featureFlags"]).to be_empty - expect(Bugsnag.configuration.feature_flag_delegate.as_json).to eq([ + expect(Bugsnag.feature_flag_delegate.as_json).to eq([ { "featureFlag" => "abc" }, { "featureFlag" => "xyz", "variant" => "123" }, ]) } end - it "does not mutate the event's feature flags if global flags are removed" do + it "does not mutate the event's feature flags if the Bugsnag module's flags are removed" do Bugsnag.add_feature_flags([ Bugsnag::FeatureFlag.new('abc'), Bugsnag::FeatureFlag.new('xyz', 123), @@ -1203,8 +1203,43 @@ module Kernel { "featureFlag" => "xyz", "variant" => "123" }, ]) - expect(Bugsnag.configuration.feature_flag_delegate.as_json).to be_empty + expect(Bugsnag.feature_flag_delegate.as_json).to be_empty } end + + it "stores feature flags per-thread" do + threads = 5.times.map do |i| + Thread.new do + expect(Bugsnag.feature_flag_delegate.to_a).to be_empty + + Bugsnag.add_feature_flag("thread_#{i} flag 1", i) + + expect(Bugsnag.feature_flag_delegate.to_a).to eq([ + Bugsnag::FeatureFlag.new("thread_#{i} flag 1", i), + ]) + end + end + + threads += 5.times.map do |i| + Thread.new do + expect(Bugsnag.feature_flag_delegate.to_a).to be_empty + + Bugsnag.add_feature_flags([ + Bugsnag::FeatureFlag.new("thread_#{i} flag 2", i * 100), + Bugsnag::FeatureFlag.new("thread_#{i} flag 3", i * 100 + 1), + ]) + + expect(Bugsnag.feature_flag_delegate.to_a).to eq([ + Bugsnag::FeatureFlag.new("thread_#{i} flag 2", i * 100), + Bugsnag::FeatureFlag.new("thread_#{i} flag 3", i * 100 + 1), + ]) + end + end + + threads.shuffle.each(&:join) + + # we added no flags in this thread, so there should be none + expect(Bugsnag.feature_flag_delegate.to_a).to be_empty + end end end diff --git a/spec/configuration_spec.rb b/spec/configuration_spec.rb index b78a6c12b..df0567ea2 100644 --- a/spec/configuration_spec.rb +++ b/spec/configuration_spec.rb @@ -712,76 +712,4 @@ def output_lines end end end - - describe "feature flags" do - describe "#feature_flag_delegate" do - it "is initially empty" do - expect(subject.feature_flag_delegate.to_a).to be_empty - end - - it "cannot be reassigned" do - expect(subject).not_to respond_to(:feature_flag_delegate=) - end - - it "reflects changes in add/clear feature flags" do - subject.add_feature_flag('abc') - subject.add_feature_flags([ - Bugsnag::FeatureFlag.new('1'), - Bugsnag::FeatureFlag.new('2', 'z'), - Bugsnag::FeatureFlag.new('3'), - ]) - subject.add_feature_flag('xyz', '1234') - - subject.clear_feature_flag('3') - - expect(subject.feature_flag_delegate.to_a).to eq([ - Bugsnag::FeatureFlag.new('abc'), - Bugsnag::FeatureFlag.new('1'), - Bugsnag::FeatureFlag.new('2', 'z'), - Bugsnag::FeatureFlag.new('xyz', '1234'), - ]) - - subject.clear_feature_flags - - expect(subject.feature_flag_delegate.to_a).to be_empty - end - end - - it "has a set of feature flags per-thread" do - configuration = Bugsnag::Configuration.new - - threads = 5.times.map do |i| - Thread.new do - expect(configuration.feature_flag_delegate.to_a).to be_empty - - configuration.add_feature_flag("thread_#{i} flag 1", i) - - expect(configuration.feature_flag_delegate.to_a).to eq([ - Bugsnag::FeatureFlag.new("thread_#{i} flag 1", i), - ]) - end - end - - threads += 5.times.map do |i| - Thread.new do - expect(configuration.feature_flag_delegate.to_a).to be_empty - - configuration.add_feature_flags([ - Bugsnag::FeatureFlag.new("thread_#{i} flag 2", i * 100), - Bugsnag::FeatureFlag.new("thread_#{i} flag 3", i * 100 + 1), - ]) - - expect(configuration.feature_flag_delegate.to_a).to eq([ - Bugsnag::FeatureFlag.new("thread_#{i} flag 2", i * 100), - Bugsnag::FeatureFlag.new("thread_#{i} flag 3", i * 100 + 1), - ]) - end - end - - threads.shuffle.map(&:join) - - # we added no flags in this thread, so there should be none - expect(configuration.feature_flag_delegate.to_a).to be_empty - end - end end diff --git a/spec/report_spec.rb b/spec/report_spec.rb index 4c1bc3d04..b226dcaea 100644 --- a/spec/report_spec.rb +++ b/spec/report_spec.rb @@ -2109,9 +2109,9 @@ def to_s } end - it "includes the configuration's feature flags if present" do - Bugsnag.configuration.add_feature_flag('abc') - Bugsnag.configuration.add_feature_flag('xyz', '123') + it "includes the Bugsnag module's feature flags if present" do + Bugsnag.add_feature_flag('abc') + Bugsnag.add_feature_flag('xyz', '123') Bugsnag.notify(BugsnagTestException.new("It crashed")) @@ -2124,9 +2124,9 @@ def to_s } end - it "does not mutate the configuration's feature flags if more flags are added" do - Bugsnag.configuration.add_feature_flag('abc') - Bugsnag.configuration.add_feature_flag('xyz', '123') + it "does not mutate the Bugsnag module's feature flags if more flags are added" do + Bugsnag.add_feature_flag('abc') + Bugsnag.add_feature_flag('xyz', '123') Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| event.add_feature_flag('another one') @@ -2140,16 +2140,16 @@ def to_s { "featureFlag" => "another one" }, ]) - expect(Bugsnag.configuration.feature_flag_delegate.as_json).to eq([ + expect(Bugsnag.feature_flag_delegate.as_json).to eq([ { "featureFlag" => "abc" }, { "featureFlag" => "xyz", "variant" => "123" }, ]) } end - it "does not mutate the configuration's feature flags if flags are removed" do - Bugsnag.configuration.add_feature_flag('abc') - Bugsnag.configuration.add_feature_flag('xyz', '123') + it "does not mutate the Bugsnag module's feature flags if flags are removed" do + Bugsnag.add_feature_flag('abc') + Bugsnag.add_feature_flag('xyz', '123') Bugsnag.notify(BugsnagTestException.new("It crashed")) do |event| event.clear_feature_flags @@ -2159,7 +2159,7 @@ def to_s event = get_event_from_payload(payload) expect(event["featureFlags"]).to be_empty - expect(Bugsnag.configuration.feature_flag_delegate.as_json).to eq([ + expect(Bugsnag.feature_flag_delegate.as_json).to eq([ { "featureFlag" => "abc" }, { "featureFlag" => "xyz", "variant" => "123" }, ]) From 25784ea5a66036fe4fa640ab59d32c8e28d248cb Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Wed, 30 Nov 2022 14:58:50 +0000 Subject: [PATCH 59/61] Update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e88acb5f5..d90981eb0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## TBD + +### Enhancements + +* Add support for feature flags & experiments. For more information, please see https://docs.bugsnag.com/product/features-experiments + | [#758](https://github.com/bugsnag/bugsnag-ruby/pull/758) + ## v6.24.2 (21 January 2022) ### Fixes From 43a0da01dc71ddb77bc2a9a865528039dd78e04c Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 1 Dec 2022 09:57:33 +0000 Subject: [PATCH 60/61] Bump version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 21128ab39..961b1c8ec 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.24.2 +6.25.0 From 69826679b41dfe91f00a8a2d7e76f8e6f7bb34b8 Mon Sep 17 00:00:00 2001 From: Joe Haines Date: Thu, 1 Dec 2022 09:57:38 +0000 Subject: [PATCH 61/61] Add version & date to changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d90981eb0..27d996621 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ Changelog ========= -## TBD +## v6.26.0 (1 December 2022) ### Enhancements