airbrake-ruby 4.8.0 → 4.11.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/airbrake-ruby.rb +101 -25
- data/lib/airbrake-ruby/async_sender.rb +3 -3
- data/lib/airbrake-ruby/backtrace.rb +2 -2
- data/lib/airbrake-ruby/benchmark.rb +1 -1
- data/lib/airbrake-ruby/code_hunk.rb +1 -1
- data/lib/airbrake-ruby/config.rb +1 -1
- data/lib/airbrake-ruby/config/validator.rb +3 -3
- data/lib/airbrake-ruby/deploy_notifier.rb +1 -1
- data/lib/airbrake-ruby/filters/exception_attributes_filter.rb +2 -2
- data/lib/airbrake-ruby/filters/git_last_checkout_filter.rb +2 -2
- data/lib/airbrake-ruby/filters/keys_filter.rb +1 -1
- data/lib/airbrake-ruby/filters/sql_filter.rb +3 -3
- data/lib/airbrake-ruby/filters/thread_filter.rb +1 -1
- data/lib/airbrake-ruby/grouppable.rb +12 -0
- data/lib/airbrake-ruby/inspectable.rb +2 -2
- data/lib/airbrake-ruby/mergeable.rb +12 -0
- data/lib/airbrake-ruby/notice.rb +7 -7
- data/lib/airbrake-ruby/notice_notifier.rb +3 -2
- data/lib/airbrake-ruby/performance_breakdown.rb +12 -6
- data/lib/airbrake-ruby/performance_notifier.rb +69 -22
- data/lib/airbrake-ruby/query.rb +15 -11
- data/lib/airbrake-ruby/queue.rb +56 -0
- data/lib/airbrake-ruby/request.rb +14 -12
- data/lib/airbrake-ruby/stat.rb +1 -1
- data/lib/airbrake-ruby/version.rb +1 -1
- data/spec/airbrake_spec.rb +135 -45
- data/spec/async_sender_spec.rb +4 -4
- data/spec/backtrace_spec.rb +18 -18
- data/spec/code_hunk_spec.rb +9 -9
- data/spec/config/validator_spec.rb +5 -5
- data/spec/config_spec.rb +5 -9
- data/spec/deploy_notifier_spec.rb +2 -2
- data/spec/filter_chain_spec.rb +1 -1
- data/spec/filters/dependency_filter_spec.rb +1 -1
- data/spec/filters/gem_root_filter_spec.rb +5 -5
- data/spec/filters/git_last_checkout_filter_spec.rb +1 -1
- data/spec/filters/git_repository_filter.rb +1 -1
- data/spec/filters/git_revision_filter_spec.rb +10 -10
- data/spec/filters/keys_blacklist_spec.rb +22 -22
- data/spec/filters/keys_whitelist_spec.rb +21 -21
- data/spec/filters/root_directory_filter_spec.rb +5 -5
- data/spec/filters/sql_filter_spec.rb +53 -55
- data/spec/filters/system_exit_filter_spec.rb +1 -1
- data/spec/filters/thread_filter_spec.rb +28 -28
- data/spec/fixtures/project_root/code.rb +9 -9
- data/spec/notice_notifier/options_spec.rb +12 -12
- data/spec/notice_notifier_spec.rb +18 -18
- data/spec/notice_spec.rb +5 -5
- data/spec/performance_breakdown_spec.rb +11 -0
- data/spec/performance_notifier_spec.rb +243 -72
- data/spec/query_spec.rb +11 -1
- data/spec/queue_spec.rb +21 -0
- data/spec/request_spec.rb +11 -1
- data/spec/response_spec.rb +8 -8
- data/spec/spec_helper.rb +2 -2
- data/spec/stat_spec.rb +2 -2
- data/spec/sync_sender_spec.rb +12 -12
- data/spec/tdigest_spec.rb +6 -6
- data/spec/thread_pool_spec.rb +5 -5
- data/spec/timed_trace_spec.rb +1 -1
- data/spec/truncator_spec.rb +12 -12
- metadata +7 -2
data/lib/airbrake-ruby/stat.rb
CHANGED
data/spec/airbrake_spec.rb
CHANGED
@@ -51,28 +51,6 @@ RSpec.describe Airbrake do
|
|
51
51
|
expect(described_class).to be_configured
|
52
52
|
end
|
53
53
|
|
54
|
-
context "when a notifier was configured" do
|
55
|
-
before do
|
56
|
-
expect(described_class).to receive(:configured?).and_return(true)
|
57
|
-
end
|
58
|
-
|
59
|
-
it "closes previously configured notice notifier" do
|
60
|
-
expect(described_class).to receive(:close)
|
61
|
-
described_class.configure {}
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
context "when a notifier wasn't configured" do
|
66
|
-
before do
|
67
|
-
expect(described_class).to receive(:configured?).and_return(false)
|
68
|
-
end
|
69
|
-
|
70
|
-
it "doesn't close previously configured notice notifier" do
|
71
|
-
expect(described_class).not_to receive(:close)
|
72
|
-
described_class.configure {}
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
54
|
context "when called multiple times" do
|
77
55
|
it "doesn't overwrite performance notifier" do
|
78
56
|
described_class.configure {}
|
@@ -182,19 +160,6 @@ RSpec.describe Airbrake do
|
|
182
160
|
end
|
183
161
|
end
|
184
162
|
|
185
|
-
describe "#reset" do
|
186
|
-
context "when Airbrake was previously configured" do
|
187
|
-
before do
|
188
|
-
expect(described_class).to receive(:configured?).and_return(true)
|
189
|
-
end
|
190
|
-
|
191
|
-
it "closes notice notifier" do
|
192
|
-
expect(described_class).to receive(:close)
|
193
|
-
subject.reset
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
163
|
describe "#notify_request" do
|
199
164
|
context "when :stash key is not provided" do
|
200
165
|
it "doesn't add anything to the stash of the request" do
|
@@ -206,7 +171,7 @@ RSpec.describe Airbrake do
|
|
206
171
|
method: 'GET',
|
207
172
|
route: '/',
|
208
173
|
status_code: 200,
|
209
|
-
|
174
|
+
timing: 1,
|
210
175
|
)
|
211
176
|
end
|
212
177
|
end
|
@@ -222,14 +187,30 @@ RSpec.describe Airbrake do
|
|
222
187
|
method: 'GET',
|
223
188
|
route: '/',
|
224
189
|
status_code: 200,
|
225
|
-
|
190
|
+
timing: 1,
|
226
191
|
},
|
227
|
-
request_id: 1
|
192
|
+
request_id: 1,
|
228
193
|
)
|
229
194
|
end
|
230
195
|
end
|
231
196
|
end
|
232
197
|
|
198
|
+
describe "#notify_request_sync" do
|
199
|
+
it "notifies request synchronously" do
|
200
|
+
expect(described_class.performance_notifier).to receive(:notify_sync)
|
201
|
+
|
202
|
+
described_class.notify_request_sync(
|
203
|
+
{
|
204
|
+
method: 'GET',
|
205
|
+
route: '/',
|
206
|
+
status_code: 200,
|
207
|
+
timing: 1,
|
208
|
+
},
|
209
|
+
request_id: 1,
|
210
|
+
)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
233
214
|
describe "#notify_query" do
|
234
215
|
context "when :stash key is not provided" do
|
235
216
|
it "doesn't add anything to the stash of the query" do
|
@@ -241,7 +222,7 @@ RSpec.describe Airbrake do
|
|
241
222
|
method: 'GET',
|
242
223
|
route: '/',
|
243
224
|
query: '',
|
244
|
-
|
225
|
+
timing: 1,
|
245
226
|
)
|
246
227
|
end
|
247
228
|
end
|
@@ -257,14 +238,30 @@ RSpec.describe Airbrake do
|
|
257
238
|
method: 'GET',
|
258
239
|
route: '/',
|
259
240
|
query: '',
|
260
|
-
|
241
|
+
timing: 1,
|
261
242
|
},
|
262
|
-
request_id: 1
|
243
|
+
request_id: 1,
|
263
244
|
)
|
264
245
|
end
|
265
246
|
end
|
266
247
|
end
|
267
248
|
|
249
|
+
describe "#notify_query_sync" do
|
250
|
+
it "notifies query synchronously" do
|
251
|
+
expect(described_class.performance_notifier).to receive(:notify_sync)
|
252
|
+
|
253
|
+
described_class.notify_query_sync(
|
254
|
+
{
|
255
|
+
method: 'GET',
|
256
|
+
route: '/',
|
257
|
+
query: '',
|
258
|
+
timing: 1,
|
259
|
+
},
|
260
|
+
request_id: 1,
|
261
|
+
)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
268
265
|
describe "#notify_performance_breakdown" do
|
269
266
|
context "when :stash key is not provided" do
|
270
267
|
it "doesn't add anything to the stash of the performance breakdown" do
|
@@ -276,7 +273,7 @@ RSpec.describe Airbrake do
|
|
276
273
|
method: 'GET',
|
277
274
|
route: '/',
|
278
275
|
query: '',
|
279
|
-
|
276
|
+
timing: 1,
|
280
277
|
)
|
281
278
|
end
|
282
279
|
end
|
@@ -284,7 +281,7 @@ RSpec.describe Airbrake do
|
|
284
281
|
context "when :stash key is provided" do
|
285
282
|
it "adds the value as the stash of the performance breakdown" do
|
286
283
|
expect(
|
287
|
-
described_class.performance_notifier
|
284
|
+
described_class.performance_notifier,
|
288
285
|
).to receive(:notify) do |performance_breakdown|
|
289
286
|
expect(performance_breakdown.stash).to eq(request_id: 1)
|
290
287
|
end
|
@@ -295,14 +292,76 @@ RSpec.describe Airbrake do
|
|
295
292
|
route: '/',
|
296
293
|
response_type: :html,
|
297
294
|
groups: {},
|
298
|
-
|
295
|
+
timing: 1,
|
296
|
+
},
|
297
|
+
request_id: 1,
|
298
|
+
)
|
299
|
+
end
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
describe "#notify_performance_breakdown_sync" do
|
304
|
+
it "notifies performance breakdown synchronously" do
|
305
|
+
expect(described_class.performance_notifier).to receive(:notify_sync)
|
306
|
+
|
307
|
+
described_class.notify_performance_breakdown_sync(
|
308
|
+
{
|
309
|
+
method: 'GET',
|
310
|
+
route: '/',
|
311
|
+
response_type: :html,
|
312
|
+
groups: {},
|
313
|
+
timing: 1,
|
314
|
+
},
|
315
|
+
request_id: 1,
|
316
|
+
)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe "#notify_queue" do
|
321
|
+
context "when :stash key is not provided" do
|
322
|
+
it "doesn't add anything to the stash of the queue" do
|
323
|
+
expect(described_class.performance_notifier).to receive(:notify) do |queue|
|
324
|
+
expect(queue.stash).to be_empty
|
325
|
+
end
|
326
|
+
|
327
|
+
described_class.notify_queue(
|
328
|
+
queue: 'bananas',
|
329
|
+
error_count: 10,
|
330
|
+
)
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
context "when :stash key is provided" do
|
335
|
+
it "adds the value as the stash of the queue" do
|
336
|
+
expect(described_class.performance_notifier).to receive(:notify) do |queue|
|
337
|
+
expect(queue.stash).to eq(request_id: 1)
|
338
|
+
end
|
339
|
+
|
340
|
+
described_class.notify_queue(
|
341
|
+
{
|
342
|
+
queue: 'bananas',
|
343
|
+
error_count: 10,
|
299
344
|
},
|
300
|
-
request_id: 1
|
345
|
+
request_id: 1,
|
301
346
|
)
|
302
347
|
end
|
303
348
|
end
|
304
349
|
end
|
305
350
|
|
351
|
+
describe "#notify_queue_sync" do
|
352
|
+
it "notifies queue synchronously" do
|
353
|
+
expect(described_class.performance_notifier).to receive(:notify_sync)
|
354
|
+
|
355
|
+
described_class.notify_queue_sync(
|
356
|
+
{
|
357
|
+
queue: 'bananas',
|
358
|
+
error_count: 10,
|
359
|
+
},
|
360
|
+
request_id: 1,
|
361
|
+
)
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
306
365
|
describe ".performance_notifier" do
|
307
366
|
it "returns a performance notifier" do
|
308
367
|
expect(described_class.performance_notifier)
|
@@ -321,4 +380,35 @@ RSpec.describe Airbrake do
|
|
321
380
|
expect(described_class.deploy_notifier).to be_an(Airbrake::DeployNotifier)
|
322
381
|
end
|
323
382
|
end
|
383
|
+
|
384
|
+
describe ".close" do
|
385
|
+
after { Airbrake.reset }
|
386
|
+
|
387
|
+
context "when notice_notifier is defined" do
|
388
|
+
it "gets closed" do
|
389
|
+
expect(Airbrake.notice_notifier).to receive(:close)
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
context "when notice_notifier is undefined" do
|
394
|
+
it "doesn't get closed (because it wasn't initialized)" do
|
395
|
+
Airbrake.instance_variable_set(:@notice_notifier, nil)
|
396
|
+
expect_any_instance_of(Airbrake::NoticeNotifier).not_to receive(:close)
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
context "when performance_notifier is defined" do
|
401
|
+
it "gets closed" do
|
402
|
+
expect(Airbrake.performance_notifier).to receive(:close)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
context "when perforance_notifier is undefined" do
|
407
|
+
it "doesn't get closed (because it wasn't initialized)" do
|
408
|
+
Airbrake.instance_variable_set(:@performance_notifier, nil)
|
409
|
+
expect_any_instance_of(Airbrake::PerformanceNotifier)
|
410
|
+
.not_to receive(:close)
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
324
414
|
end
|
data/spec/async_sender_spec.rb
CHANGED
@@ -8,7 +8,7 @@ RSpec.describe Airbrake::AsyncSender do
|
|
8
8
|
Airbrake::Config.instance = Airbrake::Config.new(
|
9
9
|
project_id: '1',
|
10
10
|
workers: 3,
|
11
|
-
queue_size: 10
|
11
|
+
queue_size: 10,
|
12
12
|
)
|
13
13
|
end
|
14
14
|
|
@@ -35,7 +35,7 @@ RSpec.describe Airbrake::AsyncSender do
|
|
35
35
|
Airbrake::Config.instance = Airbrake::Config.new(
|
36
36
|
project_id: '1',
|
37
37
|
workers: 0,
|
38
|
-
queue_size: 1
|
38
|
+
queue_size: 1,
|
39
39
|
)
|
40
40
|
end
|
41
41
|
|
@@ -55,13 +55,13 @@ RSpec.describe Airbrake::AsyncSender do
|
|
55
55
|
|
56
56
|
expect(promise).to be_rejected
|
57
57
|
expect(promise.value).to eq(
|
58
|
-
'error' => "AsyncSender has reached its capacity of 1"
|
58
|
+
'error' => "AsyncSender has reached its capacity of 1",
|
59
59
|
)
|
60
60
|
end
|
61
61
|
|
62
62
|
it "logs discarded notice" do
|
63
63
|
expect(Airbrake::Loggable.instance).to receive(:error).with(
|
64
|
-
/reached its capacity
|
64
|
+
/reached its capacity/,
|
65
65
|
).at_least(:once)
|
66
66
|
|
67
67
|
15.times { subject.send(notice, Airbrake::Promise.new) }
|
data/spec/backtrace_spec.rb
CHANGED
@@ -172,13 +172,13 @@ RSpec.describe Airbrake::Backtrace do
|
|
172
172
|
|
173
173
|
it "returns array of hashes where each unknown frame is marked as 'function'" do
|
174
174
|
expect(
|
175
|
-
described_class.parse(ex)
|
175
|
+
described_class.parse(ex),
|
176
176
|
).to eq([file: nil, line: nil, function: 'a b c 1 23 321 .rb'])
|
177
177
|
end
|
178
178
|
|
179
179
|
it "logs frames that cannot be parsed" do
|
180
180
|
expect(Airbrake::Loggable.instance).to receive(:error).with(
|
181
|
-
/can't parse 'a b c 1 23 321 .rb'
|
181
|
+
/can't parse 'a b c 1 23 321 .rb'/,
|
182
182
|
)
|
183
183
|
described_class.parse(ex)
|
184
184
|
end
|
@@ -280,18 +280,18 @@ RSpec.describe Airbrake::Backtrace do
|
|
280
280
|
# rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
|
281
281
|
96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
|
282
282
|
# rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
|
283
|
-
}
|
283
|
+
},
|
284
284
|
},
|
285
285
|
{
|
286
286
|
file: fixture_path('notroot.txt'),
|
287
287
|
line: 3,
|
288
|
-
function: 'pineapple'
|
288
|
+
function: 'pineapple',
|
289
289
|
},
|
290
290
|
{
|
291
291
|
file: project_root_path('vendor/bundle/ignored_file.rb'),
|
292
292
|
line: 2,
|
293
|
-
function: 'ignore_me'
|
294
|
-
}
|
293
|
+
function: 'ignore_me',
|
294
|
+
},
|
295
295
|
]
|
296
296
|
end
|
297
297
|
|
@@ -300,7 +300,7 @@ RSpec.describe Airbrake::Backtrace do
|
|
300
300
|
backtrace = [
|
301
301
|
project_root_path('code.rb') + ":94:in `to_json'",
|
302
302
|
fixture_path('notroot.txt' + ":3:in `pineapple'"),
|
303
|
-
project_root_path('vendor/bundle/ignored_file.rb') + ":2:in `ignore_me'"
|
303
|
+
project_root_path('vendor/bundle/ignored_file.rb') + ":2:in `ignore_me'",
|
304
304
|
]
|
305
305
|
ex.set_backtrace(backtrace)
|
306
306
|
expect(described_class.parse(ex)).to eq(parsed_backtrace)
|
@@ -310,7 +310,7 @@ RSpec.describe Airbrake::Backtrace do
|
|
310
310
|
context "and when root_directory is a Pathname" do
|
311
311
|
before do
|
312
312
|
Airbrake::Config.instance.merge(
|
313
|
-
root_directory: Pathname.new(project_root_path(''))
|
313
|
+
root_directory: Pathname.new(project_root_path('')),
|
314
314
|
)
|
315
315
|
end
|
316
316
|
|
@@ -328,8 +328,8 @@ RSpec.describe Airbrake::Backtrace do
|
|
328
328
|
# rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
|
329
329
|
96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
|
330
330
|
# rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
|
331
|
-
}
|
332
|
-
}
|
331
|
+
},
|
332
|
+
},
|
333
333
|
]
|
334
334
|
end
|
335
335
|
|
@@ -360,7 +360,7 @@ RSpec.describe Airbrake::Backtrace do
|
|
360
360
|
# rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
|
361
361
|
96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
|
362
362
|
# rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
|
363
|
-
}
|
363
|
+
},
|
364
364
|
},
|
365
365
|
{
|
366
366
|
file: project_root_path('code.rb'),
|
@@ -373,14 +373,14 @@ RSpec.describe Airbrake::Backtrace do
|
|
373
373
|
# rubocop:disable Metrics/LineLength,Lint/InterpolationCheck
|
374
374
|
96 => ' @config.logger.debug("#{LOG_LABEL} `notice.to_json` failed: #{ex.class}: #{ex}")',
|
375
375
|
# rubocop:enable Metrics/LineLength,Lint/InterpolationCheck
|
376
|
-
97 => ' else'
|
377
|
-
}
|
376
|
+
97 => ' else',
|
377
|
+
},
|
378
378
|
},
|
379
379
|
{
|
380
380
|
file: project_root_path('code.rb'),
|
381
381
|
line: 96,
|
382
|
-
function: 'to_json'
|
383
|
-
}
|
382
|
+
function: 'to_json',
|
383
|
+
},
|
384
384
|
]
|
385
385
|
end
|
386
386
|
|
@@ -389,7 +389,7 @@ RSpec.describe Airbrake::Backtrace do
|
|
389
389
|
backtrace = [
|
390
390
|
project_root_path('code.rb') + ":94:in `to_json'",
|
391
391
|
project_root_path('code.rb') + ":95:in `to_json'",
|
392
|
-
project_root_path('code.rb') + ":96:in `to_json'"
|
392
|
+
project_root_path('code.rb') + ":96:in `to_json'",
|
393
393
|
]
|
394
394
|
ex.set_backtrace(backtrace)
|
395
395
|
expect(described_class.parse(ex)).to eq(parsed_backtrace)
|
@@ -410,8 +410,8 @@ RSpec.describe Airbrake::Backtrace do
|
|
410
410
|
{
|
411
411
|
file: project_root_path('code.rb'),
|
412
412
|
line: 94,
|
413
|
-
function: 'to_json'
|
414
|
-
}
|
413
|
+
function: 'to_json',
|
414
|
+
},
|
415
415
|
]
|
416
416
|
end
|
417
417
|
|
data/spec/code_hunk_spec.rb
CHANGED
@@ -37,7 +37,7 @@ RSpec.describe Airbrake::CodeHunk do
|
|
37
37
|
# rubocop:disable Metrics/LineLength
|
38
38
|
3 => ' # Represents a chunk of information that is meant to be either sent to',
|
39
39
|
# rubocop:enable Metrics/LineLength
|
40
|
-
)
|
40
|
+
),
|
41
41
|
)
|
42
42
|
end
|
43
43
|
end
|
@@ -49,8 +49,8 @@ RSpec.describe Airbrake::CodeHunk do
|
|
49
49
|
is_expected.to(
|
50
50
|
eq(
|
51
51
|
220 => ' end',
|
52
|
-
221 => 'end'
|
53
|
-
)
|
52
|
+
221 => 'end',
|
53
|
+
),
|
54
54
|
)
|
55
55
|
end
|
56
56
|
end
|
@@ -65,8 +65,8 @@ RSpec.describe Airbrake::CodeHunk do
|
|
65
65
|
eq(
|
66
66
|
1 => 'module Banana',
|
67
67
|
2 => ' attr_reader :bingo',
|
68
|
-
3 => 'end'
|
69
|
-
)
|
68
|
+
3 => 'end',
|
69
|
+
),
|
70
70
|
)
|
71
71
|
end
|
72
72
|
end
|
@@ -81,8 +81,8 @@ RSpec.describe Airbrake::CodeHunk do
|
|
81
81
|
99 => ' end',
|
82
82
|
100 => '',
|
83
83
|
101 => ' break if truncate == 0',
|
84
|
-
102 => ' end'
|
85
|
-
)
|
84
|
+
102 => ' end',
|
85
|
+
),
|
86
86
|
)
|
87
87
|
end
|
88
88
|
end
|
@@ -104,10 +104,10 @@ RSpec.describe Airbrake::CodeHunk do
|
|
104
104
|
|
105
105
|
it "logs error and returns nil" do
|
106
106
|
expect(Airbrake::Loggable.instance).to receive(:error).with(
|
107
|
-
/can't read code hunk.+Permission denied
|
107
|
+
/can't read code hunk.+Permission denied/,
|
108
108
|
)
|
109
109
|
expect(subject.get(project_root_path('code.rb'), 1)).to(
|
110
|
-
eq(1 => '')
|
110
|
+
eq(1 => ''),
|
111
111
|
)
|
112
112
|
end
|
113
113
|
end
|