airbrake-ruby 4.1.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
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