appsignal 1.2.5 → 1.3.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -0
  3. data/circle.yml +4 -0
  4. data/ext/agent.yml +11 -11
  5. data/ext/appsignal_extension.c +105 -40
  6. data/lib/appsignal.rb +18 -6
  7. data/lib/appsignal/cli/install.rb +3 -3
  8. data/lib/appsignal/config.rb +19 -5
  9. data/lib/appsignal/event_formatter.rb +3 -2
  10. data/lib/appsignal/event_formatter/elastic_search/search_formatter.rb +1 -1
  11. data/lib/appsignal/event_formatter/mongo_ruby_driver/query_formatter.rb +1 -1
  12. data/lib/appsignal/event_formatter/moped/query_formatter.rb +13 -6
  13. data/lib/appsignal/hooks/mongo_ruby_driver.rb +0 -1
  14. data/lib/appsignal/hooks/net_http.rb +2 -5
  15. data/lib/appsignal/hooks/redis.rb +1 -1
  16. data/lib/appsignal/hooks/sequel.rb +8 -4
  17. data/lib/appsignal/integrations/mongo_ruby_driver.rb +1 -1
  18. data/lib/appsignal/integrations/object.rb +35 -0
  19. data/lib/appsignal/integrations/resque.rb +5 -0
  20. data/lib/appsignal/integrations/sinatra.rb +2 -2
  21. data/lib/appsignal/minutely.rb +41 -0
  22. data/lib/appsignal/params_sanitizer.rb +4 -105
  23. data/lib/appsignal/rack/sinatra_instrumentation.rb +25 -2
  24. data/lib/appsignal/transaction.rb +41 -15
  25. data/lib/appsignal/transmitter.rb +1 -1
  26. data/lib/appsignal/utils.rb +42 -47
  27. data/lib/appsignal/utils/params_sanitizer.rb +58 -0
  28. data/lib/appsignal/utils/query_params_sanitizer.rb +54 -0
  29. data/lib/appsignal/version.rb +1 -1
  30. data/spec/lib/appsignal/config_spec.rb +12 -2
  31. data/spec/lib/appsignal/extension_spec.rb +4 -0
  32. data/spec/lib/appsignal/hooks/net_http_spec.rb +20 -28
  33. data/spec/lib/appsignal/hooks/redis_spec.rb +9 -11
  34. data/spec/lib/appsignal/integrations/object_spec.rb +211 -0
  35. data/spec/lib/appsignal/integrations/sinatra_spec.rb +2 -2
  36. data/spec/lib/appsignal/minutely_spec.rb +54 -0
  37. data/spec/lib/appsignal/rack/sinatra_instrumentation_spec.rb +50 -10
  38. data/spec/lib/appsignal/subscriber_spec.rb +5 -6
  39. data/spec/lib/appsignal/transaction_spec.rb +102 -23
  40. data/spec/lib/appsignal/transmitter_spec.rb +1 -1
  41. data/spec/lib/appsignal/utils/params_sanitizer_spec.rb +122 -0
  42. data/spec/lib/appsignal/utils/query_params_sanitizer_spec.rb +194 -0
  43. data/spec/lib/appsignal/utils_spec.rb +13 -76
  44. data/spec/lib/appsignal_spec.rb +82 -13
  45. metadata +15 -11
  46. data/lib/appsignal/event_formatter/net_http/request_formatter.rb +0 -13
  47. data/lib/appsignal/event_formatter/sequel/sql_formatter.rb +0 -13
  48. data/spec/lib/appsignal/event_formatter/net_http/request_formatter_spec.rb +0 -26
  49. data/spec/lib/appsignal/event_formatter/sequel/sql_formatter_spec.rb +0 -22
  50. data/spec/lib/appsignal/params_sanitizer_spec.rb +0 -200
@@ -6,7 +6,7 @@ describe Appsignal::Transmitter do
6
6
  let(:instance) { Appsignal::Transmitter.new(action, config) }
7
7
 
8
8
  describe "#uri" do
9
- before { Socket.stub(:gethostname => 'app1.local') }
9
+ before { ENV['APPSIGNAL_HOSTNAME'] = 'app1.local' }
10
10
 
11
11
  subject { instance.uri.to_s }
12
12
 
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Utils::ParamsSanitizer do
4
+ let(:file) { uploaded_file }
5
+ let(:params) do
6
+ {
7
+ :text => 'string',
8
+ 'string' => 'string key value',
9
+ :file => file,
10
+ :float => 0.0,
11
+ :bool_true => true,
12
+ :bool_false => false,
13
+ :nil => nil,
14
+ :int => 1,
15
+ :hash => {
16
+ :nested_text => 'string',
17
+ :nested_array => [
18
+ 'something',
19
+ 'else',
20
+ file,
21
+ {
22
+ :key => 'value',
23
+ :file => file
24
+ }
25
+ ]
26
+ }
27
+ }
28
+ end
29
+
30
+ describe ".sanitize" do
31
+ let(:sanitized_params) { described_class.sanitize(params) }
32
+ subject { sanitized_params }
33
+
34
+ it { should be_instance_of Hash }
35
+ its([:text]) { should eq('string') }
36
+ its(['string']) { should eq('string key value') }
37
+ its([:file]) { should be_instance_of String }
38
+ its([:file]) { should include '::UploadedFile' }
39
+ its([:float]) { should eq(0.0) }
40
+ its([:bool_true]) { should be(true) }
41
+ its([:bool_false]) { should be(false) }
42
+ its([:nil]) { should be_nil }
43
+ its([:int]) { should eq(1) }
44
+
45
+ it "does not change the original params" do
46
+ subject
47
+ params[:file].should eq(file)
48
+ params[:hash][:nested_array][2].should eq(file)
49
+ end
50
+
51
+ describe ":hash" do
52
+ subject { sanitized_params[:hash] }
53
+
54
+ it { should be_instance_of Hash }
55
+ its([:nested_text]) { should eq('string') }
56
+
57
+ describe ":nested_array" do
58
+ subject { sanitized_params[:hash][:nested_array] }
59
+
60
+ it { should be_instance_of Array }
61
+ its([0]) { should eq('something') }
62
+ its([1]) { should eq('else') }
63
+ its([2]) { should be_instance_of String }
64
+ its([2]) { should include '::UploadedFile' }
65
+
66
+ describe ":nested_hash" do
67
+ subject { sanitized_params[:hash][:nested_array][3] }
68
+
69
+ it { should be_instance_of Hash }
70
+ its([:key]) { should eq('value') }
71
+ its([:file]) { should be_instance_of String }
72
+ its([:file]) { should include '::UploadedFile' }
73
+ end
74
+ end
75
+ end
76
+
77
+ context "with :filter_parameters option" do
78
+ let(:sanitized_params) do
79
+ described_class.sanitize(params, :filter_parameters => %w(text hash))
80
+ end
81
+ subject { sanitized_params }
82
+
83
+ its([:text]) { should eq(described_class::FILTERED) }
84
+ its([:hash]) { should eq(described_class::FILTERED) }
85
+ its([:file]) { should be_instance_of String }
86
+ its([:file]) { should include '::UploadedFile' }
87
+ its([:float]) { should eq(0.0) }
88
+ its([:bool_true]) { should be(true) }
89
+ its([:bool_false]) { should be(false) }
90
+ its([:nil]) { should be_nil }
91
+ its([:int]) { should eq(1) }
92
+
93
+ context "with strings as key filter values" do
94
+ let(:sanitized_params) do
95
+ described_class.sanitize(params, :filter_parameters => %w(string))
96
+ end
97
+
98
+ its(['string']) { should eq('[FILTERED]') }
99
+ end
100
+
101
+ describe ":hash" do
102
+ let(:sanitized_params) do
103
+ described_class.sanitize(params, :filter_parameters => %w(nested_text))
104
+ end
105
+ subject { sanitized_params[:hash] }
106
+
107
+ its([:nested_text]) { should eq('[FILTERED]') }
108
+
109
+ describe ":nested_array" do
110
+ describe ":nested_hash" do
111
+ let(:sanitized_params) do
112
+ described_class.sanitize(params, :filter_parameters => %w(key))
113
+ end
114
+ subject { sanitized_params[:hash][:nested_array][3] }
115
+
116
+ its([:key]) { should eq('[FILTERED]') }
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,194 @@
1
+ require 'spec_helper'
2
+
3
+ describe Appsignal::Utils::QueryParamsSanitizer do
4
+ describe ".sanitize" do
5
+ context "when only_top_level = true" do
6
+ subject { described_class.sanitize(value, true) }
7
+
8
+ context "when value is a hash" do
9
+ let(:value) { {'foo' => 'bar'} }
10
+
11
+ it "should only return the first level of the object" do
12
+ expect(subject).to eq('foo' => '?')
13
+ end
14
+
15
+ it "should not modify source value" do
16
+ subject
17
+ expect(value).to eq('foo' => 'bar')
18
+ end
19
+ end
20
+
21
+ context "when value is a nested hash" do
22
+ let(:value) { {'foo' => { 'bar' => 'baz' }} }
23
+
24
+ it "should only return the first level of the object" do
25
+ expect(subject).to eq('foo' => '?')
26
+ end
27
+
28
+ it "should not modify source value" do
29
+ subject
30
+ expect(value).to eq('foo' => { 'bar' => 'baz' })
31
+ end
32
+ end
33
+
34
+ context "when value is an array of hashes" do
35
+ let(:value) { ['foo' => 'bar'] }
36
+
37
+ it "should sanitize all hash values with a questionmark" do
38
+ expect(subject).to eq('foo' => '?')
39
+ end
40
+
41
+ it "should not modify source value" do
42
+ subject
43
+ expect(value).to eq(['foo' => 'bar'])
44
+ end
45
+ end
46
+
47
+ context "when value is an array" do
48
+ let(:value) { ['foo', 'bar'] }
49
+
50
+ it "should only return the first level of the object" do
51
+ expect(subject).to eq('?')
52
+ end
53
+
54
+ it "should not modify source value" do
55
+ subject
56
+ expect(value).to eq(['foo', 'bar'])
57
+ end
58
+ end
59
+
60
+ context "when value is a mixed array" do
61
+ let(:value) { [nil, 'foo', 'bar'] }
62
+
63
+ it "should sanitize all hash values with a single questionmark" do
64
+ expect(subject).to eq('?')
65
+ end
66
+ end
67
+
68
+ context "when value is a string" do
69
+ let(:value) { 'foo' }
70
+
71
+ it "should sanitize all hash values with a questionmark" do
72
+ expect(subject).to eq('?')
73
+ end
74
+ end
75
+ end
76
+
77
+ context "when only_top_level = false" do
78
+ subject { described_class.sanitize(value, false) }
79
+
80
+ context "when value is a hash" do
81
+ let(:value) { {'foo' => 'bar'} }
82
+
83
+ it "should sanitize all hash values with a questionmark" do
84
+ expect(subject).to eq('foo' => '?')
85
+ end
86
+
87
+ it "should not modify source value" do
88
+ subject
89
+ expect(value).to eq('foo' => 'bar')
90
+ end
91
+ end
92
+
93
+ context "when value is a nested hash" do
94
+ let(:value) { {'foo' => { 'bar' => 'baz' }} }
95
+
96
+ it "should replaces values" do
97
+ expect(subject).to eq('foo' => {'bar' => '?'})
98
+ end
99
+
100
+ it "should not modify source value" do
101
+ subject
102
+ expect(value).to eq('foo' => {'bar' => 'baz'})
103
+ end
104
+ end
105
+
106
+ context "when value is an array of hashes" do
107
+ let(:value) { ['foo' => 'bar'] }
108
+
109
+ it "should sanitize all hash values with a questionmark" do
110
+ expect(subject).to eq(['foo' => '?'])
111
+ end
112
+
113
+ it "should not modify source value" do
114
+ subject
115
+ expect(value).to eq(['foo' => 'bar'])
116
+ end
117
+ end
118
+
119
+ context "when value is an array" do
120
+ let(:value) { ['foo', 'bar'] }
121
+
122
+ it "should sanitize all hash values with a single questionmark" do
123
+ expect(subject).to eq(['?'])
124
+ end
125
+ end
126
+
127
+ context "when value is a mixed array" do
128
+ let(:value) { [nil, 'foo', 'bar'] }
129
+
130
+ it "should sanitize all hash values with a single questionmark" do
131
+ expect(subject).to eq(['?'])
132
+ end
133
+ end
134
+
135
+ context "when value is a string" do
136
+ let(:value) { 'bar' }
137
+
138
+ it "should sanitize all hash values with a questionmark" do
139
+ expect(subject).to eq('?')
140
+ end
141
+ end
142
+ end
143
+ end
144
+
145
+ describe "key_sanitizer option" do
146
+ context "without key_sanitizer" do
147
+ subject { described_class.sanitize(value) }
148
+
149
+ context "when dots are in the key" do
150
+ let(:value) { {'foo.bar' => 'bar'} }
151
+
152
+ it "should not sanitize the key" do
153
+ expect(subject).to eql('foo.bar' => '?')
154
+ end
155
+ end
156
+
157
+ context "when key is a symbol" do
158
+ let(:value) { {:ismaster => 'bar'} }
159
+
160
+ it "should sanitize the key" do
161
+ expect(subject).to eql(:ismaster => '?')
162
+ end
163
+ end
164
+ end
165
+
166
+ context "with mongodb key_sanitizer" do
167
+ subject { described_class.sanitize(value, false, :mongodb) }
168
+
169
+ context "when no dots are in the key" do
170
+ let(:value) { {'foo' => 'bar'} }
171
+
172
+ it "should not sanitize the key" do
173
+ expect(subject).to eql('foo' => '?')
174
+ end
175
+ end
176
+
177
+ context "when dots are in the key" do
178
+ let(:value) { {'foo.bar' => 'bar'} }
179
+
180
+ it "should sanitize the key" do
181
+ expect(subject).to eql('foo.?' => '?')
182
+ end
183
+ end
184
+
185
+ context "when key is a symbol" do
186
+ let(:value) { {:ismaster => 'bar'} }
187
+
188
+ it "should sanitize the key" do
189
+ expect(subject).to eql('ismaster' => '?')
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
@@ -3,82 +3,29 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe Appsignal::Utils do
6
- describe ".sanitize" do
7
- context "when params is a hash" do
8
- let(:params) { {'foo' => 'bar'} }
9
-
10
- it "should sanitize all hash values with a questionmark" do
11
- expect( Appsignal::Utils.sanitize(params) ).to eq('foo' => '?')
12
- end
13
- end
14
-
15
- context "when params is an array of hashes" do
16
- let(:params) { [{'foo' => 'bar'}] }
17
-
18
- it "should sanitize all hash values with a questionmark" do
19
- expect( Appsignal::Utils.sanitize(params) ).to eq([{'foo' => '?'}])
20
- end
21
- end
22
-
23
- context "when params is an array of strings" do
24
- let(:params) { ['foo', 'bar'] }
25
-
26
- it "should sanitize all hash values with a single questionmark" do
27
- expect( Appsignal::Utils.sanitize(params) ).to eq(['?'])
28
- end
29
- end
30
-
31
- context "when params is a mixed array" do
32
- let(:params) { [nil, 'foo', 'bar'] }
33
-
34
- it "should sanitize all hash values with a single questionmark" do
35
- expect( Appsignal::Utils.sanitize(params) ).to eq(['?'])
36
- end
37
- end
38
-
39
- context "when params is a string" do
40
- let(:params) { 'bar'}
41
-
42
- it "should sanitize all hash values with a questionmark" do
43
- expect( Appsignal::Utils.sanitize(params) ).to eq('?')
44
- end
45
- end
46
- end
47
-
48
- describe ".sanitize_key" do
49
- it "should not sanitize key when no key_sanitizer is given" do
50
- expect( Appsignal::Utils.sanitize_key('foo', nil) ).to eql('foo')
51
- end
52
-
53
- context "with mongodb sanitizer" do
54
- it "should not sanitize key when no dots are in the key" do
55
- expect( Appsignal::Utils.sanitize_key('foo', :mongodb) ).to eql('foo')
56
- end
57
-
58
- it "should sanitize key when dots are in the key" do
59
- expect( Appsignal::Utils.sanitize_key('foo.bar', :mongodb) ).to eql('foo.?')
60
- end
61
-
62
- it "should sanitize a symbol" do
63
- expect( Appsignal::Utils.sanitize_key(:ismaster, :mongodb) ).to eql('ismaster')
64
- end
65
- end
66
- end
67
-
68
6
  describe ".json_generate" do
69
7
  subject { Appsignal::Utils.json_generate(body) }
70
8
 
71
9
  context "with a valid body" do
72
- let(:body) { {'the' => 'payload'} }
10
+ let(:body) do
11
+ {
12
+ 'the' => 'payload',
13
+ 1 => true,
14
+ nil => 'test',
15
+ :foo => [1, 2, 'three'],
16
+ 'bar' => nil,
17
+ 'baz' => { 'foo' => 'bar' }
18
+ }
19
+ end
73
20
 
74
- it { should == "{\"the\":\"payload\"}" }
21
+ it { should == %({"the":"payload","1":true,"":"test","foo":[1,2,"three"],"bar":null,"baz":{"foo":"bar"}}) }
75
22
  end
76
23
 
77
24
  context "with a body that contains strings with invalid utf-8 content" do
78
25
  let(:string_with_invalid_utf8) { [0x61, 0x61, 0x85].pack('c*') }
79
26
  let(:body) { {
80
27
  'field_one' => [0x61, 0x61].pack('c*'),
81
- 'field_two' => string_with_invalid_utf8,
28
+ :field_two => string_with_invalid_utf8,
82
29
  'field_three' => [
83
30
  'one', string_with_invalid_utf8
84
31
  ],
@@ -87,17 +34,7 @@ describe Appsignal::Utils do
87
34
  }
88
35
  } }
89
36
 
90
- it { should == "{\"field_one\":\"aa\",\"field_two\":\"aa�\",\"field_three\":[\"one\",\"aa�\"],\"field_four\":{\"one\":\"aa�\"}}" }
91
- end
92
- end
93
-
94
- describe ".encode_utf8" do
95
- subject { Appsignal::Utils.encode_utf8(value) }
96
-
97
- context "value with invalid utf-8 content" do
98
- let(:value) { [0x61, 0x61, 0x85].pack('c*') }
99
-
100
- it { should == "aa�" }
37
+ it { should == %({"field_one":"aa","field_two":"aa","field_three":["one","aa"],"field_four":{"one":"aa"}}) }
101
38
  end
102
39
  end
103
40
  end
@@ -101,6 +101,11 @@ describe Appsignal do
101
101
  Appsignal::Extension.should_receive(:install_gc_event_hooks)
102
102
  Appsignal.start
103
103
  end
104
+
105
+ it "should add the gc probe to minutely" do
106
+ Appsignal::Minutely.should_receive(:add_gc_probe)
107
+ Appsignal.start
108
+ end
104
109
  end
105
110
 
106
111
  context "when allocation tracking and gc instrumentation have been disabled" do
@@ -114,6 +119,33 @@ describe Appsignal do
114
119
  Appsignal::Extension.should_not_receive(:install_gc_event_hooks)
115
120
  Appsignal.start
116
121
  end
122
+
123
+ it "should not add the gc probe to minutely" do
124
+ Appsignal::Minutely.should_not_receive(:add_gc_probe)
125
+ Appsignal.start
126
+ end
127
+ end
128
+
129
+ context "when minutely metrics has been enabled" do
130
+ before do
131
+ Appsignal.config.config_hash[:enable_minutely_probes] = true
132
+ end
133
+
134
+ it "should start minutely" do
135
+ Appsignal::Minutely.should_receive(:start)
136
+ Appsignal.start
137
+ end
138
+ end
139
+
140
+ context "when minutely metrics has been disabled" do
141
+ before do
142
+ Appsignal.config.config_hash[:enable_minutely_probes] = false
143
+ end
144
+
145
+ it "should not start minutely" do
146
+ Appsignal::Minutely.should_not_receive(:start)
147
+ Appsignal.start
148
+ end
117
149
  end
118
150
  end
119
151
 
@@ -266,6 +298,19 @@ describe Appsignal do
266
298
  }.should_not raise_error
267
299
  end
268
300
  end
301
+
302
+ describe ".instrument" do
303
+ it "should not instrument, but still call the block" do
304
+ stub = double
305
+ stub.should_receive(:method_call).and_return('return value')
306
+
307
+ lambda {
308
+ Appsignal.instrument 'name' do
309
+ stub.method_call
310
+ end.should eq 'return value'
311
+ }.should_not raise_error
312
+ end
313
+ end
269
314
  end
270
315
 
271
316
  context "with config and started" do
@@ -393,14 +438,14 @@ describe Appsignal do
393
438
 
394
439
  describe "custom stats" do
395
440
  describe ".set_gauge" do
396
- it "should call set_gauge on the extension with a float" do
441
+ it "should call set_gauge on the extension with a string key and float" do
397
442
  Appsignal::Extension.should_receive(:set_gauge).with('key', 0.1)
398
443
  Appsignal.set_gauge('key', 0.1)
399
444
  end
400
445
 
401
- it "should call set_gauge on the extension with an int" do
446
+ it "should call set_gauge on the extension with a symbol key and int" do
402
447
  Appsignal::Extension.should_receive(:set_gauge).with('key', 1.0)
403
- Appsignal.set_gauge('key', 1)
448
+ Appsignal.set_gauge(:key, 1)
404
449
  end
405
450
 
406
451
  it "should not raise an exception when out of range" do
@@ -413,14 +458,14 @@ describe Appsignal do
413
458
  end
414
459
 
415
460
  describe ".set_host_gauge" do
416
- it "should call set_host_gauge on the extension with a float" do
461
+ it "should call set_host_gauge on the extension with a string key and float" do
417
462
  Appsignal::Extension.should_receive(:set_host_gauge).with('key', 0.1)
418
463
  Appsignal.set_host_gauge('key', 0.1)
419
464
  end
420
465
 
421
- it "should call set_host_gauge on the extension with an int" do
466
+ it "should call set_host_gauge on the extension with a symbol key and int" do
422
467
  Appsignal::Extension.should_receive(:set_host_gauge).with('key', 1.0)
423
- Appsignal.set_host_gauge('key', 1)
468
+ Appsignal.set_host_gauge(:key, 1)
424
469
  end
425
470
 
426
471
  it "should not raise an exception when out of range" do
@@ -433,14 +478,14 @@ describe Appsignal do
433
478
  end
434
479
 
435
480
  describe ".set_process_gauge" do
436
- it "should call set_process_gauge on the extension with a float" do
481
+ it "should call set_process_gauge on the extension with a string key and float" do
437
482
  Appsignal::Extension.should_receive(:set_process_gauge).with('key', 0.1)
438
483
  Appsignal.set_process_gauge('key', 0.1)
439
484
  end
440
485
 
441
- it "should call set_process_gauge on the extension with an int" do
486
+ it "should call set_process_gauge on the extension with a symbol key and int" do
442
487
  Appsignal::Extension.should_receive(:set_process_gauge).with('key', 1.0)
443
- Appsignal.set_process_gauge('key', 1)
488
+ Appsignal.set_process_gauge(:key, 1)
444
489
  end
445
490
 
446
491
  it "should not raise an exception when out of range" do
@@ -453,11 +498,16 @@ describe Appsignal do
453
498
  end
454
499
 
455
500
  describe ".increment_counter" do
456
- it "should call increment_counter on the extension" do
501
+ it "should call increment_counter on the extension with a string key" do
457
502
  Appsignal::Extension.should_receive(:increment_counter).with('key', 1)
458
503
  Appsignal.increment_counter('key')
459
504
  end
460
505
 
506
+ it "should call increment_counter on the extension with a symbol key" do
507
+ Appsignal::Extension.should_receive(:increment_counter).with('key', 1)
508
+ Appsignal.increment_counter(:key)
509
+ end
510
+
461
511
  it "should call increment_counter on the extension with a count" do
462
512
  Appsignal::Extension.should_receive(:increment_counter).with('key', 5)
463
513
  Appsignal.increment_counter('key', 5)
@@ -473,14 +523,14 @@ describe Appsignal do
473
523
  end
474
524
 
475
525
  describe ".add_distribution_value" do
476
- it "should call add_distribution_value on the extension with a float" do
526
+ it "should call add_distribution_value on the extension with a string key and float" do
477
527
  Appsignal::Extension.should_receive(:add_distribution_value).with('key', 0.1)
478
528
  Appsignal.add_distribution_value('key', 0.1)
479
529
  end
480
530
 
481
- it "should call add_distribution_value on the extension with an int" do
531
+ it "should call add_distribution_value on the extension with a symbol key and int" do
482
532
  Appsignal::Extension.should_receive(:add_distribution_value).with('key', 1.0)
483
- Appsignal.add_distribution_value('key', 1)
533
+ Appsignal.add_distribution_value(:key, 1)
484
534
  end
485
535
 
486
536
  it "should not raise an exception when out of range" do
@@ -676,6 +726,25 @@ describe Appsignal do
676
726
  end
677
727
  end
678
728
 
729
+ describe ".instrument" do
730
+ it "should instrument through the transaction" do
731
+ stub = double
732
+ stub.should_receive(:method_call).and_return('return value')
733
+
734
+ transaction.should_receive(:start_event)
735
+ transaction.should_receive(:finish_event).with(
736
+ 'name',
737
+ 'title',
738
+ 'body',
739
+ 0
740
+ )
741
+
742
+ Appsignal.instrument 'name', 'title', 'body' do
743
+ stub.method_call
744
+ end.should eq 'return value'
745
+ end
746
+ end
747
+
679
748
  describe ".without_instrumentation" do
680
749
  let(:transaction) { double }
681
750
  before { Appsignal::Transaction.stub(:current => transaction) }