airbrake-ruby 4.1.0 → 5.0.0

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 (94) hide show
  1. checksums.yaml +5 -5
  2. data/lib/airbrake-ruby/async_sender.rb +22 -96
  3. data/lib/airbrake-ruby/backtrace.rb +8 -7
  4. data/lib/airbrake-ruby/benchmark.rb +39 -0
  5. data/lib/airbrake-ruby/code_hunk.rb +1 -1
  6. data/lib/airbrake-ruby/config/processor.rb +84 -0
  7. data/lib/airbrake-ruby/config/validator.rb +9 -3
  8. data/lib/airbrake-ruby/config.rb +76 -20
  9. data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
  10. data/lib/airbrake-ruby/file_cache.rb +6 -0
  11. data/lib/airbrake-ruby/filter_chain.rb +16 -1
  12. data/lib/airbrake-ruby/filters/dependency_filter.rb +1 -0
  13. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
  14. data/lib/airbrake-ruby/filters/gem_root_filter.rb +1 -0
  15. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +5 -5
  16. data/lib/airbrake-ruby/filters/git_repository_filter.rb +3 -0
  17. data/lib/airbrake-ruby/filters/git_revision_filter.rb +2 -0
  18. data/lib/airbrake-ruby/filters/{keys_whitelist.rb → keys_allowlist.rb} +3 -3
  19. data/lib/airbrake-ruby/filters/{keys_blacklist.rb → keys_blocklist.rb} +3 -3
  20. data/lib/airbrake-ruby/filters/keys_filter.rb +39 -20
  21. data/lib/airbrake-ruby/filters/root_directory_filter.rb +1 -0
  22. data/lib/airbrake-ruby/filters/sql_filter.rb +30 -6
  23. data/lib/airbrake-ruby/filters/system_exit_filter.rb +1 -0
  24. data/lib/airbrake-ruby/filters/thread_filter.rb +4 -2
  25. data/lib/airbrake-ruby/grouppable.rb +12 -0
  26. data/lib/airbrake-ruby/ignorable.rb +1 -0
  27. data/lib/airbrake-ruby/inspectable.rb +2 -2
  28. data/lib/airbrake-ruby/loggable.rb +2 -2
  29. data/lib/airbrake-ruby/mergeable.rb +12 -0
  30. data/lib/airbrake-ruby/monotonic_time.rb +48 -0
  31. data/lib/airbrake-ruby/notice.rb +10 -20
  32. data/lib/airbrake-ruby/notice_notifier.rb +23 -42
  33. data/lib/airbrake-ruby/performance_breakdown.rb +52 -0
  34. data/lib/airbrake-ruby/performance_notifier.rb +126 -49
  35. data/lib/airbrake-ruby/promise.rb +1 -0
  36. data/lib/airbrake-ruby/query.rb +26 -11
  37. data/lib/airbrake-ruby/queue.rb +65 -0
  38. data/lib/airbrake-ruby/remote_settings/settings_data.rb +120 -0
  39. data/lib/airbrake-ruby/remote_settings.rb +145 -0
  40. data/lib/airbrake-ruby/request.rb +20 -6
  41. data/lib/airbrake-ruby/stashable.rb +15 -0
  42. data/lib/airbrake-ruby/stat.rb +34 -24
  43. data/lib/airbrake-ruby/sync_sender.rb +3 -2
  44. data/lib/airbrake-ruby/tdigest.rb +43 -58
  45. data/lib/airbrake-ruby/thread_pool.rb +138 -0
  46. data/lib/airbrake-ruby/timed_trace.rb +58 -0
  47. data/lib/airbrake-ruby/truncator.rb +10 -4
  48. data/lib/airbrake-ruby/version.rb +11 -1
  49. data/lib/airbrake-ruby.rb +219 -53
  50. data/spec/airbrake_spec.rb +428 -9
  51. data/spec/async_sender_spec.rb +26 -110
  52. data/spec/backtrace_spec.rb +44 -44
  53. data/spec/benchmark_spec.rb +33 -0
  54. data/spec/code_hunk_spec.rb +11 -11
  55. data/spec/config/processor_spec.rb +209 -0
  56. data/spec/config/validator_spec.rb +23 -6
  57. data/spec/config_spec.rb +77 -7
  58. data/spec/deploy_notifier_spec.rb +2 -2
  59. data/spec/{file_cache.rb → file_cache_spec.rb} +2 -4
  60. data/spec/filter_chain_spec.rb +28 -1
  61. data/spec/filters/dependency_filter_spec.rb +1 -1
  62. data/spec/filters/gem_root_filter_spec.rb +9 -9
  63. data/spec/filters/git_last_checkout_filter_spec.rb +21 -4
  64. data/spec/filters/git_repository_filter.rb +1 -1
  65. data/spec/filters/git_revision_filter_spec.rb +13 -11
  66. data/spec/filters/{keys_whitelist_spec.rb → keys_allowlist_spec.rb} +29 -28
  67. data/spec/filters/{keys_blacklist_spec.rb → keys_blocklist_spec.rb} +39 -29
  68. data/spec/filters/root_directory_filter_spec.rb +9 -9
  69. data/spec/filters/sql_filter_spec.rb +110 -55
  70. data/spec/filters/system_exit_filter_spec.rb +1 -1
  71. data/spec/filters/thread_filter_spec.rb +33 -31
  72. data/spec/fixtures/project_root/code.rb +9 -9
  73. data/spec/loggable_spec.rb +17 -0
  74. data/spec/monotonic_time_spec.rb +23 -0
  75. data/spec/{notice_notifier_spec → notice_notifier}/options_spec.rb +19 -21
  76. data/spec/notice_notifier_spec.rb +20 -80
  77. data/spec/notice_spec.rb +9 -11
  78. data/spec/performance_breakdown_spec.rb +11 -0
  79. data/spec/performance_notifier_spec.rb +360 -85
  80. data/spec/query_spec.rb +11 -0
  81. data/spec/queue_spec.rb +18 -0
  82. data/spec/remote_settings/settings_data_spec.rb +365 -0
  83. data/spec/remote_settings_spec.rb +230 -0
  84. data/spec/request_spec.rb +9 -0
  85. data/spec/response_spec.rb +8 -8
  86. data/spec/spec_helper.rb +9 -13
  87. data/spec/stashable_spec.rb +23 -0
  88. data/spec/stat_spec.rb +17 -15
  89. data/spec/sync_sender_spec.rb +14 -12
  90. data/spec/tdigest_spec.rb +6 -6
  91. data/spec/thread_pool_spec.rb +187 -0
  92. data/spec/timed_trace_spec.rb +125 -0
  93. data/spec/truncator_spec.rb +12 -12
  94. metadata +55 -18
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,6 @@
1
+ require 'simplecov'
2
+ SimpleCov.start if ENV['COVERAGE']
3
+
1
4
  require 'airbrake-ruby'
2
5
 
3
6
  require 'rspec/its'
@@ -30,7 +33,7 @@ class AirbrakeTestError < RuntimeError
30
33
 
31
34
  def initialize(*)
32
35
  super
33
- # rubocop:disable Metrics/LineLength
36
+ # rubocop:disable Layout/LineLength
34
37
  @backtrace = [
35
38
  "/home/kyrylo/code/airbrake/ruby/spec/spec_helper.rb:23:in `<top (required)>'",
36
39
  "/opt/rubies/ruby-2.2.2/lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in `require'",
@@ -44,9 +47,9 @@ class AirbrakeTestError < RuntimeError
44
47
  "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:88:in `run'",
45
48
  "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:73:in `run'",
46
49
  "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:41:in `invoke'",
47
- "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'"
50
+ "/home/kyrylo/.gem/ruby/2.2.2/gems/rspec-core-3.3.2/exe/rspec:4:in `<main>'",
48
51
  ]
49
- # rubocop:enable Metrics/LineLength
52
+ # rubocop:enable Layout/LineLength
50
53
  end
51
54
 
52
55
  # rubocop:disable Naming/AccessorMethodName
@@ -63,7 +66,7 @@ end
63
66
  class JavaAirbrakeTestError < AirbrakeTestError
64
67
  def initialize(*)
65
68
  super
66
- # rubocop:disable Metrics/LineLength
69
+ # rubocop:disable Layout/LineLength
67
70
  @backtrace = [
68
71
  "org.jruby.java.invokers.InstanceMethodInvoker.call(InstanceMethodInvoker.java:26)",
69
72
  "org.jruby.ir.interpreter.Interpreter.INTERPRET_EVAL(Interpreter.java:126)",
@@ -75,9 +78,9 @@ class JavaAirbrakeTestError < AirbrakeTestError
75
78
  "opt.rubies.jruby_minus_9_dot_0_dot_0_dot_0.bin.irb.RUBY$script(/opt/rubies/jruby-9.0.0.0/bin/irb:13)",
76
79
  "org.jruby.ir.Compiler$1.load(Compiler.java:111)",
77
80
  "org.jruby.Main.run(Main.java:225)",
78
- "org.jruby.Main.main(Main.java:197)"
81
+ "org.jruby.Main.main(Main.java:197)",
79
82
  ]
80
- # rubocop:enable Metrics/LineLength
83
+ # rubocop:enable Layout/LineLength
81
84
  end
82
85
 
83
86
  def is_a?(*)
@@ -95,10 +98,3 @@ class Ruby21Error < RuntimeError
95
98
  raise ex
96
99
  end
97
100
  end
98
-
99
- puts <<BANNER
100
- #{'#' * 80}
101
- # RUBY_VERSION: #{RUBY_VERSION}
102
- # RUBY_ENGINE: #{RUBY_ENGINE}
103
- #{'#' * 80}
104
- BANNER
@@ -0,0 +1,23 @@
1
+ RSpec.describe Airbrake::Stashable do
2
+ let(:klass) do
3
+ mod = described_class
4
+ Class.new { include(mod) }
5
+ end
6
+
7
+ describe "#stash" do
8
+ subject { klass.new }
9
+
10
+ it "returns a hash" do
11
+ expect(subject.stash).to be_a(Hash)
12
+ end
13
+
14
+ it "returns an empty hash" do
15
+ expect(subject.stash).to be_empty
16
+ end
17
+
18
+ it "remembers what was put in the stash" do
19
+ subject.stash[:foo] = 1
20
+ expect(subject.stash[:foo]).to eq(1)
21
+ end
22
+ end
23
+ end
data/spec/stat_spec.rb CHANGED
@@ -5,31 +5,33 @@ RSpec.describe Airbrake::Stat do
5
5
  'count' => 0,
6
6
  'sum' => 0.0,
7
7
  'sumsq' => 0.0,
8
- 'tdigest' => 'AAAAAkA0AAAAAAAAAAAAAA=='
8
+ 'tdigest' => 'AAAAAkA0AAAAAAAAAAAAAA==',
9
9
  )
10
10
  end
11
11
  end
12
12
 
13
- describe "#increment" do
14
- let(:start_time) { Time.new(2018, 1, 1, 0, 0, 20, 0) }
15
- let(:end_time) { Time.new(2018, 1, 1, 0, 0, 21, 0) }
13
+ describe "#increment_ms" do
14
+ before { subject.increment_ms(1000) }
16
15
 
17
- before { subject.increment(start_time, end_time) }
16
+ its(:sum) { is_expected.to eq(1000) }
17
+ its(:sumsq) { is_expected.to eq(1000000) }
18
18
 
19
- it "increments count" do
20
- expect(subject.count).to eq(1)
21
- end
22
-
23
- it "updates sum" do
24
- expect(subject.sum).to eq(1000)
19
+ it "updates tdigest" do
20
+ expect(subject.tdigest.size).to eq(1)
25
21
  end
22
+ end
26
23
 
27
- it "updates sumsq" do
28
- expect(subject.sumsq).to eq(1000000)
24
+ describe "#inspect" do
25
+ it "provides custom inspect output" do
26
+ expect(subject.inspect).to eq(
27
+ '#<struct Airbrake::Stat count=0, sum=0.0, sumsq=0.0>',
28
+ )
29
29
  end
30
+ end
30
31
 
31
- it "updates tdigest" do
32
- expect(subject.tdigest.size).to eq(1)
32
+ describe "#pretty_print" do
33
+ it "is an alias of #inspect" do
34
+ expect(subject.method(:pretty_print)).to eql(subject.method(:inspect))
33
35
  end
34
36
  end
35
37
  end
@@ -1,7 +1,7 @@
1
1
  RSpec.describe Airbrake::SyncSender do
2
2
  before do
3
3
  Airbrake::Config.instance = Airbrake::Config.new(
4
- project_id: 1, project_key: 'banana'
4
+ project_id: 1, project_key: 'banana',
5
5
  )
6
6
  end
7
7
 
@@ -17,8 +17,8 @@ RSpec.describe Airbrake::SyncSender do
17
17
  subject.send({}, promise)
18
18
  expect(
19
19
  a_request(:post, endpoint).with(
20
- headers: { 'Content-Type' => 'application/json' }
21
- )
20
+ headers: { 'Content-Type' => 'application/json' },
21
+ ),
22
22
  ).to have_been_made.once
23
23
  end
24
24
 
@@ -27,9 +27,11 @@ RSpec.describe Airbrake::SyncSender do
27
27
  expect(
28
28
  a_request(:post, endpoint).with(
29
29
  headers: {
30
- 'User-Agent' => %r{airbrake-ruby/\d+\.\d+\.\d+ Ruby/\d+\.\d+\.\d+}
31
- }
32
- )
30
+ 'User-Agent' => %r{
31
+ airbrake-ruby/\d+\.\d+\.\d+(\.rc\.\d+)?\sRuby/\d+\.\d+\.\d+
32
+ }x,
33
+ },
34
+ ),
33
35
  ).to have_been_made.once
34
36
  end
35
37
 
@@ -37,8 +39,8 @@ RSpec.describe Airbrake::SyncSender do
37
39
  subject.send({}, promise)
38
40
  expect(
39
41
  a_request(:post, endpoint).with(
40
- headers: { 'Authorization' => 'Bearer banana' }
41
- )
42
+ headers: { 'Authorization' => 'Bearer banana' },
43
+ ),
42
44
  ).to have_been_made.once
43
45
  end
44
46
 
@@ -47,7 +49,7 @@ RSpec.describe Airbrake::SyncSender do
47
49
  allow(subject).to receive(:build_https).and_return(https)
48
50
  allow(https).to receive(:request).and_raise(StandardError.new('foo'))
49
51
  expect(Airbrake::Loggable.instance).to receive(:error).with(
50
- /HTTP error: foo/
52
+ /HTTP error: foo/,
51
53
  )
52
54
  expect(subject.send({}, promise)).to be_an(Airbrake::Promise)
53
55
  expect(promise.value).to eq('error' => '**Airbrake: HTTP error: foo')
@@ -69,10 +71,10 @@ RSpec.describe Airbrake::SyncSender do
69
71
  notice = Airbrake::Notice.new(ex)
70
72
 
71
73
  expect(Airbrake::Loggable.instance).to receive(:error).with(
72
- /data was not sent/
74
+ /data was not sent/,
73
75
  )
74
76
  expect(Airbrake::Loggable.instance).to receive(:error).with(
75
- /truncation failed/
77
+ /truncation failed/,
76
78
  )
77
79
  expect(subject.send(notice, promise)).to be_an(Airbrake::Promise)
78
80
  expect(promise.value)
@@ -87,7 +89,7 @@ RSpec.describe Airbrake::SyncSender do
87
89
  stub_request(:post, endpoint).to_return(
88
90
  status: 429,
89
91
  body: '{"message":"IP is rate limited"}',
90
- headers: { 'X-RateLimit-Delay' => '1' }
92
+ headers: { 'X-RateLimit-Delay' => '1' },
91
93
  )
92
94
  end
93
95
 
data/spec/tdigest_spec.rb CHANGED
@@ -29,7 +29,7 @@ RSpec.describe Airbrake::TDigest do
29
29
  new_tdigest = described_class.from_bytes(bytes)
30
30
  # Expect some rounding error due to compression
31
31
  expect(new_tdigest.percentile(0.9).round(5)).to eq(
32
- subject.percentile(0.9).round(5)
32
+ subject.percentile(0.9).round(5),
33
33
  )
34
34
  expect(new_tdigest.as_small_bytes).to eq(bytes)
35
35
  end
@@ -126,8 +126,8 @@ RSpec.describe Airbrake::TDigest do
126
126
  113270270.27027026,
127
127
  154459459.45945945,
128
128
  123829787.23404256,
129
- 103191489.36170213
130
- ]
129
+ 103191489.36170213,
130
+ ],
131
131
  )
132
132
  end
133
133
 
@@ -166,13 +166,13 @@ RSpec.describe Airbrake::TDigest do
166
166
  it "has the parameters of the left argument (the calling tdigest)" do
167
167
  new_tdigest = subject + @other
168
168
  expect(new_tdigest.instance_variable_get(:@delta)).to eq(
169
- subject.instance_variable_get(:@delta)
169
+ subject.instance_variable_get(:@delta),
170
170
  )
171
171
  expect(new_tdigest.instance_variable_get(:@k)).to eq(
172
- subject.instance_variable_get(:@k)
172
+ subject.instance_variable_get(:@k),
173
173
  )
174
174
  expect(new_tdigest.instance_variable_get(:@cx)).to eq(
175
- subject.instance_variable_get(:@cx)
175
+ subject.instance_variable_get(:@cx),
176
176
  )
177
177
  end
178
178
 
@@ -0,0 +1,187 @@
1
+ RSpec.describe Airbrake::ThreadPool do
2
+ let(:tasks) { [] }
3
+ let(:worker_size) { 1 }
4
+ let(:queue_size) { 2 }
5
+
6
+ subject do
7
+ described_class.new(
8
+ worker_size: worker_size,
9
+ queue_size: queue_size,
10
+ block: proc { |message| tasks << message },
11
+ )
12
+ end
13
+
14
+ describe "#<<" do
15
+ it "returns true" do
16
+ retval = subject << 1
17
+ subject.close
18
+ expect(retval).to eq(true)
19
+ end
20
+
21
+ it "performs work in background" do
22
+ subject << 2
23
+ subject << 1
24
+ subject.close
25
+
26
+ expect(tasks).to eq([2, 1])
27
+ end
28
+
29
+ context "when the queue is full" do
30
+ before do
31
+ allow(subject).to receive(:backlog).and_return(queue_size)
32
+ end
33
+
34
+ subject do
35
+ described_class.new(
36
+ worker_size: 1,
37
+ queue_size: 1,
38
+ block: proc { |message| tasks << message },
39
+ )
40
+ end
41
+
42
+ it "returns false" do
43
+ retval = subject << 1
44
+ subject.close
45
+ expect(retval).to eq(false)
46
+ end
47
+
48
+ it "discards tasks" do
49
+ 200.times { subject << 1 }
50
+ subject.close
51
+
52
+ expect(tasks.size).to be_zero
53
+ end
54
+
55
+ it "logs discarded tasks" do
56
+ expect(Airbrake::Loggable.instance).to receive(:error).with(
57
+ /reached its capacity/,
58
+ ).exactly(15).times
59
+
60
+ 15.times { subject << 1 }
61
+ subject.close
62
+ end
63
+ end
64
+ end
65
+
66
+ describe "#backlog" do
67
+ let(:worker_size) { 0 }
68
+
69
+ it "returns the size of the queue" do
70
+ subject << 1
71
+ expect(subject.backlog).to eq(1)
72
+ end
73
+ end
74
+
75
+ describe "#has_workers?" do
76
+ it "returns false when the thread pool is not closed, but has 0 workers" do
77
+ subject.workers.list.each do |worker|
78
+ worker.kill.join
79
+ end
80
+ expect(subject).not_to have_workers
81
+ end
82
+
83
+ it "returns false when the thread pool is closed" do
84
+ subject.close
85
+ expect(subject).not_to have_workers
86
+ end
87
+
88
+ describe "forking behavior" do
89
+ before do
90
+ skip('fork() is unsupported on JRuby') if %w[jruby].include?(RUBY_ENGINE)
91
+ unless Process.respond_to?(:last_status)
92
+ skip('Process.last_status is unsupported on this Ruby')
93
+ end
94
+ end
95
+
96
+ it "respawns workers on fork()" do
97
+ pid = fork { expect(subject).to have_workers }
98
+ Process.wait(pid)
99
+ subject.close
100
+
101
+ expect(Process.last_status).to be_success
102
+ expect(subject).not_to have_workers
103
+ end
104
+
105
+ it "ensures that a new thread group is created per process" do
106
+ subject << 1
107
+ pid = fork { subject.has_workers? }
108
+ Process.wait(pid)
109
+ subject.close
110
+
111
+ expect(Process.last_status).to be_success
112
+ end
113
+ end
114
+ end
115
+
116
+ describe "#close" do
117
+ context "when there's no work to do" do
118
+ it "joins the spawned thread" do
119
+ workers = subject.workers.list
120
+ expect(workers).to all(be_alive)
121
+
122
+ subject.close
123
+ expect(workers).to all(be_stop)
124
+ end
125
+ end
126
+
127
+ context "when there's some work to do" do
128
+ it "logs how many tasks are left to process" do
129
+ thread_pool = described_class.new(
130
+ worker_size: 0, queue_size: 2, block: proc {},
131
+ )
132
+
133
+ expect(Airbrake::Loggable.instance).to receive(:debug).with(
134
+ /waiting to process \d+ task\(s\)/,
135
+ )
136
+ expect(Airbrake::Loggable.instance).to receive(:debug).with(/closed/)
137
+
138
+ 2.times { thread_pool << 1 }
139
+ thread_pool.close
140
+ end
141
+
142
+ it "waits until the queue gets empty" do
143
+ thread_pool = described_class.new(
144
+ worker_size: 1, queue_size: 2, block: proc {},
145
+ )
146
+
147
+ 10.times { subject << 1 }
148
+ thread_pool.close
149
+ expect(thread_pool.backlog).to be_zero
150
+ end
151
+ end
152
+
153
+ context "when it was already closed" do
154
+ it "doesn't increase the queue size" do
155
+ begin
156
+ subject.close
157
+ rescue Airbrake::Error
158
+ nil
159
+ end
160
+
161
+ expect(subject.backlog).to be_zero
162
+ end
163
+
164
+ it "raises error" do
165
+ subject.close
166
+ expect { subject.close }.to raise_error(
167
+ Airbrake::Error, 'this thread pool is closed already'
168
+ )
169
+ end
170
+ end
171
+ end
172
+
173
+ describe "#spawn_workers" do
174
+ it "spawns alive threads in an enclosed ThreadGroup" do
175
+ expect(subject.workers).to be_a(ThreadGroup)
176
+ expect(subject.workers.list).to all(be_alive)
177
+ expect(subject.workers).to be_enclosed
178
+
179
+ subject.close
180
+ end
181
+
182
+ it "spawns exactly `workers_size` workers" do
183
+ expect(subject.workers.list.size).to eq(worker_size)
184
+ subject.close
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,125 @@
1
+ RSpec.describe Airbrake::TimedTrace do
2
+ describe ".span" do
3
+ it "returns a timed trace" do
4
+ expect(described_class.span('operation') {}).to be_a(described_class)
5
+ end
6
+
7
+ it "returns a timed trace with a stopped span" do
8
+ timed_trace = described_class.span('operation') {}
9
+ expect(timed_trace.spans).to match('operation' => be > 0)
10
+ end
11
+ end
12
+
13
+ describe "#span" do
14
+ it "captures a span" do
15
+ subject.span('operation') {}
16
+ expect(subject.spans).to match('operation' => be > 0)
17
+ end
18
+ end
19
+
20
+ describe "#start_span" do
21
+ context "when called once" do
22
+ it "returns true" do
23
+ expect(subject.start_span('operation')).to eq(true)
24
+ end
25
+ end
26
+
27
+ context "when called multiple times" do
28
+ before { subject.start_span('operation') }
29
+
30
+ it "returns false" do
31
+ expect(subject.start_span('operation')).to eq(false)
32
+ end
33
+ end
34
+
35
+ context "when another span was started" do
36
+ before { subject.start_span('operation') }
37
+
38
+ it "returns true" do
39
+ expect(subject.start_span('another operation')).to eq(true)
40
+ end
41
+ end
42
+
43
+ context "when #spans was called" do
44
+ before { subject.start_span('operation') }
45
+
46
+ it "returns spans with zero values" do
47
+ expect(subject.spans).to eq('operation' => 0.0)
48
+ end
49
+ end
50
+ end
51
+
52
+ describe "#stop_span" do
53
+ context "when #start_span wasn't invoked" do
54
+ it "returns false" do
55
+ expect(subject.stop_span('operation')).to eq(false)
56
+ end
57
+ end
58
+
59
+ context "when #start_span was invoked" do
60
+ before { subject.start_span('operation') }
61
+
62
+ it "returns true" do
63
+ expect(subject.stop_span('operation')).to eq(true)
64
+ end
65
+ end
66
+
67
+ context "when multiple spans were started" do
68
+ before do
69
+ subject.start_span('operation')
70
+ subject.start_span('another operation')
71
+ end
72
+
73
+ context "and when stopping in LIFO order" do
74
+ it "returns true for all spans" do
75
+ expect(subject.stop_span('another operation')).to eq(true)
76
+ expect(subject.stop_span('operation')).to eq(true)
77
+ end
78
+ end
79
+
80
+ context "and when stopping in FIFO order" do
81
+ it "returns true for all spans" do
82
+ expect(subject.stop_span('operation')).to eq(true)
83
+ expect(subject.stop_span('another operation')).to eq(true)
84
+ end
85
+ end
86
+ end
87
+ end
88
+
89
+ describe "#spans" do
90
+ context "when no spans were captured" do
91
+ it "returns an empty hash" do
92
+ expect(subject.spans).to eq({})
93
+ end
94
+ end
95
+
96
+ context "when a span was captured" do
97
+ before do
98
+ subject.start_span('operation')
99
+ subject.stop_span('operation')
100
+ end
101
+
102
+ it "returns a Hash with the corresponding span" do
103
+ subject.stop_span('operation')
104
+ expect(subject.spans).to match('operation' => be > 0)
105
+ end
106
+ end
107
+
108
+ context "when multiple spans were captured" do
109
+ before do
110
+ subject.start_span('operation')
111
+ subject.stop_span('operation')
112
+
113
+ subject.start_span('another operation')
114
+ subject.stop_span('another operation')
115
+ end
116
+
117
+ it "returns a Hash with all spans" do
118
+ expect(subject.spans).to match(
119
+ 'operation' => be > 0,
120
+ 'another operation' => be > 0,
121
+ )
122
+ end
123
+ end
124
+ end
125
+ end
@@ -25,7 +25,7 @@ RSpec.describe Airbrake::Truncator do
25
25
  banana: multiply_by_2_max_len('a'),
26
26
  kiwi: multiply_by_2_max_len('b'),
27
27
  strawberry: 'c',
28
- shrimp: 'd'
28
+ shrimp: 'd',
29
29
  }.freeze
30
30
  end
31
31
 
@@ -34,7 +34,7 @@ RSpec.describe Airbrake::Truncator do
34
34
  expect(subject).to be_frozen
35
35
 
36
36
  expect(subject).to eq(
37
- banana: 'aaa[Truncated]', kiwi: 'bbb[Truncated]', strawberry: 'c'
37
+ banana: 'aaa[Truncated]', kiwi: 'bbb[Truncated]', strawberry: 'c',
38
38
  )
39
39
  expect(subject[:banana]).to be_frozen
40
40
  expect(subject[:kiwi]).to be_frozen
@@ -48,7 +48,7 @@ RSpec.describe Airbrake::Truncator do
48
48
  multiply_by_2_max_len('a'),
49
49
  'b',
50
50
  multiply_by_2_max_len('c'),
51
- 'd'
51
+ 'd',
52
52
  ].freeze
53
53
  end
54
54
 
@@ -69,7 +69,7 @@ RSpec.describe Airbrake::Truncator do
69
69
  multiply_by_2_max_len('a'),
70
70
  'b',
71
71
  multiply_by_2_max_len('c'),
72
- 'd'
72
+ 'd',
73
73
  ]).freeze
74
74
  end
75
75
 
@@ -78,7 +78,7 @@ RSpec.describe Airbrake::Truncator do
78
78
  expect(subject).to be_frozen
79
79
 
80
80
  expect(subject).to eq(
81
- Set.new(['aaa[Truncated]', 'b', 'ccc[Truncated]'])
81
+ Set.new(['aaa[Truncated]', 'b', 'ccc[Truncated]']),
82
82
  )
83
83
  expect(subject).to be_frozen
84
84
  end
@@ -166,7 +166,7 @@ RSpec.describe Airbrake::Truncator do
166
166
 
167
167
  it "prevents recursion" do
168
168
  expect(subject).to eq(
169
- Set.new(['[Circular]', { k: '[Circular]' }, 'aaa[Truncated]'])
169
+ Set.new(['[Circular]', { k: '[Circular]' }, 'aaa[Truncated]']),
170
170
  )
171
171
  expect(subject).to be_frozen
172
172
  end
@@ -177,13 +177,13 @@ RSpec.describe Airbrake::Truncator do
177
177
  {
178
178
  a: multiply_by_2_max_len('a'),
179
179
  b: multiply_by_2_max_len('b'),
180
- c: { d: multiply_by_2_max_len('d'), e: 'e' }
180
+ c: { d: multiply_by_2_max_len('d'), e: 'e' },
181
181
  }
182
182
  end
183
183
 
184
184
  it "truncates the long strings" do
185
185
  expect(subject).to eq(
186
- a: 'aaa[Truncated]', b: 'bbb[Truncated]', c: { d: 'ddd[Truncated]', e: 'e' }
186
+ a: 'aaa[Truncated]', b: 'bbb[Truncated]', c: { d: 'ddd[Truncated]', e: 'e' },
187
187
  )
188
188
  expect(subject).to be_frozen
189
189
  end
@@ -218,8 +218,8 @@ RSpec.describe Airbrake::Truncator do
218
218
  errors: [
219
219
  { file: 'a' },
220
220
  { file: 'a' },
221
- hashie.new.merge(file: 'bcde')
222
- ]
221
+ hashie.new.merge(file: 'bcde'),
222
+ ],
223
223
  }
224
224
  end
225
225
 
@@ -228,8 +228,8 @@ RSpec.describe Airbrake::Truncator do
228
228
  errors: [
229
229
  { file: 'a' },
230
230
  { file: 'a' },
231
- hashie.new.merge(file: 'bcd[Truncated]')
232
- ]
231
+ hashie.new.merge(file: 'bcd[Truncated]'),
232
+ ],
233
233
  )
234
234
  expect(subject).to be_frozen
235
235
  end