airbrake-ruby 5.2.0 → 6.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby/async_sender.rb +3 -1
  3. data/lib/airbrake-ruby/config.rb +3 -3
  4. data/lib/airbrake-ruby/context.rb +51 -0
  5. data/lib/airbrake-ruby/filter_chain.rb +2 -0
  6. data/lib/airbrake-ruby/filters/context_filter.rb +4 -5
  7. data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +1 -1
  8. data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +2 -2
  9. data/lib/airbrake-ruby/filters/git_repository_filter.rb +1 -1
  10. data/lib/airbrake-ruby/filters/git_revision_filter.rb +1 -1
  11. data/lib/airbrake-ruby/filters/keys_filter.rb +2 -2
  12. data/lib/airbrake-ruby/filters/sql_filter.rb +8 -8
  13. data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
  14. data/lib/airbrake-ruby/ignorable.rb +0 -2
  15. data/lib/airbrake-ruby/monotonic_time.rb +1 -1
  16. data/lib/airbrake-ruby/notice_notifier.rb +3 -4
  17. data/lib/airbrake-ruby/performance_notifier.rb +39 -40
  18. data/lib/airbrake-ruby/remote_settings/settings_data.rb +1 -1
  19. data/lib/airbrake-ruby/remote_settings.rb +26 -3
  20. data/lib/airbrake-ruby/stat.rb +1 -1
  21. data/lib/airbrake-ruby/tdigest.rb +10 -9
  22. data/lib/airbrake-ruby/thread_pool.rb +8 -6
  23. data/lib/airbrake-ruby/time_truncate.rb +2 -2
  24. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  25. data/lib/airbrake-ruby/version.rb +1 -1
  26. data/lib/airbrake-ruby.rb +24 -23
  27. data/spec/airbrake_spec.rb +139 -76
  28. data/spec/async_sender_spec.rb +10 -8
  29. data/spec/backtrace_spec.rb +13 -10
  30. data/spec/benchmark_spec.rb +5 -3
  31. data/spec/code_hunk_spec.rb +24 -15
  32. data/spec/config/processor_spec.rb +12 -4
  33. data/spec/config/validator_spec.rb +5 -2
  34. data/spec/config_spec.rb +28 -20
  35. data/spec/context_spec.rb +54 -0
  36. data/spec/deploy_notifier_spec.rb +6 -4
  37. data/spec/file_cache_spec.rb +1 -0
  38. data/spec/filter_chain_spec.rb +29 -24
  39. data/spec/filters/context_filter_spec.rb +14 -5
  40. data/spec/filters/dependency_filter_spec.rb +3 -1
  41. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  42. data/spec/filters/gem_root_filter_spec.rb +5 -2
  43. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  44. data/spec/filters/git_repository_filter.rb +9 -9
  45. data/spec/filters/git_revision_filter_spec.rb +20 -20
  46. data/spec/filters/keys_allowlist_spec.rb +25 -16
  47. data/spec/filters/keys_blocklist_spec.rb +25 -18
  48. data/spec/filters/root_directory_filter_spec.rb +3 -3
  49. data/spec/filters/sql_filter_spec.rb +26 -26
  50. data/spec/filters/system_exit_filter_spec.rb +4 -2
  51. data/spec/filters/thread_filter_spec.rb +15 -13
  52. data/spec/loggable_spec.rb +2 -2
  53. data/spec/monotonic_time_spec.rb +8 -6
  54. data/spec/nested_exception_spec.rb +46 -46
  55. data/spec/notice_notifier/options_spec.rb +23 -13
  56. data/spec/notice_notifier_spec.rb +52 -47
  57. data/spec/notice_spec.rb +6 -2
  58. data/spec/performance_notifier_spec.rb +69 -62
  59. data/spec/promise_spec.rb +38 -32
  60. data/spec/remote_settings/callback_spec.rb +27 -8
  61. data/spec/remote_settings/settings_data_spec.rb +4 -4
  62. data/spec/remote_settings_spec.rb +23 -9
  63. data/spec/response_spec.rb +34 -12
  64. data/spec/stashable_spec.rb +5 -5
  65. data/spec/stat_spec.rb +7 -5
  66. data/spec/sync_sender_spec.rb +49 -16
  67. data/spec/tdigest_spec.rb +60 -55
  68. data/spec/thread_pool_spec.rb +65 -56
  69. data/spec/time_truncate_spec.rb +23 -6
  70. data/spec/timed_trace_spec.rb +32 -30
  71. data/spec/truncator_spec.rb +72 -43
  72. metadata +54 -50
data/spec/tdigest_spec.rb CHANGED
@@ -1,16 +1,18 @@
1
1
  RSpec.describe Airbrake::TDigest do
2
+ subject(:tdigest) { described_class.new }
3
+
2
4
  describe "byte serialization" do
3
5
  it "loads serialized data" do
4
- subject.push(60, 100)
5
- 10.times { subject.push(rand * 100) }
6
- bytes = subject.as_bytes
6
+ tdigest.push(60, 100)
7
+ 10.times { tdigest.push(rand * 100) }
8
+ bytes = tdigest.as_bytes
7
9
  new_tdigest = described_class.from_bytes(bytes)
8
- expect(new_tdigest.percentile(0.9)).to eq(subject.percentile(0.9))
10
+ expect(new_tdigest.percentile(0.9)).to eq(tdigest.percentile(0.9))
9
11
  expect(new_tdigest.as_bytes).to eq(bytes)
10
12
  end
11
13
 
12
14
  it "handles zero size" do
13
- bytes = subject.as_bytes
15
+ bytes = tdigest.as_bytes
14
16
  expect(described_class.from_bytes(bytes).size).to be_zero
15
17
  end
16
18
 
@@ -24,65 +26,64 @@ RSpec.describe Airbrake::TDigest do
24
26
 
25
27
  describe "small byte serialization" do
26
28
  it "loads serialized data" do
27
- 10.times { subject.push(10) }
28
- bytes = subject.as_small_bytes
29
+ 10.times { tdigest.push(10) }
30
+ bytes = tdigest.as_small_bytes
29
31
  new_tdigest = described_class.from_bytes(bytes)
30
32
  # Expect some rounding error due to compression
31
33
  expect(new_tdigest.percentile(0.9).round(5)).to eq(
32
- subject.percentile(0.9).round(5),
34
+ tdigest.percentile(0.9).round(5),
33
35
  )
34
36
  expect(new_tdigest.as_small_bytes).to eq(bytes)
35
37
  end
36
38
 
37
39
  it "handles zero size" do
38
- bytes = subject.as_small_bytes
40
+ bytes = tdigest.as_small_bytes
39
41
  expect(described_class.from_bytes(bytes).size).to be_zero
40
42
  end
41
43
  end
42
44
 
43
45
  describe "JSON serialization" do
44
46
  it "loads serialized data" do
45
- subject.push(60, 100)
46
- json = subject.as_json
47
+ tdigest.push(60, 100)
48
+ json = tdigest.as_json
47
49
  new_tdigest = described_class.from_json(json)
48
- expect(new_tdigest.percentile(0.9)).to eq(subject.percentile(0.9))
50
+ expect(new_tdigest.percentile(0.9)).to eq(tdigest.percentile(0.9))
49
51
  end
50
52
  end
51
53
 
52
54
  describe "#percentile" do
53
55
  it "returns nil if empty" do
54
- expect(subject.percentile(0.90)).to be_nil # This should not crash
56
+ expect(tdigest.percentile(0.90)).to be_nil # This should not crash
55
57
  end
56
58
 
57
59
  it "raises ArgumentError of input not between 0 and 1" do
58
- expect { subject.percentile(1.1) }.to raise_error(ArgumentError)
60
+ expect { tdigest.percentile(1.1) }.to raise_error(ArgumentError)
59
61
  end
60
62
 
61
63
  describe "with only single value" do
62
64
  it "returns the value" do
63
- subject.push(60, 100)
64
- expect(subject.percentile(0.90)).to eq(60)
65
+ tdigest.push(60, 100)
66
+ expect(tdigest.percentile(0.90)).to eq(60)
65
67
  end
66
68
 
67
69
  it "returns 0 for all percentiles when only 0 present" do
68
- subject.push(0)
69
- expect(subject.percentile([0.0, 0.5, 1.0])).to eq([0, 0, 0])
70
+ tdigest.push(0)
71
+ expect(tdigest.percentile([0.0, 0.5, 1.0])).to eq([0, 0, 0])
70
72
  end
71
73
  end
72
74
 
73
75
  describe "with alot of uniformly distributed points" do
74
76
  it "has minimal error" do
75
77
  seed = srand(1234) # Makes the values a proper fixture
76
- N = 100_000
77
78
  maxerr = 0
78
- values = Array.new(N).map { rand }
79
+ values = Array.new(100_000).map { rand }
79
80
  srand(seed)
80
81
 
81
- subject.push(values)
82
- subject.compress!
82
+ tdigest.push(values)
83
+ tdigest.compress!
83
84
 
84
85
  0.step(1, 0.1).each do |i|
85
- q = subject.percentile(i)
86
+ q = tdigest.percentile(i)
86
87
  maxerr = [maxerr, (i - q).abs].max
87
88
  end
88
89
 
@@ -93,7 +94,7 @@ RSpec.describe Airbrake::TDigest do
93
94
 
94
95
  describe "#push" do
95
96
  it "calls _cumulate so won't crash because of uninitialized mean_cumn" do
96
- subject.push(
97
+ tdigest.push(
97
98
  [
98
99
  125000000.0,
99
100
  104166666.66666666,
@@ -132,59 +133,62 @@ RSpec.describe Airbrake::TDigest do
132
133
  end
133
134
 
134
135
  it "does not blow up if data comes in sorted" do
135
- subject.push(0..10_000)
136
- expect(subject.centroids.size).to be < 5_000
137
- subject.compress!
138
- expect(subject.centroids.size).to be < 1_000
136
+ tdigest.push(0..10_000)
137
+ expect(tdigest.centroids.size).to be < 5_000
138
+ tdigest.compress!
139
+ expect(tdigest.centroids.size).to be < 1_000
139
140
  end
140
141
  end
141
142
 
142
143
  describe "#size" do
143
144
  it "reports the number of observations" do
144
145
  n = 10_000
145
- n.times { subject.push(rand) }
146
- subject.compress!
147
- expect(subject.size).to eq(n)
146
+ n.times { tdigest.push(rand) }
147
+ tdigest.compress!
148
+ expect(tdigest.size).to eq(n)
148
149
  end
149
150
  end
150
151
 
151
152
  describe "#+" do
152
153
  it "works with empty tdigests" do
153
154
  other = described_class.new(0.001, 50, 1.2)
154
- expect((subject + other).centroids.size).to eq(0)
155
+ expect((tdigest + other).centroids.size).to eq(0)
155
156
  end
156
157
 
157
158
  describe "adding two tdigests" do
159
+ let(:other) { described_class.new(0.001, 50, 1.2) }
160
+
158
161
  before do
159
- @other = described_class.new(0.001, 50, 1.2)
160
- [subject, @other].each do |td|
162
+ [tdigest, other].each do |td|
161
163
  td.push(60, 100)
162
164
  10.times { td.push(rand * 100) }
163
165
  end
164
166
  end
165
167
 
168
+ # rubocop:disable RSpec/MultipleExpectations
166
169
  it "has the parameters of the left argument (the calling tdigest)" do
167
- new_tdigest = subject + @other
170
+ new_tdigest = tdigest + other
168
171
  expect(new_tdigest.instance_variable_get(:@delta)).to eq(
169
- subject.instance_variable_get(:@delta),
172
+ tdigest.instance_variable_get(:@delta),
170
173
  )
171
174
  expect(new_tdigest.instance_variable_get(:@k)).to eq(
172
- subject.instance_variable_get(:@k),
175
+ tdigest.instance_variable_get(:@k),
173
176
  )
174
177
  expect(new_tdigest.instance_variable_get(:@cx)).to eq(
175
- subject.instance_variable_get(:@cx),
178
+ tdigest.instance_variable_get(:@cx),
176
179
  )
177
180
  end
181
+ # rubocop:enable RSpec/MultipleExpectations
178
182
 
179
183
  it "returns a tdigest with less than or equal centroids" do
180
- new_tdigest = subject + @other
184
+ new_tdigest = tdigest + other
181
185
  expect(new_tdigest.centroids.size)
182
- .to be <= subject.centroids.size + @other.centroids.size
186
+ .to be <= tdigest.centroids.size + other.centroids.size
183
187
  end
184
188
 
185
189
  it "has the size of the two digests combined" do
186
- new_tdigest = subject + @other
187
- expect(new_tdigest.size).to eq(subject.size + @other.size)
190
+ new_tdigest = tdigest + other
191
+ expect(new_tdigest.size).to eq(tdigest.size + other.size)
188
192
  end
189
193
  end
190
194
  end
@@ -192,14 +196,15 @@ RSpec.describe Airbrake::TDigest do
192
196
  describe "#merge!" do
193
197
  it "works with empty tdigests" do
194
198
  other = described_class.new(0.001, 50, 1.2)
195
- subject.merge!(other)
196
- expect(subject.centroids.size).to be_zero
199
+ tdigest.merge!(other)
200
+ expect(tdigest.centroids.size).to be_zero
197
201
  end
198
202
 
199
203
  describe "with populated tdigests" do
204
+ let(:other) { described_class.new(0.001, 50, 1.2) }
205
+
200
206
  before do
201
- @other = described_class.new(0.001, 50, 1.2)
202
- [subject, @other].each do |td|
207
+ [tdigest, other].each do |td|
203
208
  td.push(60, 100)
204
209
  10.times { td.push(rand * 100) }
205
210
  end
@@ -207,23 +212,23 @@ RSpec.describe Airbrake::TDigest do
207
212
 
208
213
  it "has the parameters of the calling tdigest" do
209
214
  vars = %i[@delta @k @cx]
210
- expected = Hash[vars.map { |v| [v, subject.instance_variable_get(v)] }]
211
- subject.merge!(@other)
215
+ expected = vars.map { |v| [v, tdigest.instance_variable_get(v)] }.to_h
216
+ tdigest.merge!(other)
212
217
  vars.each do |v|
213
- expect(subject.instance_variable_get(v)).to eq(expected[v])
218
+ expect(tdigest.instance_variable_get(v)).to eq(expected[v])
214
219
  end
215
220
  end
216
221
 
217
222
  it "returns a tdigest with less than or equal centroids" do
218
- combined_size = subject.centroids.size + @other.centroids.size
219
- subject.merge!(@other)
220
- expect(subject.centroids.size).to be <= combined_size
223
+ combined_size = tdigest.centroids.size + other.centroids.size
224
+ tdigest.merge!(other)
225
+ expect(tdigest.centroids.size).to be <= combined_size
221
226
  end
222
227
 
223
228
  it "has the size of the two digests combined" do
224
- combined_size = subject.size + @other.size
225
- subject.merge!(@other)
226
- expect(subject.size).to eq(combined_size)
229
+ combined_size = tdigest.size + other.size
230
+ tdigest.merge!(other)
231
+ expect(tdigest.size).to eq(combined_size)
227
232
  end
228
233
  end
229
234
  end
@@ -1,9 +1,5 @@
1
1
  RSpec.describe Airbrake::ThreadPool do
2
- let(:tasks) { [] }
3
- let(:worker_size) { 1 }
4
- let(:queue_size) { 2 }
5
-
6
- subject do
2
+ subject(:thread_pool) do
7
3
  described_class.new(
8
4
  worker_size: worker_size,
9
5
  queue_size: queue_size,
@@ -11,27 +7,27 @@ RSpec.describe Airbrake::ThreadPool do
11
7
  )
12
8
  end
13
9
 
10
+ let(:tasks) { [] }
11
+ let(:worker_size) { 1 }
12
+ let(:queue_size) { 2 }
13
+
14
14
  describe "#<<" do
15
15
  it "returns true" do
16
- retval = subject << 1
17
- subject.close
16
+ retval = thread_pool << 1
17
+ thread_pool.close
18
18
  expect(retval).to eq(true)
19
19
  end
20
20
 
21
21
  it "performs work in background" do
22
- subject << 2
23
- subject << 1
24
- subject.close
22
+ thread_pool << 2
23
+ thread_pool << 1
24
+ thread_pool.close
25
25
 
26
26
  expect(tasks).to eq([2, 1])
27
27
  end
28
28
 
29
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
30
+ subject(:full_thread_pool) do
35
31
  described_class.new(
36
32
  worker_size: 1,
37
33
  queue_size: 1,
@@ -39,26 +35,33 @@ RSpec.describe Airbrake::ThreadPool do
39
35
  )
40
36
  end
41
37
 
38
+ before do
39
+ # rubocop:disable RSpec/SubjectStub
40
+ allow(full_thread_pool).to receive(:backlog).and_return(queue_size)
41
+ # rubocop:enable RSpec/SubjectStub
42
+ end
43
+
42
44
  it "returns false" do
43
- retval = subject << 1
44
- subject.close
45
+ retval = full_thread_pool << 1
46
+ full_thread_pool.close
45
47
  expect(retval).to eq(false)
46
48
  end
47
49
 
48
50
  it "discards tasks" do
49
- 200.times { subject << 1 }
50
- subject.close
51
+ 200.times { full_thread_pool << 1 }
52
+ full_thread_pool.close
51
53
 
52
54
  expect(tasks.size).to be_zero
53
55
  end
54
56
 
55
57
  it "logs discarded tasks" do
56
- expect(Airbrake::Loggable.instance).to receive(:error).with(
57
- /reached its capacity/,
58
- ).exactly(15).times
58
+ allow(Airbrake::Loggable.instance).to receive(:info)
59
+
60
+ 15.times { full_thread_pool << 1 }
61
+ full_thread_pool.close
59
62
 
60
- 15.times { subject << 1 }
61
- subject.close
63
+ expect(Airbrake::Loggable.instance)
64
+ .to have_received(:info).exactly(15).times
62
65
  end
63
66
  end
64
67
  end
@@ -67,22 +70,22 @@ RSpec.describe Airbrake::ThreadPool do
67
70
  let(:worker_size) { 0 }
68
71
 
69
72
  it "returns the size of the queue" do
70
- subject << 1
71
- expect(subject.backlog).to eq(1)
73
+ thread_pool << 1
74
+ expect(thread_pool.backlog).to eq(1)
72
75
  end
73
76
  end
74
77
 
75
78
  describe "#has_workers?" do
76
79
  it "returns false when the thread pool is not closed, but has 0 workers" do
77
- subject.workers.list.each do |worker|
80
+ thread_pool.workers.list.each do |worker|
78
81
  worker.kill.join
79
82
  end
80
- expect(subject).not_to have_workers
83
+ expect(thread_pool).not_to have_workers
81
84
  end
82
85
 
83
86
  it "returns false when the thread pool is closed" do
84
- subject.close
85
- expect(subject).not_to have_workers
87
+ thread_pool.close
88
+ expect(thread_pool).not_to have_workers
86
89
  end
87
90
 
88
91
  describe "forking behavior" do
@@ -93,20 +96,22 @@ RSpec.describe Airbrake::ThreadPool do
93
96
  end
94
97
  end
95
98
 
99
+ # rubocop:disable RSpec/MultipleExpectations
96
100
  it "respawns workers on fork()" do
97
- pid = fork { expect(subject).to have_workers }
101
+ pid = fork { expect(thread_pool).to have_workers }
98
102
  Process.wait(pid)
99
- subject.close
103
+ thread_pool.close
100
104
 
101
105
  expect(Process.last_status).to be_success
102
- expect(subject).not_to have_workers
106
+ expect(thread_pool).not_to have_workers
103
107
  end
108
+ # rubocop:enable RSpec/MultipleExpectations
104
109
 
105
110
  it "ensures that a new thread group is created per process" do
106
- subject << 1
107
- pid = fork { subject.has_workers? }
111
+ thread_pool << 1
112
+ pid = fork { thread_pool.has_workers? }
108
113
  Process.wait(pid)
109
- subject.close
114
+ thread_pool.close
110
115
 
111
116
  expect(Process.last_status).to be_success
112
117
  end
@@ -116,27 +121,29 @@ RSpec.describe Airbrake::ThreadPool do
116
121
  describe "#close" do
117
122
  context "when there's no work to do" do
118
123
  it "joins the spawned thread" do
119
- workers = subject.workers.list
124
+ workers = thread_pool.workers.list
120
125
  expect(workers).to all(be_alive)
121
126
 
122
- subject.close
127
+ thread_pool.close
123
128
  expect(workers).to all(be_stop)
124
129
  end
125
130
  end
126
131
 
127
132
  context "when there's some work to do" do
128
133
  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
- )
134
+ allow(Airbrake::Loggable.instance).to receive(:debug)
132
135
 
133
- expect(Airbrake::Loggable.instance).to receive(:debug).with(
134
- /waiting to process \d+ task\(s\)/,
136
+ thread_pool = described_class.new(
137
+ name: 'foo', worker_size: 0, queue_size: 2, block: proc {},
135
138
  )
136
- expect(Airbrake::Loggable.instance).to receive(:debug).with(/closed/)
137
139
 
138
140
  2.times { thread_pool << 1 }
139
141
  thread_pool.close
142
+
143
+ expect(Airbrake::Loggable.instance).to have_received(:debug).with(
144
+ /waiting to process \d+ task\(s\)/,
145
+ )
146
+ expect(Airbrake::Loggable.instance).to have_received(:debug).with(/foo.+closed/)
140
147
  end
141
148
 
142
149
  it "waits until the queue gets empty" do
@@ -144,7 +151,7 @@ RSpec.describe Airbrake::ThreadPool do
144
151
  worker_size: 1, queue_size: 2, block: proc {},
145
152
  )
146
153
 
147
- 10.times { subject << 1 }
154
+ 10.times { thread_pool << 1 }
148
155
  thread_pool.close
149
156
  expect(thread_pool.backlog).to be_zero
150
157
  end
@@ -153,17 +160,17 @@ RSpec.describe Airbrake::ThreadPool do
153
160
  context "when it was already closed" do
154
161
  it "doesn't increase the queue size" do
155
162
  begin
156
- subject.close
163
+ thread_pool.close
157
164
  rescue Airbrake::Error
158
165
  nil
159
166
  end
160
167
 
161
- expect(subject.backlog).to be_zero
168
+ expect(thread_pool.backlog).to be_zero
162
169
  end
163
170
 
164
171
  it "raises error" do
165
- subject.close
166
- expect { subject.close }.to raise_error(
172
+ thread_pool.close
173
+ expect { thread_pool.close }.to raise_error(
167
174
  Airbrake::Error, 'this thread pool is closed already'
168
175
  )
169
176
  end
@@ -171,17 +178,19 @@ RSpec.describe Airbrake::ThreadPool do
171
178
  end
172
179
 
173
180
  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
181
+ after { thread_pool.close }
182
+
183
+ it "spawns an enclosed thread group" do
184
+ expect(thread_pool.workers).to be_a(ThreadGroup)
185
+ expect(thread_pool.workers).to be_enclosed
186
+ end
178
187
 
179
- subject.close
188
+ it "spawns threads that are alive" do
189
+ expect(thread_pool.workers.list).to all(be_alive)
180
190
  end
181
191
 
182
192
  it "spawns exactly `workers_size` workers" do
183
- expect(subject.workers.list.size).to eq(worker_size)
184
- subject.close
193
+ expect(thread_pool.workers.list.size).to eq(worker_size)
185
194
  end
186
195
  end
187
196
  end
@@ -1,13 +1,30 @@
1
1
  RSpec.describe Airbrake::TimeTruncate do
2
+ time = Time.new(2018, 1, 1, 0, 0, 20, 0)
3
+ time_with_zone = Time.new(2018, 1, 1, 0, 0, 20, '-05:00')
4
+
2
5
  describe "#utc_truncate_minutes" do
3
- it "truncates time to the floor minute and returns an RFC3339 timestamp" do
4
- time = Time.new(2018, 1, 1, 0, 0, 20, 0)
5
- expect(subject.utc_truncate_minutes(time)).to eq('2018-01-01T00:00:00+00:00')
6
+ shared_examples 'time conversion' do |t|
7
+ it "truncates the time to the floor minute and returns an RFC3339 timestamp" do
8
+ expect(described_class.utc_truncate_minutes(t))
9
+ .to eq('2018-01-01T00:00:00+00:00')
10
+ end
11
+
12
+ it "converts time with zone to UTC" do
13
+ expect(described_class.utc_truncate_minutes(time_with_zone))
14
+ .to eq('2018-01-01T05:00:00+00:00')
15
+ end
16
+ end
17
+
18
+ context "when the time argument is a Time object" do
19
+ include_examples 'time conversion', time
20
+ end
21
+
22
+ context "when the time argument is a Float" do
23
+ include_examples 'time conversion', time.to_f
6
24
  end
7
25
 
8
- it "converts time with zone to UTC" do
9
- time = Time.new(2018, 1, 1, 0, 0, 20, '-05:00')
10
- expect(subject.utc_truncate_minutes(time)).to eq('2018-01-01T05:00:00+00:00')
26
+ context "when the time argument is an Integer" do
27
+ include_examples 'time conversion', time.to_i
11
28
  end
12
29
  end
13
30
  end
@@ -1,50 +1,52 @@
1
1
  RSpec.describe Airbrake::TimedTrace do
2
+ subject(:timed_trace) { described_class.new }
3
+
2
4
  describe ".span" do
3
5
  it "returns a timed trace" do
4
- expect(described_class.span('operation') {}).to be_a(described_class)
6
+ expect(described_class.span('operation') { anything }).to be_a(described_class)
5
7
  end
6
8
 
7
9
  it "returns a timed trace with a stopped span" do
8
- timed_trace = described_class.span('operation') {}
10
+ timed_trace = described_class.span('operation') { anything }
9
11
  expect(timed_trace.spans).to match('operation' => be > 0)
10
12
  end
11
13
  end
12
14
 
13
15
  describe "#span" do
14
16
  it "captures a span" do
15
- subject.span('operation') {}
16
- expect(subject.spans).to match('operation' => be > 0)
17
+ timed_trace.span('operation') { anything }
18
+ expect(timed_trace.spans).to match('operation' => be > 0)
17
19
  end
18
20
  end
19
21
 
20
22
  describe "#start_span" do
21
23
  context "when called once" do
22
24
  it "returns true" do
23
- expect(subject.start_span('operation')).to eq(true)
25
+ expect(timed_trace.start_span('operation')).to eq(true)
24
26
  end
25
27
  end
26
28
 
27
29
  context "when called multiple times" do
28
- before { subject.start_span('operation') }
30
+ before { timed_trace.start_span('operation') }
29
31
 
30
32
  it "returns false" do
31
- expect(subject.start_span('operation')).to eq(false)
33
+ expect(timed_trace.start_span('operation')).to eq(false)
32
34
  end
33
35
  end
34
36
 
35
37
  context "when another span was started" do
36
- before { subject.start_span('operation') }
38
+ before { timed_trace.start_span('operation') }
37
39
 
38
40
  it "returns true" do
39
- expect(subject.start_span('another operation')).to eq(true)
41
+ expect(timed_trace.start_span('another operation')).to eq(true)
40
42
  end
41
43
  end
42
44
 
43
45
  context "when #spans was called" do
44
- before { subject.start_span('operation') }
46
+ before { timed_trace.start_span('operation') }
45
47
 
46
48
  it "returns spans with zero values" do
47
- expect(subject.spans).to eq('operation' => 0.0)
49
+ expect(timed_trace.spans).to eq('operation' => 0.0)
48
50
  end
49
51
  end
50
52
  end
@@ -52,35 +54,35 @@ RSpec.describe Airbrake::TimedTrace do
52
54
  describe "#stop_span" do
53
55
  context "when #start_span wasn't invoked" do
54
56
  it "returns false" do
55
- expect(subject.stop_span('operation')).to eq(false)
57
+ expect(timed_trace.stop_span('operation')).to eq(false)
56
58
  end
57
59
  end
58
60
 
59
61
  context "when #start_span was invoked" do
60
- before { subject.start_span('operation') }
62
+ before { timed_trace.start_span('operation') }
61
63
 
62
64
  it "returns true" do
63
- expect(subject.stop_span('operation')).to eq(true)
65
+ expect(timed_trace.stop_span('operation')).to eq(true)
64
66
  end
65
67
  end
66
68
 
67
69
  context "when multiple spans were started" do
68
70
  before do
69
- subject.start_span('operation')
70
- subject.start_span('another operation')
71
+ timed_trace.start_span('operation')
72
+ timed_trace.start_span('another operation')
71
73
  end
72
74
 
73
75
  context "and when stopping in LIFO order" do
74
76
  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
+ expect(timed_trace.stop_span('another operation')).to eq(true)
78
+ expect(timed_trace.stop_span('operation')).to eq(true)
77
79
  end
78
80
  end
79
81
 
80
82
  context "and when stopping in FIFO order" do
81
83
  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
+ expect(timed_trace.stop_span('operation')).to eq(true)
85
+ expect(timed_trace.stop_span('another operation')).to eq(true)
84
86
  end
85
87
  end
86
88
  end
@@ -89,33 +91,33 @@ RSpec.describe Airbrake::TimedTrace do
89
91
  describe "#spans" do
90
92
  context "when no spans were captured" do
91
93
  it "returns an empty hash" do
92
- expect(subject.spans).to eq({})
94
+ expect(timed_trace.spans).to eq({})
93
95
  end
94
96
  end
95
97
 
96
98
  context "when a span was captured" do
97
99
  before do
98
- subject.start_span('operation')
99
- subject.stop_span('operation')
100
+ timed_trace.start_span('operation')
101
+ timed_trace.stop_span('operation')
100
102
  end
101
103
 
102
104
  it "returns a Hash with the corresponding span" do
103
- subject.stop_span('operation')
104
- expect(subject.spans).to match('operation' => be > 0)
105
+ timed_trace.stop_span('operation')
106
+ expect(timed_trace.spans).to match('operation' => be > 0)
105
107
  end
106
108
  end
107
109
 
108
110
  context "when multiple spans were captured" do
109
111
  before do
110
- subject.start_span('operation')
111
- subject.stop_span('operation')
112
+ timed_trace.start_span('operation')
113
+ timed_trace.stop_span('operation')
112
114
 
113
- subject.start_span('another operation')
114
- subject.stop_span('another operation')
115
+ timed_trace.start_span('another operation')
116
+ timed_trace.stop_span('another operation')
115
117
  end
116
118
 
117
119
  it "returns a Hash with all spans" do
118
- expect(subject.spans).to match(
120
+ expect(timed_trace.spans).to match(
119
121
  'operation' => be > 0,
120
122
  'another operation' => be > 0,
121
123
  )