airbrake-ruby 5.2.0-java → 5.2.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/airbrake-ruby.rb +3 -2
  3. data/lib/airbrake-ruby/async_sender.rb +3 -1
  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 +1 -1
  9. data/lib/airbrake-ruby/filters/git_revision_filter.rb +1 -1
  10. data/lib/airbrake-ruby/filters/keys_filter.rb +2 -2
  11. data/lib/airbrake-ruby/filters/sql_filter.rb +2 -2
  12. data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
  13. data/lib/airbrake-ruby/ignorable.rb +0 -2
  14. data/lib/airbrake-ruby/notice_notifier.rb +3 -4
  15. data/lib/airbrake-ruby/performance_notifier.rb +1 -2
  16. data/lib/airbrake-ruby/remote_settings/settings_data.rb +1 -1
  17. data/lib/airbrake-ruby/tdigest.rb +7 -6
  18. data/lib/airbrake-ruby/thread_pool.rb +5 -3
  19. data/lib/airbrake-ruby/timed_trace.rb +1 -3
  20. data/lib/airbrake-ruby/version.rb +1 -1
  21. data/spec/airbrake_spec.rb +139 -76
  22. data/spec/async_sender_spec.rb +10 -8
  23. data/spec/backtrace_spec.rb +13 -10
  24. data/spec/benchmark_spec.rb +5 -3
  25. data/spec/code_hunk_spec.rb +24 -15
  26. data/spec/config/processor_spec.rb +12 -4
  27. data/spec/config/validator_spec.rb +5 -2
  28. data/spec/config_spec.rb +24 -16
  29. data/spec/context_spec.rb +54 -0
  30. data/spec/deploy_notifier_spec.rb +6 -4
  31. data/spec/file_cache_spec.rb +1 -0
  32. data/spec/filter_chain_spec.rb +29 -24
  33. data/spec/filters/context_filter_spec.rb +14 -5
  34. data/spec/filters/dependency_filter_spec.rb +3 -1
  35. data/spec/filters/exception_attributes_filter_spec.rb +5 -3
  36. data/spec/filters/gem_root_filter_spec.rb +5 -2
  37. data/spec/filters/git_last_checkout_filter_spec.rb +10 -12
  38. data/spec/filters/git_repository_filter.rb +9 -9
  39. data/spec/filters/git_revision_filter_spec.rb +19 -19
  40. data/spec/filters/keys_allowlist_spec.rb +25 -16
  41. data/spec/filters/keys_blocklist_spec.rb +25 -18
  42. data/spec/filters/root_directory_filter_spec.rb +3 -3
  43. data/spec/filters/sql_filter_spec.rb +26 -26
  44. data/spec/filters/system_exit_filter_spec.rb +4 -2
  45. data/spec/filters/thread_filter_spec.rb +15 -13
  46. data/spec/loggable_spec.rb +2 -2
  47. data/spec/monotonic_time_spec.rb +8 -6
  48. data/spec/nested_exception_spec.rb +46 -46
  49. data/spec/notice_notifier/options_spec.rb +23 -13
  50. data/spec/notice_notifier_spec.rb +52 -47
  51. data/spec/notice_spec.rb +6 -2
  52. data/spec/performance_notifier_spec.rb +67 -60
  53. data/spec/promise_spec.rb +38 -32
  54. data/spec/remote_settings/callback_spec.rb +27 -8
  55. data/spec/remote_settings/settings_data_spec.rb +4 -4
  56. data/spec/remote_settings_spec.rb +18 -8
  57. data/spec/response_spec.rb +34 -12
  58. data/spec/stashable_spec.rb +5 -5
  59. data/spec/stat_spec.rb +7 -5
  60. data/spec/sync_sender_spec.rb +49 -16
  61. data/spec/tdigest_spec.rb +60 -55
  62. data/spec/thread_pool_spec.rb +65 -55
  63. data/spec/time_truncate_spec.rb +4 -2
  64. data/spec/timed_trace_spec.rb +32 -30
  65. data/spec/truncator_spec.rb +72 -43
  66. metadata +51 -48
@@ -13,18 +13,20 @@ RSpec.describe Airbrake::AsyncSender do
13
13
  end
14
14
 
15
15
  describe "#send" do
16
+ subject(:async_sender) { described_class.new }
17
+
16
18
  context "when sender has the capacity to send" do
17
19
  it "sends notices to Airbrake" do
18
- 2.times { subject.send(notice, Airbrake::Promise.new) }
19
- subject.close
20
+ 2.times { async_sender.send(notice, Airbrake::Promise.new) }
21
+ async_sender.close
20
22
 
21
23
  expect(a_request(:post, endpoint)).to have_been_made.twice
22
24
  end
23
25
 
24
26
  it "returns a resolved promise" do
25
27
  promise = Airbrake::Promise.new
26
- subject.send(notice, promise)
27
- subject.close
28
+ async_sender.send(notice, promise)
29
+ async_sender.close
28
30
 
29
31
  expect(promise).to be_resolved
30
32
  end
@@ -40,8 +42,8 @@ RSpec.describe Airbrake::AsyncSender do
40
42
  end
41
43
 
42
44
  it "doesn't send the exceeded notices to Airbrake" do
43
- 15.times { subject.send(notice, Airbrake::Promise.new) }
44
- subject.close
45
+ 15.times { async_sender.send(notice, Airbrake::Promise.new) }
46
+ async_sender.close
45
47
 
46
48
  expect(a_request(:post, endpoint)).not_to have_been_made
47
49
  end
@@ -49,9 +51,9 @@ RSpec.describe Airbrake::AsyncSender do
49
51
  it "returns a rejected promise" do
50
52
  promise = nil
51
53
  15.times do
52
- promise = subject.send(notice, Airbrake::Promise.new)
54
+ promise = async_sender.send(notice, Airbrake::Promise.new)
53
55
  end
54
- subject.close
56
+ async_sender.close
55
57
 
56
58
  expect(promise).to be_rejected
57
59
  expect(promise.value).to eq(
@@ -177,10 +177,13 @@ RSpec.describe Airbrake::Backtrace do
177
177
  end
178
178
 
179
179
  it "logs frames that cannot be parsed" do
180
- expect(Airbrake::Loggable.instance).to receive(:error).with(
180
+ allow(Airbrake::Loggable.instance).to receive(:error)
181
+
182
+ described_class.parse(ex)
183
+
184
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
181
185
  /can't parse 'a b c 1 23 321 .rb'/,
182
186
  )
183
- described_class.parse(ex)
184
187
  end
185
188
  end
186
189
 
@@ -298,9 +301,9 @@ RSpec.describe Airbrake::Backtrace do
298
301
  it "attaches code to those frames files of which match root_directory" do
299
302
  ex = RuntimeError.new
300
303
  backtrace = [
301
- project_root_path('code.rb') + ":94:in `to_json'",
302
- fixture_path('notroot.txt' + ":3:in `pineapple'"),
303
- project_root_path('vendor/bundle/ignored_file.rb') + ":2:in `ignore_me'",
304
+ "#{project_root_path('code.rb')}:94:in `to_json'",
305
+ fixture_path("notroot.txt:3:in `pineapple'"),
306
+ "#{project_root_path('vendor/bundle/ignored_file.rb')}:2:in `ignore_me'",
304
307
  ]
305
308
  ex.set_backtrace(backtrace)
306
309
  expect(described_class.parse(ex)).to eq(parsed_backtrace)
@@ -335,7 +338,7 @@ RSpec.describe Airbrake::Backtrace do
335
338
 
336
339
  it "attaches code to those frames files of which match root_directory" do
337
340
  ex = RuntimeError.new
338
- ex.set_backtrace([project_root_path('code.rb') + ":94:in `to_json'"])
341
+ ex.set_backtrace(["#{project_root_path('code.rb')}:94:in `to_json'"])
339
342
  expect(described_class.parse(ex)).to eq(parsed_backtrace)
340
343
  end
341
344
  end
@@ -387,9 +390,9 @@ RSpec.describe Airbrake::Backtrace do
387
390
  it "attaches code to first N frames" do
388
391
  ex = RuntimeError.new
389
392
  backtrace = [
390
- project_root_path('code.rb') + ":94:in `to_json'",
391
- project_root_path('code.rb') + ":95:in `to_json'",
392
- project_root_path('code.rb') + ":96:in `to_json'",
393
+ "#{project_root_path('code.rb')}:94:in `to_json'",
394
+ "#{project_root_path('code.rb')}:95:in `to_json'",
395
+ "#{project_root_path('code.rb')}:96:in `to_json'",
393
396
  ]
394
397
  ex.set_backtrace(backtrace)
395
398
  expect(described_class.parse(ex)).to eq(parsed_backtrace)
@@ -417,7 +420,7 @@ RSpec.describe Airbrake::Backtrace do
417
420
 
418
421
  it "doesn't attach code to frames" do
419
422
  ex = RuntimeError.new
420
- backtrace = [project_root_path('code.rb') + ":94:in `to_json'"]
423
+ backtrace = ["#{project_root_path('code.rb')}:94:in `to_json'"]
421
424
  ex.set_backtrace(backtrace)
422
425
  expect(described_class.parse(ex)).to eq(parsed_backtrace)
423
426
  end
@@ -1,4 +1,6 @@
1
1
  RSpec.describe Airbrake::Benchmark do
2
+ subject(:benchmark) { described_class.new }
3
+
2
4
  describe ".measure" do
3
5
  it "returns measured performance time" do
4
6
  expect(described_class.measure { '10' * 10 }).to be_kind_of(Numeric)
@@ -6,14 +8,14 @@ RSpec.describe Airbrake::Benchmark do
6
8
  end
7
9
 
8
10
  describe "#stop" do
9
- before { subject }
11
+ before { benchmark }
10
12
 
11
13
  context "when called one time" do
12
14
  its(:stop) { is_expected.to eq(true) }
13
15
  end
14
16
 
15
17
  context "when called twice or more" do
16
- before { subject.stop }
18
+ before { benchmark.stop }
17
19
 
18
20
  its(:stop) { is_expected.to eq(false) }
19
21
  end
@@ -25,7 +27,7 @@ RSpec.describe Airbrake::Benchmark do
25
27
  end
26
28
 
27
29
  context "when #stop was called" do
28
- before { subject.stop }
30
+ before { benchmark.stop }
29
31
 
30
32
  its(:duration) { is_expected.to be > 0 }
31
33
  end
@@ -1,4 +1,6 @@
1
1
  RSpec.describe Airbrake::CodeHunk do
2
+ subject(:code_hunk) { described_class.new }
3
+
2
4
  after do
3
5
  %w[empty_file.rb code.rb banana.rb short_file.rb long_line.txt].each do |f|
4
6
  Airbrake::FileCache[project_root_path(f)] = nil
@@ -27,10 +29,12 @@ RSpec.describe Airbrake::CodeHunk do
27
29
  end
28
30
 
29
31
  context "when a file has less than NLINES lines before start line" do
30
- subject { described_class.new.get(project_root_path('code.rb'), 1) }
32
+ subject(:code_hunk) do
33
+ described_class.new.get(project_root_path('code.rb'), 1)
34
+ end
31
35
 
32
36
  it do
33
- is_expected.to(
37
+ expect(code_hunk).to(
34
38
  eq(
35
39
  1 => 'module Airbrake',
36
40
  2 => ' ##',
@@ -43,10 +47,12 @@ RSpec.describe Airbrake::CodeHunk do
43
47
  end
44
48
 
45
49
  context "when a file has less than NLINES lines after end line" do
46
- subject { described_class.new.get(project_root_path('code.rb'), 222) }
50
+ subject(:code_hunk) do
51
+ described_class.new.get(project_root_path('code.rb'), 222)
52
+ end
47
53
 
48
54
  it do
49
- is_expected.to(
55
+ expect(code_hunk).to(
50
56
  eq(
51
57
  220 => ' end',
52
58
  221 => 'end',
@@ -56,12 +62,12 @@ RSpec.describe Airbrake::CodeHunk do
56
62
  end
57
63
 
58
64
  context "when a file has less than NLINES lines before and after" do
59
- subject do
65
+ subject(:code_hunk) do
60
66
  described_class.new.get(project_root_path('short_file.rb'), 2)
61
67
  end
62
68
 
63
69
  it do
64
- is_expected.to(
70
+ expect(code_hunk).to(
65
71
  eq(
66
72
  1 => 'module Banana',
67
73
  2 => ' attr_reader :bingo',
@@ -72,10 +78,12 @@ RSpec.describe Airbrake::CodeHunk do
72
78
  end
73
79
 
74
80
  context "when a file has enough lines before and after" do
75
- subject { described_class.new.get(project_root_path('code.rb'), 100) }
81
+ subject(:code_hunk) do
82
+ described_class.new.get(project_root_path('code.rb'), 100)
83
+ end
76
84
 
77
85
  it do
78
- is_expected.to(
86
+ expect(code_hunk).to(
79
87
  eq(
80
88
  98 => ' return json if json && json.bytesize <= MAX_NOTICE_SIZE',
81
89
  99 => ' end',
@@ -88,27 +96,28 @@ RSpec.describe Airbrake::CodeHunk do
88
96
  end
89
97
 
90
98
  context "when a line exceeds the length limit" do
91
- subject do
99
+ subject(:code_hunk) do
92
100
  described_class.new.get(project_root_path('long_line.txt'), 1)
93
101
  end
94
102
 
95
103
  it "strips the line" do
96
- expect(subject[1]).to eq('l' + 'o' * 196 + 'ng')
104
+ expect(code_hunk[1]).to eq("l#{'o' * 196}ng")
97
105
  end
98
106
  end
99
107
 
100
108
  context "when an error occurrs while fetching code" do
101
109
  before do
102
- expect(Airbrake::FileCache).to receive(:[]).and_raise(Errno::EACCES)
110
+ allow(Airbrake::Loggable.instance).to receive(:error)
111
+ allow(Airbrake::FileCache).to receive(:[]).and_raise(Errno::EACCES)
103
112
  end
104
113
 
105
114
  it "logs error and returns nil" do
106
- expect(Airbrake::Loggable.instance).to receive(:error).with(
107
- /can't read code hunk.+Permission denied/,
108
- )
109
- expect(subject.get(project_root_path('code.rb'), 1)).to(
115
+ expect(code_hunk.get(project_root_path('code.rb'), 1)).to(
110
116
  eq(1 => ''),
111
117
  )
118
+ expect(Airbrake::Loggable.instance).to have_received(:error).with(
119
+ /can't read code hunk.+Permission denied/,
120
+ )
112
121
  end
113
122
  end
114
123
  end
@@ -44,12 +44,17 @@ RSpec.describe Airbrake::Config::Processor do
44
44
  end
45
45
 
46
46
  describe "#process_remote_configuration" do
47
+ before do
48
+ allow(Airbrake::RemoteSettings).to receive(:poll)
49
+ end
50
+
47
51
  context "when the config doesn't define a project_id" do
48
52
  let(:config) { Airbrake::Config.new(project_id: nil) }
49
53
 
50
54
  it "doesn't set remote settings" do
51
- expect(Airbrake::RemoteSettings).not_to receive(:poll)
52
55
  described_class.new(config).process_remote_configuration
56
+
57
+ expect(Airbrake::RemoteSettings).not_to have_received(:poll)
53
58
  end
54
59
  end
55
60
 
@@ -57,8 +62,9 @@ RSpec.describe Airbrake::Config::Processor do
57
62
  let(:config) { Airbrake::Config.new(project_id: 123, environment: 'test') }
58
63
 
59
64
  it "doesn't set remote settings" do
60
- expect(Airbrake::RemoteSettings).not_to receive(:poll)
61
65
  described_class.new(config).process_remote_configuration
66
+
67
+ expect(Airbrake::RemoteSettings).not_to have_received(:poll)
62
68
  end
63
69
  end
64
70
 
@@ -68,8 +74,9 @@ RSpec.describe Airbrake::Config::Processor do
68
74
  end
69
75
 
70
76
  it "sets remote settings" do
71
- expect(Airbrake::RemoteSettings).to receive(:poll)
72
77
  described_class.new(config).process_remote_configuration
78
+
79
+ expect(Airbrake::RemoteSettings).to have_received(:poll)
73
80
  end
74
81
  end
75
82
 
@@ -77,8 +84,9 @@ RSpec.describe Airbrake::Config::Processor do
77
84
  let(:config) { Airbrake::Config.new(project_id: 123, remote_config: false) }
78
85
 
79
86
  it "doesn't set remote settings" do
80
- expect(Airbrake::RemoteSettings).not_to receive(:poll)
81
87
  described_class.new(config).process_remote_configuration
88
+
89
+ expect(Airbrake::RemoteSettings).not_to have_received(:poll)
82
90
  end
83
91
  end
84
92
  end
@@ -175,9 +175,12 @@ RSpec.describe Airbrake::Config::Validator do
175
175
  end
176
176
 
177
177
  it "warns about 'no effect'" do
178
- expect(config.logger).to receive(:warn)
179
- .with(/'ignore_environments' has no effect/)
178
+ allow(config.logger).to receive(:warn)
179
+
180
180
  described_class.check_notify_ability(config)
181
+
182
+ expect(config.logger).to have_received(:warn)
183
+ .with(/'ignore_environments' has no effect/)
181
184
  end
182
185
  end
183
186
 
data/spec/config_spec.rb CHANGED
@@ -1,4 +1,6 @@
1
1
  RSpec.describe Airbrake::Config do
2
+ subject(:config) { described_class.new }
3
+
2
4
  let(:resolved_promise) { Airbrake::Promise.new.resolve }
3
5
  let(:rejected_promise) { Airbrake::Promise.new.reject }
4
6
 
@@ -35,18 +37,22 @@ RSpec.describe Airbrake::Config do
35
37
  describe "#new" do
36
38
  context "when user config is passed" do
37
39
  subject { described_class.new(logger: StringIO.new) }
40
+
38
41
  its(:logger) { is_expected.to be_a(StringIO) }
39
42
  end
40
43
  end
41
44
 
42
45
  describe "#valid?" do
43
- context "when #validate returns a resolved promise" do
44
- before { expect(subject).to receive(:validate).and_return(resolved_promise) }
46
+ context "when the config is valid" do
47
+ before do
48
+ config.project_id = 123
49
+ config.project_key = 'abc'
50
+ end
51
+
45
52
  it { is_expected.to be_valid }
46
53
  end
47
54
 
48
- context "when #validate returns a rejected promise" do
49
- before { expect(subject).to receive(:validate).and_return(rejected_promise) }
55
+ context "when the config is invalid" do
50
56
  it { is_expected.not_to be_valid }
51
57
  end
52
58
  end
@@ -54,7 +60,7 @@ RSpec.describe Airbrake::Config do
54
60
  describe "#ignored_environment?" do
55
61
  context "when Validator returns a resolved promise" do
56
62
  before do
57
- expect(Airbrake::Config::Validator).to receive(:check_notify_ability)
63
+ allow(Airbrake::Config::Validator).to receive(:check_notify_ability)
58
64
  .and_return(resolved_promise)
59
65
  end
60
66
 
@@ -63,7 +69,7 @@ RSpec.describe Airbrake::Config do
63
69
 
64
70
  context "when Validator returns a rejected promise" do
65
71
  before do
66
- expect(Airbrake::Config::Validator).to receive(:check_notify_ability)
72
+ allow(Airbrake::Config::Validator).to receive(:check_notify_ability)
67
73
  .and_return(rejected_promise)
68
74
  end
69
75
 
@@ -96,19 +102,21 @@ RSpec.describe Airbrake::Config do
96
102
  end
97
103
 
98
104
  describe "#check_configuration" do
99
- let(:user_config) { {} }
100
-
101
105
  subject { described_class.new(valid_params.merge(user_config)) }
102
106
 
107
+ let(:user_config) { {} }
108
+
103
109
  its(:check_configuration) { is_expected.to be_an(Airbrake::Promise) }
104
110
 
105
111
  context "when config is invalid" do
106
112
  let(:user_config) { { project_id: nil } }
113
+
107
114
  its(:check_configuration) { is_expected.to be_rejected }
108
115
  end
109
116
 
110
117
  context "when current environment is ignored" do
111
118
  let(:user_config) { { environment: 'test', ignore_environments: ['test'] } }
119
+
112
120
  its(:check_configuration) { is_expected.to be_rejected }
113
121
  end
114
122
 
@@ -120,12 +128,12 @@ RSpec.describe Airbrake::Config do
120
128
  describe "#check_performance_options" do
121
129
  it "returns a promise" do
122
130
  resource = Airbrake::Query.new(method: '', route: '', query: '', timing: 1)
123
- expect(subject.check_performance_options(resource))
131
+ expect(config.check_performance_options(resource))
124
132
  .to be_an(Airbrake::Promise)
125
133
  end
126
134
 
127
135
  context "when performance stats are disabled" do
128
- before { subject.performance_stats = false }
136
+ before { config.performance_stats = false }
129
137
 
130
138
  let(:resource) do
131
139
  Airbrake::Request.new(
@@ -134,7 +142,7 @@ RSpec.describe Airbrake::Config do
134
142
  end
135
143
 
136
144
  it "returns a rejected promise" do
137
- promise = subject.check_performance_options(resource)
145
+ promise = config.check_performance_options(resource)
138
146
  expect(promise.value).to eq(
139
147
  'error' => "The Performance Stats feature is disabled",
140
148
  )
@@ -142,14 +150,14 @@ RSpec.describe Airbrake::Config do
142
150
  end
143
151
 
144
152
  context "when query stats are disabled" do
145
- before { subject.query_stats = false }
153
+ before { config.query_stats = false }
146
154
 
147
155
  let(:resource) do
148
156
  Airbrake::Query.new(method: 'GET', route: '/foo', query: '', timing: 1)
149
157
  end
150
158
 
151
159
  it "returns a rejected promise" do
152
- promise = subject.check_performance_options(resource)
160
+ promise = config.check_performance_options(resource)
153
161
  expect(promise.value).to eq(
154
162
  'error' => "The Query Stats feature is disabled",
155
163
  )
@@ -157,14 +165,14 @@ RSpec.describe Airbrake::Config do
157
165
  end
158
166
 
159
167
  context "when job stats are disabled" do
160
- before { subject.job_stats = false }
168
+ before { config.job_stats = false }
161
169
 
162
170
  let(:resource) do
163
171
  Airbrake::Queue.new(queue: 'foo_queue', error_count: 0, timing: 1)
164
172
  end
165
173
 
166
174
  it "returns a rejected promise" do
167
- promise = subject.check_performance_options(resource)
175
+ promise = config.check_performance_options(resource)
168
176
  expect(promise.value).to eq(
169
177
  'error' => "The Job Stats feature is disabled",
170
178
  )
@@ -174,7 +182,7 @@ RSpec.describe Airbrake::Config do
174
182
 
175
183
  describe "#logger" do
176
184
  it "sets logger level to Logger::WARN" do
177
- expect(subject.logger.level).to eq(Logger::WARN)
185
+ expect(config.logger.level).to eq(Logger::WARN)
178
186
  end
179
187
  end
180
188
  end
@@ -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