hoodoo 1.7.0 → 1.8.0

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.
@@ -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