muack 1.5.1 → 1.6.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.
- 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
|
|