appsignal 4.0.3-java → 4.0.4-java
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/appsignal/check_in/cron.rb +2 -34
- data/lib/appsignal/check_in/scheduler.rb +192 -0
- data/lib/appsignal/check_in.rb +18 -0
- data/lib/appsignal/cli/diagnose.rb +1 -1
- data/lib/appsignal/hooks/at_exit.rb +2 -1
- data/lib/appsignal/rack/body_wrapper.rb +15 -0
- data/lib/appsignal/transmitter.rb +30 -7
- data/lib/appsignal/utils/ndjson.rb +15 -0
- data/lib/appsignal/utils.rb +1 -0
- data/lib/appsignal/version.rb +1 -1
- data/lib/appsignal.rb +1 -0
- data/spec/lib/appsignal/check_in/cron_spec.rb +210 -0
- data/spec/lib/appsignal/check_in/scheduler_spec.rb +484 -0
- data/spec/lib/appsignal/environment_spec.rb +1 -1
- data/spec/lib/appsignal/hooks/at_exit_spec.rb +11 -0
- data/spec/lib/appsignal/rack/body_wrapper_spec.rb +29 -21
- data/spec/lib/appsignal/transmitter_spec.rb +48 -2
- data/spec/lib/appsignal_spec.rb +5 -0
- data/spec/support/helpers/take_at_most_helper.rb +21 -0
- metadata +7 -3
- data/spec/lib/appsignal/check_in_spec.rb +0 -136
@@ -0,0 +1,484 @@
|
|
1
|
+
describe Appsignal::CheckIn::Scheduler do
|
2
|
+
include WaitForHelper
|
3
|
+
include TakeAtMostHelper
|
4
|
+
|
5
|
+
let(:transmitter) { Appsignal::Transmitter.new("http://checkin-endpoint.invalid") }
|
6
|
+
|
7
|
+
before do
|
8
|
+
allow(Appsignal).to receive(:active?).and_return(true)
|
9
|
+
allow(transmitter).to receive(:transmit).and_return(Net::HTTPSuccess.new("1.1", 200, "OK"))
|
10
|
+
allow(Appsignal::CheckIn).to receive(:transmitter).and_return(transmitter)
|
11
|
+
allow(Appsignal::CheckIn).to receive(:scheduler).and_return(subject)
|
12
|
+
# Shorten debounce intervals to make the tests run faster.
|
13
|
+
stub_const("Appsignal::CheckIn::Scheduler::INITIAL_DEBOUNCE_SECONDS", 0.1)
|
14
|
+
stub_const("Appsignal::CheckIn::Scheduler::BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS", 0.1)
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
subject.stop
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "when no event is sent" do
|
22
|
+
it "does not start a thread" do
|
23
|
+
expect(subject.thread).to be_nil
|
24
|
+
end
|
25
|
+
|
26
|
+
it "does not schedule a debounce" do
|
27
|
+
expect(subject.waker).to be_nil
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can be stopped" do
|
31
|
+
# Set all debounce intervals to 10 seconds, to make the assertion
|
32
|
+
# fail if it waits for the debounce -- this ensures that what is being
|
33
|
+
# tested is that no debounces are awaited when stopping the scheduler.
|
34
|
+
stub_const("Appsignal::CheckIn::Scheduler::INITIAL_DEBOUNCE_SECONDS", 10)
|
35
|
+
stub_const("Appsignal::CheckIn::Scheduler::BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS", 10)
|
36
|
+
|
37
|
+
take_at_most(0.1) do
|
38
|
+
expect { subject.stop }.not_to raise_error
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
it "can be stopped more than once" do
|
43
|
+
# Set all debounce intervals to 10 seconds, to make the assertion
|
44
|
+
# fail if it waits for the debounce -- this ensures that what is being
|
45
|
+
# tested is that no debounces are awaited when stopping the scheduler.
|
46
|
+
stub_const("Appsignal::CheckIn::Scheduler::INITIAL_DEBOUNCE_SECONDS", 10)
|
47
|
+
stub_const("Appsignal::CheckIn::Scheduler::BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS", 10)
|
48
|
+
|
49
|
+
take_at_most(0.1) do
|
50
|
+
expect { subject.stop }.not_to raise_error
|
51
|
+
expect { subject.stop }.not_to raise_error
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "closes the queue when stopped" do
|
56
|
+
subject.stop
|
57
|
+
expect(subject.queue.closed?).to be(true)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "when an event is sent" do
|
62
|
+
it "starts a thread" do
|
63
|
+
Appsignal::CheckIn.cron("test")
|
64
|
+
expect(subject.thread).to be_a(Thread)
|
65
|
+
end
|
66
|
+
|
67
|
+
it "schedules a debounce" do
|
68
|
+
Appsignal::CheckIn.cron("test")
|
69
|
+
expect(subject.waker).to be_a(Thread)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "schedules the event to be transmitted" do
|
73
|
+
expect(transmitter).to receive(:transmit).with([hash_including(
|
74
|
+
:identifier => "test",
|
75
|
+
:check_in_type => "cron",
|
76
|
+
:kind => "finish"
|
77
|
+
)], :format => :ndjson)
|
78
|
+
|
79
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
80
|
+
message.include?("Scheduling cron check-in `test` finish event")
|
81
|
+
end)
|
82
|
+
|
83
|
+
expect(subject.events).to be_empty
|
84
|
+
|
85
|
+
Appsignal::CheckIn.cron("test")
|
86
|
+
|
87
|
+
expect(subject.events).not_to be_empty
|
88
|
+
|
89
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
90
|
+
message.include?("Transmitted cron check-in `test` finish event")
|
91
|
+
end)
|
92
|
+
|
93
|
+
wait_for("the event to be transmitted") { subject.transmitted == 1 }
|
94
|
+
|
95
|
+
expect(subject.events).to be_empty
|
96
|
+
end
|
97
|
+
|
98
|
+
it "waits for the event to be transmitted when stopped" do
|
99
|
+
# Set all debounce intervals to 10 seconds, to make the test
|
100
|
+
# fail if it waits for the debounce -- this ensures that what is being
|
101
|
+
# tested is that the events are transmitted immediately when the
|
102
|
+
# scheduler is stopped, without waiting for any debounce.
|
103
|
+
stub_const("Appsignal::CheckIn::Scheduler::INITIAL_DEBOUNCE_SECONDS", 10)
|
104
|
+
stub_const("Appsignal::CheckIn::Scheduler::BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS", 10)
|
105
|
+
|
106
|
+
expect(transmitter).to receive(:transmit).with([hash_including(
|
107
|
+
:identifier => "test",
|
108
|
+
:check_in_type => "cron",
|
109
|
+
:kind => "finish"
|
110
|
+
)], :format => :ndjson)
|
111
|
+
|
112
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
113
|
+
message.include?("Scheduling cron check-in `test` finish event")
|
114
|
+
end)
|
115
|
+
|
116
|
+
Appsignal::CheckIn.cron("test")
|
117
|
+
|
118
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
119
|
+
message.include?("Transmitted cron check-in `test` finish event")
|
120
|
+
end)
|
121
|
+
|
122
|
+
expect(subject.events).not_to be_empty
|
123
|
+
|
124
|
+
take_at_most(0.1) do
|
125
|
+
expect { subject.stop }.not_to raise_error
|
126
|
+
end
|
127
|
+
|
128
|
+
# Check that the thread wasn't killed before the transmission was
|
129
|
+
# completed.
|
130
|
+
expect(subject.transmitted).to eq(1)
|
131
|
+
|
132
|
+
expect(subject.events).to be_empty
|
133
|
+
end
|
134
|
+
|
135
|
+
it "can be stopped more than once" do
|
136
|
+
# Set all debounce intervals to 10 seconds, to make the test
|
137
|
+
# fail if it waits for the debounce -- this ensures that what is being
|
138
|
+
# tested is that the events are transmitted immediately when the
|
139
|
+
# scheduler is stopped, without waiting for the debounce interval.
|
140
|
+
stub_const("Appsignal::CheckIn::Scheduler::INITIAL_DEBOUNCE_SECONDS", 10)
|
141
|
+
stub_const("Appsignal::CheckIn::Scheduler::BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS", 10)
|
142
|
+
|
143
|
+
Appsignal::CheckIn.cron("test")
|
144
|
+
take_at_most(0.1) do
|
145
|
+
expect { subject.stop }.not_to raise_error
|
146
|
+
end
|
147
|
+
|
148
|
+
# Check that the thread wasn't killed before the transmission was
|
149
|
+
# completed.
|
150
|
+
expect(subject.transmitted).to eq(1)
|
151
|
+
|
152
|
+
take_at_most(0.1) do
|
153
|
+
expect { subject.stop }.not_to raise_error
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it "closes the queue when stopped" do
|
158
|
+
Appsignal::CheckIn.cron("test")
|
159
|
+
subject.stop
|
160
|
+
expect(subject.queue.closed?).to be(true)
|
161
|
+
end
|
162
|
+
|
163
|
+
it "kills the thread when stopped" do
|
164
|
+
Appsignal::CheckIn.cron("test")
|
165
|
+
subject.stop
|
166
|
+
expect(subject.thread.alive?).to be(false)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "unschedules the debounce when stopped" do
|
170
|
+
Appsignal::CheckIn.cron("test")
|
171
|
+
waker = subject.waker
|
172
|
+
subject.stop
|
173
|
+
expect(waker.alive?).to be(false)
|
174
|
+
expect(subject.waker).to be_nil
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "when many events are sent" do
|
179
|
+
describe "within the short debounce interval" do
|
180
|
+
it "transmits all events at once" do
|
181
|
+
expect(transmitter).to receive(:transmit).with(
|
182
|
+
["first", "second", "third"].map do |identifier|
|
183
|
+
hash_including(
|
184
|
+
:identifier => identifier,
|
185
|
+
:check_in_type => "cron",
|
186
|
+
:kind => "finish"
|
187
|
+
)
|
188
|
+
end, :format => :ndjson
|
189
|
+
)
|
190
|
+
|
191
|
+
Appsignal::CheckIn.cron("first")
|
192
|
+
Appsignal::CheckIn.cron("second")
|
193
|
+
Appsignal::CheckIn.cron("third")
|
194
|
+
|
195
|
+
wait_for("the events to be transmitted") { subject.transmitted == 1 }
|
196
|
+
end
|
197
|
+
|
198
|
+
it "transmits all events at once when stopped" do
|
199
|
+
# Set a short debounce interval of 10 seconds, to make the final wait
|
200
|
+
# fail if it waits for the debounce -- this ensures that what is being
|
201
|
+
# tested is that the events are transmitted when the scheduler is
|
202
|
+
# stopped.
|
203
|
+
stub_const("Appsignal::CheckIn::Scheduler::INITIAL_DEBOUNCE_SECONDS", 10)
|
204
|
+
|
205
|
+
expect(transmitter).to receive(:transmit).with(
|
206
|
+
["first", "second", "third"].map do |identifier|
|
207
|
+
hash_including(
|
208
|
+
:identifier => identifier,
|
209
|
+
:check_in_type => "cron",
|
210
|
+
:kind => "finish"
|
211
|
+
)
|
212
|
+
end, :format => :ndjson
|
213
|
+
)
|
214
|
+
|
215
|
+
Appsignal::CheckIn.cron("first")
|
216
|
+
Appsignal::CheckIn.cron("second")
|
217
|
+
Appsignal::CheckIn.cron("third")
|
218
|
+
|
219
|
+
subject.stop
|
220
|
+
|
221
|
+
wait_for("the events to be transmitted") { subject.transmitted == 1 }
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe "further apart than the short debounce interval" do
|
226
|
+
it "transmits the first event and enqueues future events" do
|
227
|
+
expect(transmitter).to receive(:transmit).with([hash_including(
|
228
|
+
:identifier => "first",
|
229
|
+
:check_in_type => "cron",
|
230
|
+
:kind => "finish"
|
231
|
+
)], :format => :ndjson)
|
232
|
+
|
233
|
+
Appsignal::CheckIn.cron("first")
|
234
|
+
|
235
|
+
wait_for("the first event to be transmitted") { subject.transmitted == 1 }
|
236
|
+
|
237
|
+
Appsignal::CheckIn.cron("second")
|
238
|
+
Appsignal::CheckIn.cron("third")
|
239
|
+
|
240
|
+
expect(subject.events).to match(["second", "third"].map do |identifier|
|
241
|
+
hash_including({
|
242
|
+
:identifier => identifier,
|
243
|
+
:check_in_type => "cron",
|
244
|
+
:kind => "finish"
|
245
|
+
})
|
246
|
+
end)
|
247
|
+
end
|
248
|
+
|
249
|
+
it "transmits the other events after the debounce interval" do
|
250
|
+
expect(transmitter).to receive(:transmit)
|
251
|
+
|
252
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
253
|
+
message.include?("Scheduling cron check-in `first` finish event")
|
254
|
+
end)
|
255
|
+
|
256
|
+
Appsignal::CheckIn.cron("first")
|
257
|
+
|
258
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
259
|
+
message.include?("Transmitted cron check-in `first` finish event")
|
260
|
+
end)
|
261
|
+
|
262
|
+
wait_for("the first event to be transmitted") { subject.transmitted == 1 }
|
263
|
+
|
264
|
+
expect(transmitter).to receive(:transmit).with(
|
265
|
+
["second", "third"].map do |identifier|
|
266
|
+
hash_including(
|
267
|
+
:identifier => identifier,
|
268
|
+
:check_in_type => "cron",
|
269
|
+
:kind => "finish"
|
270
|
+
)
|
271
|
+
end, :format => :ndjson
|
272
|
+
)
|
273
|
+
|
274
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
275
|
+
message.include?("Scheduling cron check-in `second` finish event")
|
276
|
+
end)
|
277
|
+
|
278
|
+
Appsignal::CheckIn.cron("second")
|
279
|
+
|
280
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
281
|
+
message.include?("Scheduling cron check-in `third` finish event")
|
282
|
+
end)
|
283
|
+
|
284
|
+
Appsignal::CheckIn.cron("third")
|
285
|
+
|
286
|
+
expect(subject.events).to_not be_empty
|
287
|
+
|
288
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(
|
289
|
+
"Transmitted 2 check-in events"
|
290
|
+
)
|
291
|
+
|
292
|
+
wait_for("the other events to be transmitted") { subject.transmitted == 2 }
|
293
|
+
|
294
|
+
expect(subject.events).to be_empty
|
295
|
+
end
|
296
|
+
|
297
|
+
it "transmits the other events when stopped" do
|
298
|
+
# Restore the original long debounce interval of 10 seconds, to make
|
299
|
+
# the final wait fail if it waits for the debounce -- this ensures
|
300
|
+
# that what is being tested is that the events are transmitted
|
301
|
+
# immediately when the scheduler is stopped.
|
302
|
+
stub_const("Appsignal::CheckIn::Scheduler::BETWEEN_TRANSMISSIONS_DEBOUNCE_SECONDS", 10)
|
303
|
+
|
304
|
+
expect(transmitter).to receive(:transmit)
|
305
|
+
|
306
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
307
|
+
message.include?("Scheduling cron check-in `first` finish event")
|
308
|
+
end)
|
309
|
+
|
310
|
+
Appsignal::CheckIn.cron("first")
|
311
|
+
|
312
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
313
|
+
message.include?("Transmitted cron check-in `first` finish event")
|
314
|
+
end)
|
315
|
+
|
316
|
+
wait_for("the event to be transmitted") { subject.transmitted == 1 }
|
317
|
+
|
318
|
+
expect(transmitter).to receive(:transmit).with(
|
319
|
+
["second", "third"].map do |identifier|
|
320
|
+
hash_including(
|
321
|
+
:identifier => identifier,
|
322
|
+
:check_in_type => "cron",
|
323
|
+
:kind => "finish"
|
324
|
+
)
|
325
|
+
end, :format => :ndjson
|
326
|
+
)
|
327
|
+
|
328
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
329
|
+
message.include?("Scheduling cron check-in `second` finish event")
|
330
|
+
end)
|
331
|
+
|
332
|
+
Appsignal::CheckIn.cron("second")
|
333
|
+
|
334
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
335
|
+
message.include?("Scheduling cron check-in `third` finish event")
|
336
|
+
end)
|
337
|
+
|
338
|
+
Appsignal::CheckIn.cron("third")
|
339
|
+
|
340
|
+
expect(subject.events).to_not be_empty
|
341
|
+
|
342
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(
|
343
|
+
"Transmitted 2 check-in events"
|
344
|
+
)
|
345
|
+
|
346
|
+
subject.stop
|
347
|
+
|
348
|
+
wait_for("the other events to be transmitted") { subject.transmitted == 2 }
|
349
|
+
|
350
|
+
expect(subject.events).to be_empty
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
describe "when a similar event is sent more than once" do
|
356
|
+
it "only transmits one of the similar events" do
|
357
|
+
# We must instantiate `Appsignal::CheckIn::Cron` directly, as the
|
358
|
+
# `.cron` helper would use a different digest for each invocation.
|
359
|
+
cron = Appsignal::CheckIn::Cron.new(:identifier => "test")
|
360
|
+
|
361
|
+
expect(transmitter).to receive(:transmit).with([hash_including(
|
362
|
+
:identifier => "test",
|
363
|
+
:check_in_type => "cron",
|
364
|
+
:kind => "start"
|
365
|
+
)], :format => :ndjson)
|
366
|
+
|
367
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(
|
368
|
+
"Scheduling cron check-in `test` start event (digest #{cron.digest}) to be transmitted"
|
369
|
+
)
|
370
|
+
|
371
|
+
cron.start
|
372
|
+
|
373
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(
|
374
|
+
"Scheduling cron check-in `test` start event (digest #{cron.digest}) to be transmitted"
|
375
|
+
)
|
376
|
+
|
377
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(
|
378
|
+
"Replacing previously scheduled cron check-in `test` start event (digest #{cron.digest})"
|
379
|
+
)
|
380
|
+
|
381
|
+
cron.start
|
382
|
+
|
383
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(
|
384
|
+
"Transmitted cron check-in `test` start event (digest #{cron.digest})"
|
385
|
+
)
|
386
|
+
|
387
|
+
wait_for("the event to be transmitted") { subject.transmitted == 1 }
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
describe "when the scheduler is stopped" do
|
392
|
+
it "does not schedule any events to be transmitted" do
|
393
|
+
expect(transmitter).not_to receive(:transmit)
|
394
|
+
|
395
|
+
subject.stop
|
396
|
+
|
397
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
398
|
+
message.include?("Cannot transmit cron check-in `test` finish event") &&
|
399
|
+
message.include?("AppSignal is stopped")
|
400
|
+
end)
|
401
|
+
|
402
|
+
Appsignal::CheckIn.cron("test")
|
403
|
+
|
404
|
+
expect(subject.events).to be_empty
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
describe "when AppSignal is not active" do
|
409
|
+
it "does not schedule any events to be transmitted" do
|
410
|
+
allow(Appsignal).to receive(:active?).and_return(false)
|
411
|
+
|
412
|
+
subject.stop
|
413
|
+
|
414
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
415
|
+
message.include?("Cannot transmit cron check-in `test` finish event") &&
|
416
|
+
message.include?("AppSignal is not active")
|
417
|
+
end)
|
418
|
+
|
419
|
+
Appsignal::CheckIn.cron("test")
|
420
|
+
|
421
|
+
expect(subject.events).to be_empty
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
describe "when transmitting returns a non-success response code" do
|
426
|
+
it "logs the error and continues" do
|
427
|
+
expect(transmitter).to receive(:transmit).and_return(
|
428
|
+
Net::HTTPNotFound.new("1.1", 404, "Not Found")
|
429
|
+
)
|
430
|
+
|
431
|
+
Appsignal::CheckIn.cron("first")
|
432
|
+
|
433
|
+
expect(Appsignal.internal_logger).to receive(:error).with(satisfy do |message|
|
434
|
+
message.include?("Failed to transmit cron check-in `first` finish event") &&
|
435
|
+
message.include?("404 status code")
|
436
|
+
end)
|
437
|
+
|
438
|
+
wait_for("the first event to be transmitted") { subject.transmitted == 1 }
|
439
|
+
|
440
|
+
expect(transmitter).to receive(:transmit).and_return(
|
441
|
+
Net::HTTPSuccess.new("1.1", 200, "OK")
|
442
|
+
)
|
443
|
+
|
444
|
+
Appsignal::CheckIn.cron("second")
|
445
|
+
|
446
|
+
expect(Appsignal.internal_logger).not_to receive(:error)
|
447
|
+
|
448
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
449
|
+
message.include?("Transmitted cron check-in `second` finish event")
|
450
|
+
end)
|
451
|
+
|
452
|
+
wait_for("the second event to be transmitted") { subject.transmitted == 2 }
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
describe "when transmitting throws an error" do
|
457
|
+
it "logs the error and continues" do
|
458
|
+
expect(transmitter).to receive(:transmit).and_raise("Something went wrong")
|
459
|
+
|
460
|
+
Appsignal::CheckIn.cron("first")
|
461
|
+
|
462
|
+
expect(Appsignal.internal_logger).to receive(:error).with(satisfy do |message|
|
463
|
+
message.include?("Failed to transmit cron check-in `first` finish event") &&
|
464
|
+
message.include?("Something went wrong")
|
465
|
+
end)
|
466
|
+
|
467
|
+
wait_for("the first event to be transmitted") { subject.transmitted == 1 }
|
468
|
+
|
469
|
+
expect(transmitter).to receive(:transmit).and_return(
|
470
|
+
Net::HTTPSuccess.new("1.1", 200, "OK")
|
471
|
+
)
|
472
|
+
|
473
|
+
Appsignal::CheckIn.cron("second")
|
474
|
+
|
475
|
+
expect(Appsignal.internal_logger).not_to receive(:error)
|
476
|
+
|
477
|
+
expect(Appsignal.internal_logger).to receive(:debug).with(satisfy do |message|
|
478
|
+
message.include?("Transmitted cron check-in `second` finish event")
|
479
|
+
end)
|
480
|
+
|
481
|
+
wait_for("the second event to be transmitted") { subject.transmitted == 2 }
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
@@ -120,7 +120,7 @@ describe Appsignal::Environment do
|
|
120
120
|
|
121
121
|
expect(logs).to be_empty
|
122
122
|
|
123
|
-
|
123
|
+
unless Bundler.rubygems.respond_to?(:all_specs)
|
124
124
|
skip "Using new Bundler version without `all_specs` method"
|
125
125
|
end
|
126
126
|
bundle_gem_specs = silence { ::Bundler.rubygems.all_specs }
|
@@ -80,4 +80,15 @@ describe Appsignal::Hooks::AtExit::AtExitCallback do
|
|
80
80
|
end.to_not change { created_transactions.count }.from(1)
|
81
81
|
end
|
82
82
|
end
|
83
|
+
|
84
|
+
it "doesn't report the error if it is a SignalException exception" do
|
85
|
+
with_error(SignalException, "TERM") do |error|
|
86
|
+
Appsignal.report_error(error)
|
87
|
+
expect(created_transactions.count).to eq(1)
|
88
|
+
|
89
|
+
expect do
|
90
|
+
call_callback
|
91
|
+
end.to_not change { created_transactions.count }.from(1)
|
92
|
+
end
|
93
|
+
end
|
83
94
|
end
|
@@ -5,23 +5,29 @@ describe Appsignal::Rack::BodyWrapper do
|
|
5
5
|
set_current_transaction(transaction)
|
6
6
|
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
8
|
+
it "forwards method calls to the body if the method doesn't exist" do
|
9
|
+
fake_body = double(
|
10
|
+
:body => ["some body"],
|
11
|
+
:some_method => :some_value
|
12
|
+
)
|
13
|
+
|
14
|
+
wrapped = described_class.wrap(fake_body, transaction)
|
15
|
+
expect(wrapped).to respond_to(:body)
|
16
|
+
expect(wrapped.body).to eq(["some body"])
|
17
|
+
|
18
|
+
expect(wrapped).to respond_to(:some_method)
|
19
|
+
expect(wrapped.some_method).to eq(:some_value)
|
20
|
+
end
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
it "doesn't respond to methods the Rack::BodyProxy doesn't respond to" do
|
23
|
+
body = Rack::BodyProxy.new(["body"])
|
24
|
+
wrapped = described_class.wrap(body, transaction)
|
25
|
+
|
26
|
+
expect(wrapped).to_not respond_to(:to_str)
|
27
|
+
expect { wrapped.to_str }.to raise_error(NoMethodError)
|
28
|
+
|
29
|
+
expect(wrapped).to_not respond_to(:body)
|
30
|
+
expect { wrapped.body }.to raise_error(NoMethodError)
|
25
31
|
end
|
26
32
|
|
27
33
|
describe "with a body only supporting each()" do
|
@@ -97,15 +103,17 @@ describe Appsignal::Rack::BodyWrapper do
|
|
97
103
|
end
|
98
104
|
|
99
105
|
describe "with a body supporting both each() and call" do
|
100
|
-
it "wraps with the wrapper that
|
101
|
-
fake_body = double
|
102
|
-
|
103
|
-
|
106
|
+
it "wraps with the wrapper that exposes each" do
|
107
|
+
fake_body = double(
|
108
|
+
:each => true,
|
109
|
+
:call => "original call"
|
110
|
+
)
|
104
111
|
|
105
112
|
wrapped = described_class.wrap(fake_body, transaction)
|
106
113
|
expect(wrapped).to respond_to(:each)
|
107
114
|
expect(wrapped).to_not respond_to(:to_ary)
|
108
|
-
expect(wrapped).
|
115
|
+
expect(wrapped).to respond_to(:call)
|
116
|
+
expect(wrapped.call).to eq("original call")
|
109
117
|
expect(wrapped).to_not respond_to(:to_path)
|
110
118
|
expect(wrapped).to respond_to(:close)
|
111
119
|
end
|
@@ -52,13 +52,41 @@ describe Appsignal::Transmitter do
|
|
52
52
|
}
|
53
53
|
).to_return(:status => 200)
|
54
54
|
end
|
55
|
-
|
55
|
+
|
56
|
+
let(:response) { instance.transmit({ :the => :payload }) }
|
56
57
|
|
57
58
|
it "returns Net::HTTP response" do
|
58
59
|
expect(response).to be_kind_of(Net::HTTPResponse)
|
59
60
|
expect(response.code).to eq "200"
|
60
61
|
end
|
61
62
|
|
63
|
+
describe "with :ndjson format" do
|
64
|
+
before do
|
65
|
+
stub_request(:post, "https://push.appsignal.com/1/action").with(
|
66
|
+
:query => {
|
67
|
+
:api_key => "abc",
|
68
|
+
:environment => "production",
|
69
|
+
:gem_version => Appsignal::VERSION,
|
70
|
+
:hostname => config[:hostname],
|
71
|
+
:name => "TestApp"
|
72
|
+
},
|
73
|
+
:body => "{\"the\":\"payload\"}\n{\"part\":\"two\"}",
|
74
|
+
:headers => {
|
75
|
+
"Content-Type" => "application/x-ndjson; charset=UTF-8"
|
76
|
+
}
|
77
|
+
).to_return(:status => 200)
|
78
|
+
end
|
79
|
+
|
80
|
+
let(:response) do
|
81
|
+
instance.transmit([{ :the => :payload }, { :part => :two }], :format => :ndjson)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "returns Net::HTTP response" do
|
85
|
+
expect(response).to be_kind_of(Net::HTTPResponse)
|
86
|
+
expect(response.code).to eq "200"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
62
90
|
context "with ca_file_path config option set" do
|
63
91
|
context "when file does not exist" do
|
64
92
|
before do
|
@@ -106,7 +134,7 @@ describe Appsignal::Transmitter do
|
|
106
134
|
end
|
107
135
|
|
108
136
|
describe "#http_post" do
|
109
|
-
subject { instance.send(:http_post, "the" => "payload") }
|
137
|
+
subject { instance.send(:http_post, { "the" => "payload" }, :format => :json) }
|
110
138
|
|
111
139
|
it "sets the path" do
|
112
140
|
expect(subject.path).to eq instance.uri.request_uri
|
@@ -115,6 +143,24 @@ describe Appsignal::Transmitter do
|
|
115
143
|
it "sets the correct headers" do
|
116
144
|
expect(subject["Content-Type"]).to eq "application/json; charset=UTF-8"
|
117
145
|
end
|
146
|
+
|
147
|
+
it "serialises the payload to JSON" do
|
148
|
+
expect(subject.body).to eq "{\"the\":\"payload\"}"
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "with :ndjson format" do
|
152
|
+
subject do
|
153
|
+
instance.send(:http_post, [{ "the" => "payload" }, { "part" => "two" }], :format => :ndjson)
|
154
|
+
end
|
155
|
+
|
156
|
+
it "sets the correct headers" do
|
157
|
+
expect(subject["Content-Type"]).to eq "application/x-ndjson; charset=UTF-8"
|
158
|
+
end
|
159
|
+
|
160
|
+
it "serialises the payload to NDJSON" do
|
161
|
+
expect(subject.body).to eq "{\"the\":\"payload\"}\n{\"part\":\"two\"}"
|
162
|
+
end
|
163
|
+
end
|
118
164
|
end
|
119
165
|
|
120
166
|
describe "#http_client" do
|
data/spec/lib/appsignal_spec.rb
CHANGED
@@ -581,6 +581,11 @@ describe Appsignal do
|
|
581
581
|
expect(Appsignal.active?).to be_falsy
|
582
582
|
end
|
583
583
|
end
|
584
|
+
|
585
|
+
it "calls stop on the check-in scheduler" do
|
586
|
+
expect(Appsignal::CheckIn.scheduler).to receive(:stop)
|
587
|
+
Appsignal.stop
|
588
|
+
end
|
584
589
|
end
|
585
590
|
|
586
591
|
describe ".started?" do
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module TakeAtMostHelper
|
2
|
+
# Assert that it takes at most a certain amount of time to run a block.
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
# # Assert that it takes at most 1 second to run the block
|
6
|
+
# take_at_most(1) { sleep 0.5 }
|
7
|
+
#
|
8
|
+
# @param time [Integer, Float] The maximum amount of time the block is allowed to
|
9
|
+
# run in seconds.
|
10
|
+
# @yield Block to run.
|
11
|
+
# @raise [StandardError] Raises error if the block takes longer than the
|
12
|
+
# specified time to run.
|
13
|
+
def take_at_most(time)
|
14
|
+
start = Time.now
|
15
|
+
yield
|
16
|
+
elapsed = Time.now - start
|
17
|
+
return if elapsed <= time
|
18
|
+
|
19
|
+
raise "Expected block to take at most #{time} seconds, but took #{elapsed}"
|
20
|
+
end
|
21
|
+
end
|