hoodoo 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,6 +12,6 @@ module Hoodoo
12
12
  # The Hoodoo gem version. If this changes, ensure that the date in
13
13
  # "hoodoo.gemspec" is correct and run "bundle install" (or "update").
14
14
  #
15
- VERSION = '1.7.0'
15
+ VERSION = '1.8.0'
16
16
 
17
17
  end
@@ -1079,5 +1079,22 @@ describe Hoodoo::Client do
1079
1079
  Hoodoo::Client.new
1080
1080
  }.to raise_error( RuntimeError, 'Hoodoo::Client: Please pass one of the "discoverer", "base_uri", "drb_uri" or "drb_port" parameters.' )
1081
1081
  end
1082
+
1083
+ context 'with ActiveSupport absent' do
1084
+ before :all do
1085
+ @old_discoverer = Hoodoo::Services::Discovery::ByConvention
1086
+ Hoodoo::Services::Discovery.send( :remove_const, :ByConvention )
1087
+ end
1088
+
1089
+ it 'lead to a useful exception if the ByDiscovery discoverer is requested' do
1090
+ expect {
1091
+ Hoodoo::Client.new( base_uri: 'http://localhost' )
1092
+ }.to raise_error( RuntimeError, 'Hoodoo::Client: The constructor parameters indicate the use of a "by convention" discoverer. This discoverer requires ActiveSupport; ensure the ActiveSupport gem is present and "require"-able.' )
1093
+ end
1094
+
1095
+ after :all do
1096
+ Hoodoo::Services::Discovery.send( :const_set, :ByConvention, @old_discoverer )
1097
+ end
1098
+ end
1082
1099
  end
1083
1100
  end
@@ -0,0 +1,265 @@
1
+ require 'spec_helper.rb'
2
+ require 'benchmark'
3
+
4
+ describe Hoodoo::Monkey do
5
+
6
+ BENCHMARK_ITERATIONS = 500000
7
+
8
+ # Have a base class which we will subclass in 'context's below. Define a
9
+ # different-signature instance and class method to test that the patched
10
+ # code still passes parameters and blocks properly. They're both the same
11
+ # name to make sure there's no accidental name-based overwiting of things
12
+ # to do with class versus instance methods anywhere.
13
+ #
14
+ class Foo
15
+ def bar( a, b, c, &block )
16
+ a + b + c + yield
17
+ end
18
+
19
+ def self.bar( x, &block )
20
+ x + yield
21
+ end
22
+ end
23
+
24
+ # Define patches for the above instance and class methods which double the
25
+ # returned value via calling 'super', testing the method chain in passing
26
+ # and providing an easily detectable value to see if the original, or the
27
+ # patched method has been called.
28
+ #
29
+ module InstanceExtendedFoo
30
+ module InstanceExtensions
31
+ def bar( a, b, c, &block )
32
+ super * 2
33
+ end
34
+ end
35
+ end
36
+
37
+ module ClassExtendedFoo
38
+ module ClassExtensions
39
+ def bar( x, &block )
40
+ super * 2
41
+ end
42
+ end
43
+ end
44
+
45
+ module ExtendedFoo
46
+ module InstanceExtensions
47
+ def bar( a, b, c, &block )
48
+ super * 2
49
+ end
50
+ end
51
+
52
+ module ClassExtensions
53
+ def bar( x, &block )
54
+ super * 2
55
+ end
56
+ end
57
+ end
58
+
59
+ # The next few contexts run a single "it" case because execution order is
60
+ # important. They each iterate the test set several times to prove that
61
+ # enable/disable cycles work properly.
62
+ #
63
+ context 'instance method patching' do
64
+
65
+ # Just register the instance extensions here.
66
+
67
+ class FooForInstanceTests < Foo; end
68
+
69
+ before :all do
70
+ Hoodoo::Monkey.register(
71
+ target_unit: FooForInstanceTests,
72
+ extension_module: InstanceExtendedFoo
73
+ )
74
+ end
75
+
76
+ it 'works' do
77
+ expect( FooForInstanceTests.ancestors ).to_not include( InstanceExtendedFoo::InstanceExtensions )
78
+
79
+ 1.upto( 10 ) do
80
+ expect( FooForInstanceTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
81
+ expect( FooForInstanceTests.bar( 1 ) { 2 } ).to eq( 3 )
82
+
83
+ Hoodoo::Monkey.enable( extension_module: InstanceExtendedFoo )
84
+
85
+ expect( FooForInstanceTests.ancestors ).to include( InstanceExtendedFoo::InstanceExtensions )
86
+
87
+ expect( FooForInstanceTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 36 )
88
+ expect( FooForInstanceTests.bar( 1 ) { 2 } ).to eq( 3 )
89
+
90
+ Hoodoo::Monkey.disable( extension_module: InstanceExtendedFoo )
91
+ end
92
+ end
93
+ end
94
+
95
+ context 'class method patching' do
96
+
97
+ # Just register the class extensions here.
98
+
99
+ class FooForClassTests < Foo; end
100
+
101
+ before :all do
102
+ Hoodoo::Monkey.register(
103
+ target_unit: FooForClassTests,
104
+ extension_module: ClassExtendedFoo
105
+ )
106
+ end
107
+
108
+ it 'works' do
109
+ expect( FooForClassTests.singleton_class.ancestors ).to_not include( ClassExtendedFoo::ClassExtensions )
110
+
111
+ 1.upto( 10 ) do
112
+ expect( FooForClassTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
113
+ expect( FooForClassTests.bar( 1 ) { 2 } ).to eq( 3 )
114
+
115
+ Hoodoo::Monkey.enable( extension_module: ClassExtendedFoo )
116
+
117
+ expect( FooForClassTests.singleton_class.ancestors ).to include( ClassExtendedFoo::ClassExtensions )
118
+
119
+ expect( FooForClassTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
120
+ expect( FooForClassTests.bar( 1 ) { 2 } ).to eq( 6 )
121
+
122
+ Hoodoo::Monkey.disable( extension_module: ClassExtendedFoo )
123
+ end
124
+ end
125
+ end
126
+
127
+ context 'instance and class method patching' do
128
+
129
+ # Register the class and instance extensions here.
130
+
131
+ class FooForBothTests < Foo; end
132
+
133
+ before :all do
134
+ Hoodoo::Monkey.register(
135
+ target_unit: FooForBothTests,
136
+ extension_module: ExtendedFoo
137
+ )
138
+ end
139
+
140
+ it 'works' do
141
+ expect( FooForBothTests.ancestors ).to_not include( ExtendedFoo::InstanceExtensions )
142
+ expect( FooForBothTests.singleton_class.ancestors ).to_not include( ExtendedFoo::ClassExtensions )
143
+
144
+ 1.upto( 10 ) do
145
+ expect( FooForBothTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
146
+ expect( FooForBothTests.bar( 1 ) { 2 } ).to eq( 3 )
147
+
148
+ Hoodoo::Monkey.enable( extension_module: ExtendedFoo )
149
+
150
+ expect( FooForBothTests.ancestors ).to include( ExtendedFoo::InstanceExtensions )
151
+ expect( FooForBothTests.singleton_class.ancestors ).to include( ExtendedFoo::ClassExtensions )
152
+
153
+ expect( FooForBothTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 36 )
154
+ expect( FooForBothTests.bar( 1 ) { 2 } ).to eq( 6 )
155
+
156
+ Hoodoo::Monkey.disable( extension_module: ExtendedFoo )
157
+ end
158
+ end
159
+ end
160
+
161
+ # A whole bunch of fiddly tests for different misuse or edge use cases.
162
+ #
163
+ context 'errant calls' do
164
+ before :each do
165
+ expect( Foo.ancestors ).to_not include( ExtendedFoo::InstanceExtensions )
166
+ expect( Foo.singleton_class.ancestors ).to_not include( ExtendedFoo::ClassExtensions )
167
+ end
168
+
169
+ module NoIntanceOrClassExtensionsDefinedInside
170
+ end
171
+
172
+ it 'fault no-patch registration' do
173
+ expect {
174
+ Hoodoo::Monkey.register(
175
+ target_unit: Foo,
176
+ extension_module: NoIntanceOrClassExtensionsDefinedInside
177
+ )
178
+ }.to raise_exception( RuntimeError, "Hoodoo::Monkey::register: You must define either an InstanceExtensions module ClassExtensions module or both inside 'NoIntanceOrClassExtensionsDefinedInside'" )
179
+ end
180
+
181
+ context 'fault unrecognised' do
182
+
183
+ class FooForUnrecognisedTests < Foo; end
184
+
185
+ before :all do
186
+ Hoodoo::Monkey.register(
187
+ target_unit: FooForUnrecognisedTests,
188
+ extension_module: ExtendedFoo
189
+ )
190
+ end
191
+
192
+ it 'extension module enables' do
193
+ expect {
194
+ Hoodoo::Monkey.enable( extension_module: ExtendedFoo::InstanceExtensions )
195
+ }.to raise_exception( RuntimeError, "Hoodoo::Monkey::enable: Extension module 'ExtendedFoo::InstanceExtensions' is not registered" )
196
+ end
197
+
198
+ it 'extension module disables' do
199
+ expect {
200
+ Hoodoo::Monkey.disable( extension_module: ExtendedFoo::InstanceExtensions )
201
+ }.to raise_exception( RuntimeError, "Hoodoo::Monkey::disable: Extension module 'ExtendedFoo::InstanceExtensions' is not registered" )
202
+ end
203
+ end
204
+
205
+ class FooForSuccessiveEnableTests < Foo; end
206
+
207
+ it 'survive multiple successive enables' do
208
+ Hoodoo::Monkey.register(
209
+ target_unit: FooForSuccessiveEnableTests,
210
+ extension_module: ExtendedFoo
211
+ )
212
+
213
+ expect( FooForSuccessiveEnableTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
214
+ expect( FooForSuccessiveEnableTests.bar( 1 ) { 2 } ).to eq( 3 )
215
+
216
+ 1.upto( 10 ) do
217
+ Hoodoo::Monkey.enable( extension_module: ExtendedFoo )
218
+ end
219
+
220
+ expect( FooForSuccessiveEnableTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 36 )
221
+ expect( FooForSuccessiveEnableTests.bar( 1 ) { 2 } ).to eq( 6 )
222
+ end
223
+
224
+ class FooForSuccessiveDisableTests < Foo; end
225
+
226
+ it 'survive multiple successive disables' do
227
+ Hoodoo::Monkey.register(
228
+ target_unit: FooForSuccessiveDisableTests,
229
+ extension_module: ExtendedFoo
230
+ )
231
+
232
+ expect( FooForSuccessiveDisableTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
233
+ expect( FooForSuccessiveDisableTests.bar( 1 ) { 2 } ).to eq( 3 )
234
+
235
+ Hoodoo::Monkey.enable( extension_module: ExtendedFoo )
236
+
237
+ expect( FooForSuccessiveDisableTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 36 )
238
+ expect( FooForSuccessiveDisableTests.bar( 1 ) { 2 } ).to eq( 6 )
239
+
240
+ 1.upto( 10 ) do
241
+ Hoodoo::Monkey.disable( extension_module: ExtendedFoo )
242
+ end
243
+
244
+ expect( FooForSuccessiveDisableTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
245
+ expect( FooForSuccessiveDisableTests.bar( 1 ) { 2 } ).to eq( 3 )
246
+ end
247
+
248
+ class FooForNotPreviouslyEnabledTests < Foo; end
249
+
250
+ it 'survive being disabled when not previously enabled' do
251
+ Hoodoo::Monkey.register(
252
+ target_unit: FooForNotPreviouslyEnabledTests,
253
+ extension_module: ExtendedFoo
254
+ )
255
+
256
+ expect( FooForNotPreviouslyEnabledTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
257
+ expect( FooForNotPreviouslyEnabledTests.bar( 1 ) { 2 } ).to eq( 3 )
258
+
259
+ Hoodoo::Monkey.disable( extension_module: ExtendedFoo )
260
+
261
+ expect( FooForNotPreviouslyEnabledTests.new.bar( 3, 4, 5 ) { 6 } ).to eq( 18 )
262
+ expect( FooForNotPreviouslyEnabledTests.bar( 1 ) { 2 } ).to eq( 3 )
263
+ end
264
+ end
265
+ end
@@ -0,0 +1,160 @@
1
+ require 'spec_helper.rb'
2
+
3
+ # We need to run these tests in order. First a bunch of shared examples
4
+ # run. Then we check to see if hook methods were invoked in passing. You
5
+ # can't do that in an 'after' hook as exceptions therein cause warnings
6
+ # to be printed, but don't cause test failures.
7
+ #
8
+ describe Hoodoo::Monkey::Patch::NewRelicTracedAMQP, :order => :defined do
9
+
10
+ # Not every test in the 'an AMQP-based middleware/client endpoint' shared
11
+ # example group will provoke a request. We cannot expect to have the
12
+ # NewRelic hooks called for every example. We *can* expect them to be
13
+ # called several times though, so use 'allow' to track those calls and
14
+ # count them.
15
+ #
16
+ before :all do
17
+ @@endpoint_do_amqp_count = 0
18
+ @@newrelic_crossapp_count = 0
19
+ @@newrelic_agent_disable_count = 0
20
+
21
+ module NewRelic
22
+ class Agent
23
+ class CrossAppTracing
24
+ end
25
+ end
26
+ end
27
+
28
+ Hoodoo::Monkey.enable( extension_module: Hoodoo::Monkey::Patch::NewRelicTracedAMQP )
29
+ end
30
+
31
+ after :all do
32
+ Hoodoo::Monkey.disable( extension_module: Hoodoo::Monkey::Patch::NewRelicTracedAMQP )
33
+ Object.send( :remove_const, :NewRelic )
34
+ end
35
+
36
+ before :each do
37
+ expect( Hoodoo::Client::Endpoint::AMQP.ancestors ).to include( Hoodoo::Monkey::Patch::NewRelicTracedAMQP::InstanceExtensions )
38
+
39
+ original_do_amqp = Hoodoo::Client::Endpoint::AMQP.instance_method( :do_amqp )
40
+
41
+ # Count the number of times the AMQP endpoint's non-patched private
42
+ # "do_amqp" method is called. This calls through to the monkey patch
43
+ # under test, so we expect an equal number of calls to the NewRelic
44
+ # methods - *except* that "do_amqp" can deliberately raise an
45
+ # exception and there is test coverage for it. Thus, only increment
46
+ # the count if the call was successful - no exception raised.
47
+ #
48
+ allow_any_instance_of( Hoodoo::Client::Endpoint::AMQP ).to receive( :do_amqp ) do | instance, description_of_request |
49
+ result = original_do_amqp.bind( instance ).call( description_of_request )
50
+ @@endpoint_do_amqp_count += 1
51
+ result
52
+ end
53
+
54
+ allow( NewRelic::Agent::CrossAppTracing ).to receive( :tl_trace_http_request ) do | newrelic_request, &block |
55
+ @@newrelic_crossapp_count += 1
56
+
57
+ expect( newrelic_request ).to be_a( Hoodoo::Monkey::Patch::NewRelicTracedAMQP::AMQPNewRelicRequestWrapper )
58
+ block.call()
59
+ end
60
+
61
+ allow( NewRelic::Agent ).to receive( :disable_all_tracing ) do | &block |
62
+ @@newrelic_agent_disable_count += 1
63
+
64
+ result = block.call()
65
+ expect( result ).to be_a( Hoodoo::Monkey::Patch::NewRelicTracedAMQP::AMQPNewRelicResponseWrapper )
66
+ result
67
+ end
68
+ end
69
+
70
+ it_behaves_like 'an AMQP-based middleware/client endpoint'
71
+
72
+ context 'afterwards' do
73
+ it 'has non-zero NewRelic method call counts' do
74
+ expect( @@endpoint_do_amqp_count ).to be > 5
75
+ expect( @@newrelic_crossapp_count ).to eq( @@endpoint_do_amqp_count )
76
+ expect( @@newrelic_agent_disable_count ).to eq( @@endpoint_do_amqp_count )
77
+ end
78
+ end
79
+ end
80
+
81
+ describe Hoodoo::Monkey::Patch::NewRelicTracedAMQP::AMQPNewRelicRequestWrapper do
82
+ before :each do
83
+ @full_uri = 'https://test.com:8080/1/Person/1234?_embed=Birthday'
84
+ @http_message = {
85
+ 'scheme' => 'http',
86
+ 'verb' => 'GET',
87
+
88
+ 'host' => 'test.com',
89
+ 'port' => '8080',
90
+ 'path' => '/1/Person/1234',
91
+ 'query' => { '_embed' => 'Birthday' },
92
+
93
+ 'headers' => { 'CONTENT_TYPE' => 'application/json; charset=utf-8' },
94
+ 'body' => ''
95
+ }
96
+
97
+ @wrapper = described_class.new( @http_message, @full_uri )
98
+ end
99
+
100
+ it 'reports the correct "type" value' do
101
+ expect( @wrapper.type ).to eq( 'Hoodoo::Monkey::Patch::NewRelicTracedAMQP::AMQPNewRelicRequestWrapper' )
102
+ end
103
+
104
+ it 'reports the correct "host" value' do
105
+ expect( @wrapper.host ).to eq( @http_message[ 'host' ] )
106
+ end
107
+
108
+ it 'reports the correct "method" value' do
109
+ expect( @wrapper.method ).to eq( @http_message[ 'verb' ] )
110
+ end
111
+
112
+ it 'reports the correct "uri" value' do
113
+ expect( @wrapper.uri ).to eq( @full_uri )
114
+ end
115
+
116
+ it 'can read headers' do
117
+ expect( @wrapper[ 'CONTENT_TYPE' ] ).to eq( @http_message[ 'headers' ][ 'CONTENT_TYPE' ] )
118
+ end
119
+
120
+ it 'can write headers' do
121
+ @wrapper[ 'HTTP_X_FOO' ] = '23'
122
+ expect( @http_message[ 'headers' ][ 'HTTP_X_FOO' ] ).to eq( '23' )
123
+ end
124
+ end
125
+
126
+ describe Hoodoo::Monkey::Patch::NewRelicTracedAMQP::AMQPNewRelicResponseWrapper do
127
+
128
+ before :all do
129
+ module NewRelic
130
+ class Agent
131
+ class CrossAppTracing
132
+ NR_APPDATA_HEADER = 'X_Foo_AppData'
133
+ end
134
+ end
135
+ end
136
+ end
137
+
138
+ before :each do
139
+ @http_response = {
140
+ 'headers' => { NewRelic::Agent::CrossAppTracing::NR_APPDATA_HEADER => '4321' },
141
+ 'CONTENT_TYPE' => 'application/json; charset=utf-8',
142
+ 'HTTP_X_FOO' => '46'
143
+ }
144
+
145
+ @wrapper = described_class.new( @http_response )
146
+ end
147
+
148
+ after :all do
149
+ Object.send( :remove_const, :NewRelic )
150
+ end
151
+
152
+ it 'accesses the NewRelic NR_APPDATA_HEADER correctly' do
153
+ expect( @wrapper[ NewRelic::Agent::CrossAppTracing::NR_APPDATA_HEADER ] ).to eq( @http_response[ 'headers' ][ NewRelic::Agent::CrossAppTracing::NR_APPDATA_HEADER ] )
154
+ end
155
+
156
+ it 'accesses other headers correctly' do
157
+ expect( @wrapper[ 'CONTENT_TYPE' ] ).to eq( @http_response[ 'CONTENT_TYPE' ] )
158
+ expect( @wrapper[ 'HTTP_X_FOO' ] ).to eq( @http_response[ 'HTTP_X_FOO' ] )
159
+ end
160
+ end