airbrake-ruby 5.0.1-java → 5.2.1-java

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +4 -2
  3. data/lib/airbrake-ruby/async_sender.rb +3 -1
  4. data/lib/airbrake-ruby/config.rb +15 -6
  5. data/lib/airbrake-ruby/config/processor.rb +7 -20
  6. data/lib/airbrake-ruby/context.rb +51 -0
  7. data/lib/airbrake-ruby/file_cache.rb +1 -1
  8. data/lib/airbrake-ruby/filter_chain.rb +2 -0
  9. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  10. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
  11. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +1 -1
  12. data/lib/airbrake-ruby/filters/git_revision_filter.rb +1 -1
  13. data/lib/airbrake-ruby/filters/keys_filter.rb +2 -2
  14. data/lib/airbrake-ruby/filters/sql_filter.rb +2 -2
  15. data/lib/airbrake-ruby/filters/thread_filter.rb +2 -3
  16. data/lib/airbrake-ruby/grouppable.rb +1 -1
  17. data/lib/airbrake-ruby/ignorable.rb +0 -2
  18. data/lib/airbrake-ruby/mergeable.rb +1 -1
  19. data/lib/airbrake-ruby/notice_notifier.rb +3 -4
  20. data/lib/airbrake-ruby/performance_notifier.rb +1 -2
  21. data/lib/airbrake-ruby/remote_settings.rb +10 -50
  22. data/lib/airbrake-ruby/remote_settings/callback.rb +44 -0
  23. data/lib/airbrake-ruby/remote_settings/settings_data.rb +3 -8
  24. data/lib/airbrake-ruby/tdigest.rb +7 -6
  25. data/lib/airbrake-ruby/thread_pool.rb +5 -3
  26. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  27. data/lib/airbrake-ruby/version.rb +2 -2
  28. data/spec/airbrake_spec.rb +139 -76
  29. data/spec/async_sender_spec.rb +10 -8
  30. data/spec/backtrace_spec.rb +13 -10
  31. data/spec/benchmark_spec.rb +5 -3
  32. data/spec/code_hunk_spec.rb +24 -15
  33. data/spec/config/processor_spec.rb +29 -87
  34. data/spec/config/validator_spec.rb +5 -2
  35. data/spec/config_spec.rb +26 -17
  36. data/spec/context_spec.rb +54 -0
  37. data/spec/deploy_notifier_spec.rb +6 -4
  38. data/spec/file_cache_spec.rb +1 -0
  39. data/spec/filter_chain_spec.rb +29 -24
  40. data/spec/filters/context_filter_spec.rb +14 -5
  41. data/spec/filters/dependency_filter_spec.rb +3 -1
  42. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  43. data/spec/filters/gem_root_filter_spec.rb +5 -2
  44. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  45. data/spec/filters/git_repository_filter.rb +9 -9
  46. data/spec/filters/git_revision_filter_spec.rb +19 -19
  47. data/spec/filters/keys_allowlist_spec.rb +25 -16
  48. data/spec/filters/keys_blocklist_spec.rb +25 -18
  49. data/spec/filters/root_directory_filter_spec.rb +3 -3
  50. data/spec/filters/sql_filter_spec.rb +26 -26
  51. data/spec/filters/system_exit_filter_spec.rb +4 -2
  52. data/spec/filters/thread_filter_spec.rb +16 -14
  53. data/spec/loggable_spec.rb +2 -2
  54. data/spec/monotonic_time_spec.rb +8 -6
  55. data/spec/nested_exception_spec.rb +46 -46
  56. data/spec/notice_notifier/options_spec.rb +23 -13
  57. data/spec/notice_notifier_spec.rb +52 -47
  58. data/spec/notice_spec.rb +6 -2
  59. data/spec/performance_notifier_spec.rb +67 -60
  60. data/spec/promise_spec.rb +38 -32
  61. data/spec/remote_settings/callback_spec.rb +162 -0
  62. data/spec/remote_settings/settings_data_spec.rb +23 -53
  63. data/spec/remote_settings_spec.rb +42 -75
  64. data/spec/response_spec.rb +34 -12
  65. data/spec/stashable_spec.rb +5 -5
  66. data/spec/stat_spec.rb +7 -5
  67. data/spec/sync_sender_spec.rb +49 -16
  68. data/spec/tdigest_spec.rb +61 -56
  69. data/spec/thread_pool_spec.rb +65 -55
  70. data/spec/time_truncate_spec.rb +4 -2
  71. data/spec/timed_trace_spec.rb +32 -30
  72. data/spec/truncator_spec.rb +72 -43
  73. metadata +53 -47
@@ -0,0 +1,54 @@
1
+ RSpec.describe Airbrake::Context do
2
+ subject(:context) { described_class.current }
3
+
4
+ before { described_class.current.clear }
5
+
6
+ after { described_class.current.clear }
7
+
8
+ describe "#merge!" do
9
+ it "merges the given context with the current one" do
10
+ context.merge!(apples: 'oranges')
11
+ expect(context.to_h).to match(apples: 'oranges')
12
+ end
13
+ end
14
+
15
+ describe "#clear" do
16
+ it "clears the context" do
17
+ context.merge!(apples: 'oranges')
18
+ context.clear
19
+ expect(context.to_h).to be_empty
20
+ end
21
+ end
22
+
23
+ describe "#to_h" do
24
+ it "returns a hash representation of the context" do
25
+ expect(context.to_h).to be_a(Hash)
26
+ end
27
+ end
28
+
29
+ describe "#empty?" do
30
+ context "when the context has data" do
31
+ it "returns true" do
32
+ context.merge!(apples: 'oranges')
33
+ expect(context).not_to be_empty
34
+ end
35
+ end
36
+
37
+ context "when the context has NO data" do
38
+ it "returns false" do
39
+ expect(context).to be_empty
40
+ end
41
+ end
42
+ end
43
+
44
+ context "when another thread is spawned" do
45
+ it "doesn't clash with other threads' contexts" do
46
+ described_class.current.merge!(apples: 'oranges')
47
+ th = Thread.new do
48
+ described_class.current.merge!(foos: 'bars')
49
+ end
50
+ th.join
51
+ expect(described_class.current.to_h).to match(apples: 'oranges')
52
+ end
53
+ end
54
+ end
@@ -4,17 +4,19 @@ RSpec.describe Airbrake::DeployNotifier do
4
4
  end
5
5
 
6
6
  describe "#notify" do
7
+ subject(:deploy_notifier) { described_class.new }
8
+
7
9
  it "returns a promise" do
8
10
  stub_request(:post, 'https://api.airbrake.io/api/v4/projects/1/deploys')
9
11
  .to_return(status: 201, body: '{}')
10
- expect(subject.notify({})).to be_an(Airbrake::Promise)
12
+ expect(deploy_notifier.notify({})).to be_an(Airbrake::Promise)
11
13
  end
12
14
 
13
15
  context "when config is invalid" do
14
16
  before { Airbrake::Config.instance.merge(project_id: nil) }
15
17
 
16
18
  it "returns a rejected promise" do
17
- promise = subject.notify({})
19
+ promise = deploy_notifier.notify({})
18
20
  expect(promise).to be_rejected
19
21
  end
20
22
  end
@@ -28,7 +30,7 @@ RSpec.describe Airbrake::DeployNotifier do
28
30
  instance_of(Airbrake::Promise),
29
31
  URI('https://api.airbrake.io/api/v4/projects/1/deploys'),
30
32
  )
31
- subject.notify(environment: 'barenv')
33
+ deploy_notifier.notify(environment: 'barenv')
32
34
  end
33
35
  end
34
36
 
@@ -41,7 +43,7 @@ RSpec.describe Airbrake::DeployNotifier do
41
43
  instance_of(Airbrake::Promise),
42
44
  URI('https://api.airbrake.io/api/v4/projects/1/deploys'),
43
45
  )
44
- subject.notify({})
46
+ deploy_notifier.notify({})
45
47
  end
46
48
  end
47
49
  end
@@ -1,5 +1,6 @@
1
1
  RSpec.describe Airbrake::FileCache do
2
2
  before { described_class.reset }
3
+
3
4
  after { described_class.reset }
4
5
 
5
6
  describe ".[]=" do
@@ -1,4 +1,6 @@
1
1
  RSpec.describe Airbrake::FilterChain do
2
+ subject(:filter_chain) { described_class.new }
3
+
2
4
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
3
5
 
4
6
  describe "#refine" do
@@ -19,24 +21,27 @@ RSpec.describe Airbrake::FilterChain do
19
21
  it "executes filters from heaviest to lightest" do
20
22
  notice[:params][:bingo] = []
21
23
 
22
- (0...3).reverse_each { |i| subject.add_filter(filter.new(i)) }
23
- subject.refine(notice)
24
+ (0...3).reverse_each { |i| filter_chain.add_filter(filter.new(i)) }
25
+ filter_chain.refine(notice)
24
26
 
25
27
  expect(notice[:params][:bingo]).to eq([2, 1, 0])
26
28
  end
27
29
 
28
30
  it "stops execution once a notice was ignored" do
29
31
  f2 = filter.new(2)
30
- expect(f2).to receive(:call)
32
+ allow(f2).to receive(:call)
31
33
 
32
34
  f1 = proc { |notice| notice.ignore! }
33
35
 
34
36
  f0 = filter.new(-1)
35
- expect(f0).not_to receive(:call)
37
+ allow(f0).to receive(:call)
38
+
39
+ [f2, f1, f0].each { |f| filter_chain.add_filter(f) }
36
40
 
37
- [f2, f1, f0].each { |f| subject.add_filter(f) }
41
+ filter_chain.refine(notice)
38
42
 
39
- subject.refine(notice)
43
+ expect(f2).to have_received(:call)
44
+ expect(f0).not_to have_received(:call)
40
45
  end
41
46
  end
42
47
 
@@ -63,56 +68,56 @@ RSpec.describe Airbrake::FilterChain do
63
68
  notice[:params][:foo] = []
64
69
 
65
70
  f1 = filter.new(1)
66
- subject.add_filter(f1)
71
+ filter_chain.add_filter(f1)
67
72
 
68
73
  foo_filter_mock = double
69
- expect(foo_filter_mock).to(
70
- receive(:name).at_least(:once).and_return('FooFilter'),
71
- )
72
- subject.delete_filter(foo_filter_mock)
74
+ allow(foo_filter_mock).to receive(:name).at_least(:once).and_return('FooFilter')
75
+ filter_chain.delete_filter(foo_filter_mock)
76
+
77
+ expect(foo_filter_mock).to have_received(:name)
73
78
 
74
79
  f2 = filter.new(2)
75
- subject.add_filter(f2)
80
+ filter_chain.add_filter(f2)
76
81
 
77
- subject.refine(notice)
82
+ filter_chain.refine(notice)
78
83
  expect(notice[:params][:foo]).to eq([2])
79
84
  end
80
85
  end
81
86
 
82
87
  describe "#inspect" do
83
88
  it "returns a string representation of an empty FilterChain" do
84
- expect(subject.inspect).to eq('[]')
89
+ expect(filter_chain.inspect).to eq('[]')
85
90
  end
86
91
 
87
92
  it "returns a string representation of a non-empty FilterChain" do
88
- subject.add_filter(proc {})
89
- expect(subject.inspect).to eq('[Proc]')
93
+ filter_chain.add_filter(proc {})
94
+ expect(filter_chain.inspect).to eq('[Proc]')
90
95
  end
91
96
  end
92
97
 
93
98
  describe "#includes?" do
94
99
  context "when a custom filter class is included in the filter chain" do
95
100
  it "returns true" do
96
- klass = Class.new {}
101
+ klass = Class.new
97
102
 
98
- subject.add_filter(klass.new)
99
- expect(subject.includes?(klass)).to eq(true)
103
+ filter_chain.add_filter(klass.new)
104
+ expect(filter_chain.includes?(klass)).to eq(true)
100
105
  end
101
106
  end
102
107
 
103
108
  context "when Proc filter class is included in the filter chain" do
104
109
  it "returns true" do
105
- subject.add_filter(proc {})
106
- expect(subject.includes?(Proc)).to eq(true)
110
+ filter_chain.add_filter(proc {})
111
+ expect(filter_chain.includes?(Proc)).to eq(true)
107
112
  end
108
113
  end
109
114
 
110
115
  context "when filter class is NOT included in the filter chain" do
111
116
  it "returns false" do
112
- klass = Class.new {}
117
+ klass = Class.new
113
118
 
114
- subject.add_filter(proc {})
115
- expect(subject.includes?(klass)).to eq(false)
119
+ filter_chain.add_filter(proc {})
120
+ expect(filter_chain.includes?(klass)).to eq(false)
116
121
  end
117
122
  end
118
123
  end
@@ -3,21 +3,30 @@ RSpec.describe Airbrake::Filters::ContextFilter do
3
3
 
4
4
  context "when the current context is empty" do
5
5
  it "doesn't merge anything with params" do
6
- described_class.new({}).call(notice)
6
+ described_class.new.call(notice)
7
7
  expect(notice[:params]).to be_empty
8
8
  end
9
9
  end
10
10
 
11
11
  context "when the current context has some data" do
12
12
  it "merges the data with params" do
13
- described_class.new(apples: 'oranges').call(notice)
13
+ Airbrake.merge_context(apples: 'oranges')
14
+ described_class.new.call(notice)
14
15
  expect(notice[:params]).to eq(airbrake_context: { apples: 'oranges' })
15
16
  end
16
17
 
17
- it "clears the data from the provided context" do
18
+ it "clears the data from the current context" do
18
19
  context = { apples: 'oranges' }
19
- described_class.new(context).call(notice)
20
- expect(context).to be_empty
20
+ Airbrake.merge_context(context)
21
+ described_class.new.call(notice)
22
+ expect(Airbrake::Context.current).to be_empty
23
+ end
24
+
25
+ it "does not mutate the provided context object" do
26
+ context = { apples: 'oranges' }
27
+ Airbrake.merge_context(context)
28
+ described_class.new.call(notice)
29
+ expect(context).to match(apples: 'oranges')
21
30
  end
22
31
  end
23
32
  end
@@ -1,9 +1,11 @@
1
1
  RSpec.describe Airbrake::Filters::DependencyFilter do
2
+ subject(:dependency_filter) { described_class.new }
3
+
2
4
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
3
5
 
4
6
  describe "#call" do
5
7
  it "attaches loaded dependencies to context/versions/dependencies" do
6
- subject.call(notice)
8
+ dependency_filter.call(notice)
7
9
  expect(notice[:context][:versions][:dependencies]).to include(
8
10
  'airbrake-ruby' => Airbrake::AIRBRAKE_RUBY_VERSION,
9
11
  )
@@ -1,4 +1,6 @@
1
1
  RSpec.describe Airbrake::Filters::ExceptionAttributesFilter do
2
+ subject(:exception_attributes_filter) { described_class.new }
3
+
2
4
  describe "#call" do
3
5
  let(:notice) { Airbrake::Notice.new(ex) }
4
6
 
@@ -12,7 +14,7 @@ RSpec.describe Airbrake::Filters::ExceptionAttributesFilter do
12
14
  end
13
15
 
14
16
  it "doesn't raise" do
15
- expect { subject.call(notice) }.not_to raise_error
17
+ expect { exception_attributes_filter.call(notice) }.not_to raise_error
16
18
  expect(notice[:params]).to be_empty
17
19
  end
18
20
  end
@@ -27,7 +29,7 @@ RSpec.describe Airbrake::Filters::ExceptionAttributesFilter do
27
29
  end
28
30
 
29
31
  it "doesn't raise" do
30
- expect { subject.call(notice) }.not_to raise_error
32
+ expect { exception_attributes_filter.call(notice) }.not_to raise_error
31
33
  expect(notice[:params]).to be_empty
32
34
  end
33
35
  end
@@ -42,7 +44,7 @@ RSpec.describe Airbrake::Filters::ExceptionAttributesFilter do
42
44
  end
43
45
 
44
46
  it "merges parameters with the notice" do
45
- subject.call(notice)
47
+ exception_attributes_filter.call(notice)
46
48
  expect(notice[:params]).to eq(foo: '1')
47
49
  end
48
50
  end
@@ -1,9 +1,12 @@
1
1
  RSpec.describe Airbrake::Filters::GemRootFilter do
2
+ subject(:gem_root_filter) { described_class.new }
3
+
2
4
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
3
5
  let(:root1) { '/my/gem/root' }
4
6
  let(:root2) { '/my/other/gem/root' }
5
7
 
6
8
  before { Gem.path << root1 << root2 }
9
+
7
10
  after { 2.times { Gem.path.pop } }
8
11
 
9
12
  it "replaces gem root in the backtrace with a label" do
@@ -16,7 +19,7 @@ RSpec.describe Airbrake::Filters::GemRootFilter do
16
19
  ]
17
20
  # rubocop:enable Layout/LineLength
18
21
 
19
- subject.call(notice)
22
+ gem_root_filter.call(notice)
20
23
 
21
24
  # rubocop:disable Layout/LineLength
22
25
  expect(notice[:errors].first[:backtrace]).to(
@@ -34,7 +37,7 @@ RSpec.describe Airbrake::Filters::GemRootFilter do
34
37
 
35
38
  it "does not filter file when it is nil" do
36
39
  expect(notice[:errors].first[:file]).to be_nil
37
- expect { subject.call(notice) }.not_to(
40
+ expect { gem_root_filter.call(notice) }.not_to(
38
41
  change { notice[:errors].first[:file] },
39
42
  )
40
43
  end
@@ -1,21 +1,21 @@
1
1
  RSpec.describe Airbrake::Filters::GitLastCheckoutFilter do
2
- subject { described_class.new('.') }
2
+ subject(:git_last_checkout_filter) { described_class.new('.') }
3
3
 
4
4
  let(:notice) { Airbrake::Notice.new(AirbrakeTestError.new) }
5
5
 
6
6
  context "when context/lastCheckout is defined" do
7
7
  it "doesn't attach anything to context/lastCheckout" do
8
8
  notice[:context][:lastCheckout] = '123'
9
- subject.call(notice)
9
+ git_last_checkout_filter.call(notice)
10
10
  expect(notice[:context][:lastCheckout]).to eq('123')
11
11
  end
12
12
  end
13
13
 
14
14
  context "when .git directory doesn't exist" do
15
- subject { described_class.new('root/dir') }
15
+ subject(:git_last_checkout_without_git_dir_filter) { described_class.new('root/dir') }
16
16
 
17
17
  it "doesn't attach anything to context/lastCheckout" do
18
- subject.call(notice)
18
+ git_last_checkout_without_git_dir_filter.call(notice)
19
19
  expect(notice[:context][:lastCheckout]).to be_nil
20
20
  end
21
21
  end
@@ -25,7 +25,7 @@ RSpec.describe Airbrake::Filters::GitLastCheckoutFilter do
25
25
  before { ENV['AIRBRAKE_DEPLOY_USERNAME'] = 'deployer' }
26
26
 
27
27
  it "attaches username from the environment" do
28
- subject.call(notice)
28
+ git_last_checkout_filter.call(notice)
29
29
  expect(notice[:context][:lastCheckout][:username]).to eq('deployer')
30
30
  end
31
31
  end
@@ -34,7 +34,7 @@ RSpec.describe Airbrake::Filters::GitLastCheckoutFilter do
34
34
  before { ENV['AIRBRAKE_DEPLOY_USERNAME'] = nil }
35
35
 
36
36
  it "attaches last checkouted username" do
37
- subject.call(notice)
37
+ git_last_checkout_filter.call(notice)
38
38
  username = notice[:context][:lastCheckout][:username]
39
39
  expect(username).not_to be_empty
40
40
  expect(username).not_to be_nil
@@ -42,20 +42,18 @@ RSpec.describe Airbrake::Filters::GitLastCheckoutFilter do
42
42
  end
43
43
 
44
44
  it "attaches last checkouted email" do
45
- subject.call(notice)
46
- expect(notice[:context][:lastCheckout][:email]).to(
47
- match(/\A\w+[\w.-]*@\w+\.?\w+?\z/),
48
- )
45
+ git_last_checkout_filter.call(notice)
46
+ expect(notice[:context][:lastCheckout][:email]).to(match(/\A\w+@[\w\-.]+\z/))
49
47
  end
50
48
 
51
49
  it "attaches last checkouted revision" do
52
- subject.call(notice)
50
+ git_last_checkout_filter.call(notice)
53
51
  expect(notice[:context][:lastCheckout][:revision]).not_to be_empty
54
52
  expect(notice[:context][:lastCheckout][:revision].size).to eq(40)
55
53
  end
56
54
 
57
55
  it "attaches last checkouted time" do
58
- subject.call(notice)
56
+ git_last_checkout_filter.call(notice)
59
57
  expect(notice[:context][:lastCheckout][:time]).not_to be_empty
60
58
  expect(notice[:context][:lastCheckout][:time].size).to eq(25)
61
59
  end
@@ -1,5 +1,5 @@
1
1
  RSpec.describe Airbrake::Filters::GitRepositoryFilter do
2
- subject { described_class.new('.') }
2
+ subject(:git_repository_filter) { described_class.new('.') }
3
3
 
4
4
  let(:notice) do
5
5
  Airbrake::Notice.new(Airbrake::Config.new, AirbrakeTestError.new)
@@ -9,42 +9,42 @@ RSpec.describe Airbrake::Filters::GitRepositoryFilter do
9
9
  it "parses standard git version" do
10
10
  allow_any_instance_of(Kernel)
11
11
  .to receive(:`).and_return('git version 2.18.0')
12
- expect { subject }.not_to raise_error
12
+ expect { git_repository_filter }.not_to raise_error
13
13
  end
14
14
 
15
15
  it "parses release candidate git version" do
16
16
  allow_any_instance_of(Kernel)
17
17
  .to receive(:`).and_return('git version 2.21.0-rc0')
18
- expect { subject }.not_to raise_error
18
+ expect { git_repository_filter }.not_to raise_error
19
19
  end
20
20
 
21
21
  it "parses git version with brackets" do
22
22
  allow_any_instance_of(Kernel)
23
23
  .to receive(:`).and_return('git version 2.17.2 (Apple Git-113)')
24
- expect { subject }.not_to raise_error
24
+ expect { git_repository_filter }.not_to raise_error
25
25
  end
26
26
  end
27
27
 
28
28
  context "when context/repository is defined" do
29
29
  it "doesn't attach anything to context/repository" do
30
30
  notice[:context][:repository] = 'git@github.com:kyrylo/test.git'
31
- subject.call(notice)
31
+ git_repository_filter.call(notice)
32
32
  expect(notice[:context][:repository]).to eq('git@github.com:kyrylo/test.git')
33
33
  end
34
34
  end
35
35
 
36
36
  context "when .git directory doesn't exist" do
37
- subject { described_class.new('root/dir') }
37
+ git_repository_filter { described_class.new('root/dir') }
38
38
 
39
39
  it "doesn't attach anything to context/repository" do
40
- subject.call(notice)
40
+ git_repository_filter.call(notice)
41
41
  expect(notice[:context][:repository]).to be_nil
42
42
  end
43
43
  end
44
44
 
45
45
  context "when .git directory exists" do
46
46
  it "attaches context/repository" do
47
- subject.call(notice)
47
+ git_repository_filter.call(notice)
48
48
  expect(notice[:context][:repository]).to eq(
49
49
  'ssh://git@github.com/airbrake/airbrake-ruby.git',
50
50
  )
@@ -54,7 +54,7 @@ RSpec.describe Airbrake::Filters::GitRepositoryFilter do
54
54
  context "when git is not in PATH" do
55
55
  it "does not attach context/repository" do
56
56
  ENV['PATH'] = ''
57
- subject.call(notice)
57
+ git_repository_filter.call(notice)
58
58
  expect(notice[:context][:repository]).to be_nil
59
59
  end
60
60
  end