muack 1.5.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +2 -1
- data/CHANGES.md +13 -0
- data/lib/muack/definition.rb +1 -1
- data/lib/muack/mock.rb +68 -22
- data/lib/muack/version.rb +1 -1
- data/muack.gemspec +2 -2
- data/test/test_mock.rb +148 -7
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 012f658496e2711ec98bf468ffbb58ddfe339eafe03c4618e3cc9b91025b0b03
|
4
|
+
data.tar.gz: 505dbe6462efb20502468cf75f23459afa4589a8cd9d5f5ebc24eaf9a4d004ea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9b538db391549b7da213ab0be7af6f58f71682277e897aae341285daecb47aad5bedfee581e0602908df4a5be55e5faa471d4bf8127a5cd68190058e22dc422c
|
7
|
+
data.tar.gz: 2d3ca26974fa4b73f2dd394ccf0323ebb720a40f3f6c30c72736680f4399f714a4a141998abf799dc846c4f1e0d2d4e3716fb719a256cfd2dd3618a57572be27
|
data/.travis.yml
CHANGED
@@ -7,6 +7,7 @@ script: 'ruby -vwr bundler/setup -S rake test'
|
|
7
7
|
|
8
8
|
matrix:
|
9
9
|
include:
|
10
|
+
- rvm: 2.3
|
10
11
|
- rvm: 2.4
|
11
12
|
env: RUBYOPT=--enable-frozen-string-literal
|
12
13
|
- rvm: 2.5
|
@@ -17,5 +18,5 @@ matrix:
|
|
17
18
|
env: RUBYOPT=--enable-frozen-string-literal
|
18
19
|
- rvm: ruby-head
|
19
20
|
env: RUBYOPT=--enable-frozen-string-literal
|
20
|
-
- rvm: jruby
|
21
|
+
- rvm: jruby-9.2
|
21
22
|
env: RUBYOPT=--enable-frozen-string-literal
|
data/CHANGES.md
CHANGED
@@ -1,5 +1,18 @@
|
|
1
1
|
# CHANGES
|
2
2
|
|
3
|
+
## Muack 1.6.0 -- 2020-12-06
|
4
|
+
|
5
|
+
### Enhancement
|
6
|
+
|
7
|
+
* Fix a few cases for mocking against modules/classes with prepended modules,
|
8
|
+
especially when `any_instance_of` is also used in combination. Previously,
|
9
|
+
it's either not mocking correctly or it may affect modules/classes which
|
10
|
+
also use the same prepended modules. For mocking prepended methods, an
|
11
|
+
internal `MuackPrepended` module will be prepended into the modules/classes
|
12
|
+
to properly override the prepended methods. This module cannot be removed
|
13
|
+
between tests because there's no way to do that with current Rubies.
|
14
|
+
* Performance could be potentially slightly improved with Ruby 2.6+
|
15
|
+
|
3
16
|
## Muack 1.5.1 -- 2020-12-06
|
4
17
|
|
5
18
|
### Bugs fixed
|
data/lib/muack/definition.rb
CHANGED
data/lib/muack/mock.rb
CHANGED
@@ -112,22 +112,18 @@ module Muack
|
|
112
112
|
private
|
113
113
|
def __mock_inject_method defi
|
114
114
|
__mock_injected[defi.msg] = defi
|
115
|
-
|
116
|
-
|
117
|
-
# otherwise the last prepended module.
|
118
|
-
# b) would be the class in AnyInstanceOf.
|
119
|
-
target = object.singleton_class.ancestors.first
|
115
|
+
target = Mock.prepare_target(object.singleton_class, defi.msg)
|
116
|
+
defi.target = target
|
120
117
|
Mock.store_original_method(target, defi)
|
121
118
|
__mock_inject_mock_method(target, defi)
|
122
119
|
end
|
123
120
|
|
124
121
|
def __mock_reset_method defi
|
125
|
-
|
122
|
+
defi.target.module_eval do
|
126
123
|
remove_method(defi.msg)
|
127
124
|
# restore original method
|
128
|
-
if
|
129
|
-
|
130
|
-
private_instance_methods(false).include?(defi.original_method)
|
125
|
+
if defi.original_method &&
|
126
|
+
Mock.direct_method_defined?(self, defi.original_method)
|
131
127
|
alias_method(defi.msg, defi.original_method)
|
132
128
|
__send__(defi.visibility, defi.msg)
|
133
129
|
remove_method(defi.original_method)
|
@@ -135,18 +131,68 @@ module Muack
|
|
135
131
|
end
|
136
132
|
end
|
137
133
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
elsif klass.private_instance_methods(false).include?(defi.msg)
|
144
|
-
:private
|
134
|
+
if ::Class.instance_method(:method_defined?).arity == 1 # Ruby 2.5-
|
135
|
+
def self.direct_method_defined? mod, msg
|
136
|
+
mod.public_instance_methods(false).include?(msg) ||
|
137
|
+
mod.protected_instance_methods(false).include?(msg) ||
|
138
|
+
mod.private_instance_methods(false).include?(msg)
|
145
139
|
end
|
146
140
|
|
147
|
-
|
148
|
-
|
149
|
-
|
141
|
+
def self.method_visibility mod, msg
|
142
|
+
if mod.public_instance_methods(false).include?(msg)
|
143
|
+
:public
|
144
|
+
elsif mod.protected_instance_methods(false).include?(msg)
|
145
|
+
:protected
|
146
|
+
elsif mod.private_instance_methods(false).include?(msg)
|
147
|
+
:private
|
148
|
+
end
|
149
|
+
end
|
150
|
+
else # Ruby 2.6+
|
151
|
+
def self.direct_method_defined? mod, msg
|
152
|
+
mod.method_defined?(msg, false) || # this doesn't cover private method
|
153
|
+
mod.private_method_defined?(msg, false)
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.method_visibility mod, msg
|
157
|
+
if mod.public_method_defined?(msg, false)
|
158
|
+
:public
|
159
|
+
elsif mod.protected_method_defined?(msg, false)
|
160
|
+
:protected
|
161
|
+
elsif mod.private_method_defined?(msg, false)
|
162
|
+
:private
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def self.prepare_target singleton_class, msg
|
168
|
+
if singleton_class == singleton_class.ancestors.first # no prepended mod
|
169
|
+
singleton_class
|
170
|
+
else # check if we need to prepend an internal module to override
|
171
|
+
index = singleton_class.ancestors.index(singleton_class)
|
172
|
+
prepended_modules = singleton_class.ancestors[0...index]
|
173
|
+
|
174
|
+
if prepended_modules.find{ |m| direct_method_defined?(m, msg) }
|
175
|
+
# prepend an internal module to override the prepended module(s)
|
176
|
+
name = :MuackPrepended
|
177
|
+
if singleton_class.const_defined?(name)
|
178
|
+
singleton_class.const_get(name)
|
179
|
+
else
|
180
|
+
prepended = ::Module.new
|
181
|
+
singleton_class.const_set(name, prepended)
|
182
|
+
singleton_class.private_constant(name)
|
183
|
+
singleton_class.prepend(prepended)
|
184
|
+
prepended
|
185
|
+
end
|
186
|
+
else # we don't need to override so singleton class is enough
|
187
|
+
singleton_class
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def self.store_original_method mod, defi
|
193
|
+
if visibility = method_visibility(mod, defi.msg)
|
194
|
+
original_method = find_new_name(mod, defi.msg)
|
195
|
+
mod.__send__(:alias_method, original_method, defi.msg)
|
150
196
|
defi.original_method = original_method
|
151
197
|
defi.visibility = visibility
|
152
198
|
else
|
@@ -154,14 +200,14 @@ module Muack
|
|
154
200
|
end
|
155
201
|
end
|
156
202
|
|
157
|
-
def self.find_new_name
|
203
|
+
def self.find_new_name mod, message, level=0
|
158
204
|
if level >= (::ENV['MUACK_RECURSION_LEVEL'] || 9).to_i
|
159
205
|
raise CannotFindInjectionName.new(level+1, message)
|
160
206
|
end
|
161
207
|
|
162
208
|
new_name = "__muack_#{name}_#{level}_#{message}".to_sym
|
163
|
-
if
|
164
|
-
find_new_name(
|
209
|
+
if direct_method_defined?(mod, new_name)
|
210
|
+
find_new_name(mod, message, level+1)
|
165
211
|
else
|
166
212
|
new_name
|
167
213
|
end
|
data/lib/muack/version.rb
CHANGED
data/muack.gemspec
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: muack 1.
|
2
|
+
# stub: muack 1.6.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "muack".freeze
|
6
|
-
s.version = "1.
|
6
|
+
s.version = "1.6.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
|
9
9
|
s.require_paths = ["lib".freeze]
|
data/test/test_mock.rb
CHANGED
@@ -104,10 +104,73 @@ describe Muack::Mock do
|
|
104
104
|
end
|
105
105
|
|
106
106
|
describe 'not affect the original module' do
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
107
|
+
def mod
|
108
|
+
@mod ||= Module.new{ def f; :f; end }
|
109
|
+
end
|
110
|
+
|
111
|
+
def setup type
|
112
|
+
m = mod
|
113
|
+
Array.new(2).map{ Class.new{ public_send(type, m) }.new }
|
114
|
+
end
|
115
|
+
|
116
|
+
would 'with include and mock' do
|
117
|
+
c0, c1 = setup(:include)
|
118
|
+
|
119
|
+
mock(c0).f{:g}
|
120
|
+
|
121
|
+
expect(c0.f).eq :g
|
122
|
+
expect(c1.f).eq :f
|
123
|
+
end
|
124
|
+
|
125
|
+
would 'with prepend and mock' do
|
126
|
+
c0, c1 = setup(:prepend)
|
127
|
+
|
128
|
+
mock(c0).f{:g}
|
129
|
+
|
130
|
+
expect(c0.f).eq :g
|
131
|
+
expect(c1.f).eq :f
|
132
|
+
end
|
133
|
+
|
134
|
+
would 'with include and mock any_instance_of C0' do
|
135
|
+
c0, c1 = setup(:include)
|
136
|
+
|
137
|
+
mock(any_instance_of(c0.class)).f{:g}
|
138
|
+
|
139
|
+
expect(c0.f).eq :g
|
140
|
+
expect(c1.f).eq :f
|
141
|
+
end
|
142
|
+
|
143
|
+
would 'with prepend and mock any_instance_of C0' do
|
144
|
+
c0, c1 = setup(:prepend)
|
145
|
+
|
146
|
+
mock(any_instance_of(c0.class)).f{:g}
|
147
|
+
|
148
|
+
expect(c0.f).eq :g
|
149
|
+
expect(c1.f).eq :f
|
150
|
+
end
|
151
|
+
|
152
|
+
would 'with include and mock any_instance_of M' do
|
153
|
+
c0, c1 = setup(:include)
|
154
|
+
|
155
|
+
mock(any_instance_of(mod)).f{:g}.times(2)
|
156
|
+
|
157
|
+
expect(c0.f).eq :g
|
158
|
+
expect(c1.f).eq :g
|
159
|
+
end
|
160
|
+
|
161
|
+
would 'with prepend and mock any_instance_of M' do
|
162
|
+
c0, c1 = setup(:prepend)
|
163
|
+
|
164
|
+
mock(any_instance_of(mod)).f{:g}.times(2)
|
165
|
+
|
166
|
+
expect(c0.f).eq :g
|
167
|
+
expect(c1.f).eq :g
|
168
|
+
end
|
169
|
+
|
170
|
+
would 'with extend on the instance' do
|
171
|
+
m = mod
|
172
|
+
c0 = Class.new.new.extend(m)
|
173
|
+
c1 = Class.new{ prepend m }.new
|
111
174
|
|
112
175
|
mock(c0).f{:g}
|
113
176
|
|
@@ -115,9 +178,10 @@ describe Muack::Mock do
|
|
115
178
|
expect(c1.f).eq :f
|
116
179
|
end
|
117
180
|
|
118
|
-
would 'with prepend' do
|
119
|
-
m =
|
120
|
-
c0 = Class.new
|
181
|
+
would 'with prepend on the singleton class of instance' do
|
182
|
+
m = mod
|
183
|
+
c0 = Class.new.new
|
184
|
+
c0.singleton_class.prepend m
|
121
185
|
c1 = Class.new{ prepend m }.new
|
122
186
|
|
123
187
|
mock(c0).f{:g}
|
@@ -125,6 +189,83 @@ describe Muack::Mock do
|
|
125
189
|
expect(c0.f).eq :g
|
126
190
|
expect(c1.f).eq :f
|
127
191
|
end
|
192
|
+
|
193
|
+
describe 'with prepend on any_instance_of' do
|
194
|
+
describe 'on prepended method' do
|
195
|
+
would 'have a muack prepended module' do
|
196
|
+
m = mod
|
197
|
+
c = Class.new{ prepend m }
|
198
|
+
ancestors_size = c.ancestors.size
|
199
|
+
|
200
|
+
mock(any_instance_of(c)).f{:g}
|
201
|
+
|
202
|
+
expect(c.new.f).eq :g
|
203
|
+
expect(c).const_defined?(:MuackPrepended)
|
204
|
+
expect(c.ancestors.size).eq ancestors_size + 1
|
205
|
+
|
206
|
+
expect(Muack.verify).eq true
|
207
|
+
|
208
|
+
# Make sure there's only one MuackPrepended
|
209
|
+
mock(any_instance_of(c)).f{:h}
|
210
|
+
expect(c.ancestors.size).eq ancestors_size + 1
|
211
|
+
expect(c.new.f).eq :h
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
describe 'on non-prepended method' do
|
216
|
+
would 'not have a muack prepended module' do
|
217
|
+
m = mod
|
218
|
+
c = Class.new{ def g; :g; end; prepend m }
|
219
|
+
ancestors_size = c.ancestors.size
|
220
|
+
|
221
|
+
mock(any_instance_of(c)).g{:m}
|
222
|
+
|
223
|
+
expect(c.new.f).eq :f
|
224
|
+
expect(c.new.g).eq :m
|
225
|
+
expect(c).not.const_defined?(:MuackPrepended)
|
226
|
+
expect(c.ancestors.size).eq ancestors_size
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
describe 'any_instance_of on a module which has a prepended module' do
|
232
|
+
before do
|
233
|
+
@m0 = m0 = Module.new{ def f; :m0; end }
|
234
|
+
@m1 = m1 = Module.new{ def f; :m1; end; prepend m0 }
|
235
|
+
@c0 = Class.new{ prepend m0 }.new
|
236
|
+
@c1 = Class.new{ prepend m1 }.new
|
237
|
+
end
|
238
|
+
|
239
|
+
would 'any_instance_of m0' do
|
240
|
+
mock(any_instance_of(@m0)).f{:g}.times(2)
|
241
|
+
|
242
|
+
expect(@c0.f).eq :g
|
243
|
+
expect(@c1.f).eq :g
|
244
|
+
end
|
245
|
+
|
246
|
+
would 'any_instance_of m1' do
|
247
|
+
skip
|
248
|
+
|
249
|
+
mock(any_instance_of(@m1)).f{:g}
|
250
|
+
|
251
|
+
expect(@c0.f).eq :m0
|
252
|
+
expect(@c1.f).eq :g # m1 does not know c1 thus no way to pass this
|
253
|
+
end
|
254
|
+
|
255
|
+
would 'any_instance_of c0.class' do
|
256
|
+
mock(any_instance_of(@c0.class)).f{:g}
|
257
|
+
|
258
|
+
expect(@c0.f).eq :g
|
259
|
+
expect(@c1.f).eq :m0
|
260
|
+
end
|
261
|
+
|
262
|
+
would 'any_instance_of c1.class' do
|
263
|
+
mock(any_instance_of(@c1.class)).f{:g}
|
264
|
+
|
265
|
+
expect(@c0.f).eq :m0
|
266
|
+
expect(@c1.f).eq :g
|
267
|
+
end
|
268
|
+
end
|
128
269
|
end
|
129
270
|
end
|
130
271
|
|