muack 1.2.0 → 1.5.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.
data/Rakefile CHANGED
@@ -1,14 +1,13 @@
1
1
 
2
2
  begin
3
- require "#{dir = File.dirname(__FILE__)}/task/gemgem"
3
+ require "#{__dir__}/task/gemgem"
4
4
  rescue LoadError
5
- sh 'git submodule update --init'
5
+ sh 'git submodule update --init --recursive'
6
6
  exec Gem.ruby, '-S', $PROGRAM_NAME, *ARGV
7
7
  end
8
8
 
9
- Gemgem.init(dir) do |s|
9
+ Gemgem.init(__dir__) do |s|
10
10
  require 'muack/version'
11
11
  s.name = 'muack'
12
12
  s.version = Muack::VERSION
13
- %w[].each{ |g| s.add_runtime_dependency(g) }
14
13
  end
@@ -1,6 +1,6 @@
1
1
 
2
2
  require 'muack/session'
3
- require 'muack/satisfy'
3
+ require 'muack/satisfying'
4
4
 
5
5
  module Muack
6
6
  def self.verify obj=nil
@@ -46,20 +46,16 @@ module Muack
46
46
  if block_given? then yield(ret) else ret end
47
47
  end
48
48
 
49
- def is_a klass
50
- Muack::IsA.new(klass)
51
- end
52
-
53
49
  def anything
54
50
  Muack::Anything.new
55
51
  end
56
52
 
57
- def match regexp
58
- Muack::Match.new(regexp)
53
+ def is_a klass
54
+ Muack::IsA.new(klass)
59
55
  end
60
56
 
61
- def hash_including hash
62
- Muack::HashIncluding.new(hash)
57
+ def matching regexp
58
+ Muack::Matching.new(regexp)
63
59
  end
64
60
 
65
61
  def including element
@@ -70,12 +66,48 @@ module Muack
70
66
  Muack::Within.new(range_or_array)
71
67
  end
72
68
 
69
+ def responding_to *msg
70
+ Muack::RespondingTo.new(*msg)
71
+ end
72
+
73
+ def where spec
74
+ Muack::Where.new(spec)
75
+ end
76
+
77
+ def having spec
78
+ Muack::Having.new(spec)
79
+ end
80
+
81
+ def allowing spec
82
+ Muack::Allowing.new(spec)
83
+ end
84
+
85
+ def satisfying &block
86
+ Muack::Satisfying.new(&block)
87
+ end
88
+
89
+ def match regexp
90
+ $stderr.puts("Muack::API.match is deprecated." \
91
+ " Use Muack::API.matching instead.")
92
+ matching(regexp)
93
+ end
94
+
73
95
  def respond_to *msg
74
- Muack::RespondTo.new(*msg)
96
+ $stderr.puts("Muack::API.respond_to is deprecated." \
97
+ " Use Muack::API.responding_to instead.")
98
+ responding_to(*msg)
99
+ end
100
+
101
+ def hash_including spec
102
+ $stderr.puts("Muack::API.hash_including is deprecated." \
103
+ " Use Muack::API.having instead.")
104
+ having(spec)
75
105
  end
76
106
 
77
107
  def satisfy &block
78
- Muack::Satisfy.new(block)
108
+ $stderr.puts("Muack::API.satisfy is deprecated." \
109
+ " Use Muack::API.satisfying instead.")
110
+ satisfying(&block)
79
111
  end
80
112
  end
81
113
  end
@@ -1,17 +1,6 @@
1
1
 
2
- module Muack
3
- class Block
4
- attr_accessor :block, :context
5
- def initialize block, context=nil
6
- self.block, self.context = block, context
7
- end
8
-
9
- def call *args, &actual_block
10
- if context # ruby: no way to pass actual_block to instance_exec
11
- context.instance_exec(*args, &block)
12
- else
13
- block.call(*args, &actual_block)
14
- end
15
- end
16
- end
2
+ if RUBY_VERSION < '2.7'
3
+ require 'muack/block_26'
4
+ else
5
+ require 'muack/block_27'
17
6
  end
@@ -0,0 +1,16 @@
1
+
2
+ module Muack
3
+ class Block < Struct.new(:block, :context)
4
+ def initialize block, context=nil
5
+ super
6
+ end
7
+
8
+ def call(*args, &block)
9
+ if context
10
+ context.instance_exec(*args, &block)
11
+ else
12
+ block.call(*args, &block)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+
2
+ module Muack
3
+ class Block < Struct.new(:block, :context)
4
+ def initialize block, context=nil
5
+ super
6
+ end
7
+
8
+ def call(...)
9
+ if context
10
+ context.instance_exec(...)
11
+ else
12
+ block.call(...)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -4,7 +4,7 @@ require 'muack/mock'
4
4
  module Muack
5
5
  class Coat < Mock
6
6
  # used for mocked object to dispatch mocked method
7
- def __mock_dispatch msg, actual_args
7
+ def __mock_dispatch actual_call
8
8
  defi = super
9
9
  if __mock_defis[defi.msg].empty?
10
10
  __mock_reset_method(defi)
@@ -1,7 +1,8 @@
1
1
 
2
2
  module Muack
3
- Definition = Class.new(Struct.new(:msg, :args, :returns,
4
- :peek_args, :peek_return,
5
- :original_method))
3
+ Definition = Struct.new(:msg, :args, :returns,
4
+ :peek_args, :peek_return,
5
+ :original_method, :visibility)
6
+ ActualCall = Struct.new(:msg, :args, :block)
6
7
  WithAnyArgs = Object.new
7
8
  end
@@ -15,4 +15,10 @@ module Muack
15
15
  ".times(#{times})"
16
16
  end
17
17
  end
18
+
19
+ class UnknownSpec < Error
20
+ def initialize spec
21
+ super "\nUnknown spec: #{spec.inspect}"
22
+ end
23
+ end
18
24
  end
@@ -12,11 +12,12 @@ module Muack
12
12
 
13
13
  class Unexpected < Failure
14
14
  attr_reader :was
15
- def initialize obj, expected_defis, msg, args
16
- @was = "#{obj.inspect}.#{msg}(" \
17
- "#{args.map(&:inspect).join(', ')})"
15
+ def initialize obj, expected_defis, actual_call
16
+ args = actual_call.args.map(&:inspect)
17
+ @was = "#{obj.inspect}.#{actual_call.msg}(#{args.join(', ')})"
18
+
18
19
  if expected_defis.empty?
19
- super("\nUnexpected call: #{@was}")
20
+ super("\nUnexpected call: #{was}")
20
21
  else
21
22
  build_expected(obj, expected_defis)
22
23
  super("\nExpected: #{expected}\n but was: #{was}")
@@ -6,8 +6,6 @@ require 'muack/block'
6
6
  require 'muack/error'
7
7
 
8
8
  module Muack
9
- EmptyBlock = proc{}
10
-
11
9
  class Mock < BasicObject
12
10
  attr_reader :object
13
11
  def initialize object
@@ -24,8 +22,8 @@ module Muack
24
22
  end
25
23
 
26
24
  # Public API: Define mocked method
27
- def method_missing msg, *args, &block
28
- defi = Definition.new(msg, args, block)
25
+ def method_missing msg, *args, &returns
26
+ defi = Definition.new(msg, args, returns)
29
27
  if injected = __mock_injected[defi.msg]
30
28
  defi.original_method = injected.original_method
31
29
  else
@@ -51,49 +49,42 @@ module Muack
51
49
  end
52
50
 
53
51
  # used for mocked object to dispatch mocked method
54
- def __mock_dispatch msg, actual_args
55
- if defi = __mock_defis[msg].shift
52
+ def __mock_dispatch actual_call
53
+ if defi = __mock_defis[actual_call.msg].shift
56
54
  __mock_disps_push(defi)
57
- if __mock_check_args(defi.args, actual_args)
55
+ if __mock_check_args(defi, actual_call)
58
56
  defi
59
57
  else
60
58
  Mock.__send__(:raise, # Wrong argument
61
- Unexpected.new(object, [defi], msg, actual_args))
59
+ Unexpected.new(object, [defi], actual_call))
62
60
  end
63
61
  else
64
- defis = __mock_disps[msg]
65
- if expected = defis.find{ |d| __mock_check_args(d.args, actual_args) }
66
- Mock.__send__(:raise, # Too many times
67
- Expected.new(object, expected, defis.size, defis.size+1))
68
- else
69
- Mock.__send__(:raise, # Wrong argument
70
- Unexpected.new(object, defis, msg, actual_args))
71
- end
62
+ __mock_failed(actual_call)
72
63
  end
73
64
  end
74
65
 
75
66
  # used for mocked object to dispatch mocked method
76
- def __mock_dispatch_call context, disp, actual_args, actual_block, &_yield
77
- args = if disp.peek_args
78
- __mock_block_call(context, disp.peek_args,
79
- actual_args, actual_block, true)
80
- else
81
- actual_args
82
- end
83
-
84
- ret = if disp.returns
85
- __mock_block_call(context, disp.returns,
86
- args, actual_block, true)
87
- elsif disp.original_method # proxies for singleton methods
88
- context.__send__(disp.original_method, *args, &actual_block)
89
- else # proxies for instance methods
90
- # need the original context for calling `super`
91
- # ruby: can't pass a block to yield, so we name it _yield
92
- _yield.call(args, &actual_block)
93
- end
67
+ def __mock_dispatch_call context, disp, actual_call, &proxy_super
68
+ # resolving arguments
69
+ call =
70
+ if disp.peek_args
71
+ args = __mock_block_call(context, disp.peek_args, actual_call)
72
+ ActualCall.new(actual_call.msg, args, actual_call.block)
73
+ else
74
+ actual_call
75
+ end
94
76
 
77
+ # retrieve actual return
78
+ ret =
79
+ if disp.returns
80
+ __mock_block_call(context, disp.returns, call)
81
+ else
82
+ __mock_proxy_call(context, disp, call, proxy_super)
83
+ end
84
+
85
+ # resolving return
95
86
  if disp.peek_return
96
- __mock_block_call(context, disp.peek_return, ret, EmptyBlock, false)
87
+ __mock_block_call(context, disp.peek_return, ret, true)
97
88
  else
98
89
  ret
99
90
  end
@@ -102,9 +93,9 @@ module Muack
102
93
  # used for Muack::Session#verify
103
94
  def __mock_verify
104
95
  __mock_defis.values.all?(&:empty?) || begin
105
- msg, defis_with_same_msg = __mock_defis.find{ |_, v| v.size > 0 }
96
+ msg, defis_with_same_msg = __mock_defis.find{ |_, v| v.any? }
106
97
  args, defis = defis_with_same_msg.group_by(&:args).first
107
- dsize = __mock_disps[msg].select{ |d| d.args == args }.size
98
+ dsize = __mock_disps[msg].count{ |d| d.args == args }
108
99
  Mock.__send__(:raise, # Too little times
109
100
  Expected.new(object, defis.first, defis.size + dsize, dsize))
110
101
  end
@@ -121,36 +112,46 @@ module Muack
121
112
  private
122
113
  def __mock_inject_method defi
123
114
  __mock_injected[defi.msg] = defi
124
- target = object.singleton_class # would be the class in AnyInstanceOf
125
- privilege = Mock.store_original_method(target, defi)
126
- __mock_inject_mock_method(target, defi, privilege)
115
+ # a) ancestors.first is the first module in the method chain.
116
+ # it's just the singleton_class when nothing was prepended,
117
+ # otherwise the last prepended module.
118
+ # b) would be the class in AnyInstanceOf.
119
+ target = object.singleton_class.ancestors.first
120
+ Mock.store_original_method(target, defi)
121
+ __mock_inject_mock_method(target, defi)
127
122
  end
128
123
 
129
124
  def __mock_reset_method defi
130
- object.singleton_class.module_eval do
125
+ object.singleton_class.ancestors.first.module_eval do
131
126
  remove_method(defi.msg)
132
127
  # restore original method
133
- if instance_methods(false).include?(defi.original_method) ||
128
+ if public_instance_methods(false).include?(defi.original_method) ||
129
+ protected_instance_methods(false).include?(defi.original_method) ||
134
130
  private_instance_methods(false).include?(defi.original_method)
135
131
  alias_method(defi.msg, defi.original_method)
132
+ __send__(defi.visibility, defi.msg)
136
133
  remove_method(defi.original_method)
137
134
  end
138
135
  end
139
136
  end
140
137
 
141
138
  def self.store_original_method klass, defi
142
- privilege = if klass.instance_methods(false).include?(defi.msg)
143
- :public # TODO: forget about protected methods?
139
+ visibility = if klass.public_instance_methods(false).include?(defi.msg)
140
+ :public
141
+ elsif klass.protected_instance_methods(false).include?(defi.msg)
142
+ :protected
144
143
  elsif klass.private_instance_methods(false).include?(defi.msg)
145
144
  :private
146
145
  end
147
146
 
148
- return :public unless privilege
149
- # store original method
150
- original_method = find_new_name(klass, defi.msg)
151
- klass.__send__(:alias_method, original_method, defi.msg)
152
- defi.original_method = original_method
153
- privilege
147
+ if visibility # store original method
148
+ original_method = find_new_name(klass, defi.msg)
149
+ klass.__send__(:alias_method, original_method, defi.msg)
150
+ defi.original_method = original_method
151
+ defi.visibility = visibility
152
+ else
153
+ defi.visibility = :public
154
+ end
154
155
  end
155
156
 
156
157
  def self.find_new_name klass, message, level=0
@@ -166,40 +167,105 @@ module Muack
166
167
  end
167
168
  end
168
169
 
169
- def __mock_inject_mock_method target, defi, privilege=:public
170
+ def __mock_inject_mock_method target, defi
170
171
  mock = self # remember the context
171
172
  target.__send__(:define_method, defi.msg){ |*actual_args, &actual_block|
172
- disp = mock.__mock_dispatch(defi.msg, actual_args)
173
- mock.__mock_dispatch_call(self, disp, actual_args,
174
- actual_block) do |args, &block|
175
- super(*args, &block)
173
+ actual_call = ActualCall.new(defi.msg, actual_args, actual_block)
174
+ disp = mock.__mock_dispatch(actual_call)
175
+ mock.__mock_dispatch_call(self, disp, actual_call) do |call, has_kargs|
176
+ # need the original context for calling `super`
177
+ if has_kargs && kargs = call.args.last
178
+ super(*call.args[0...-1], **kargs, &call.block)
179
+ else
180
+ super(*call.args, &call.block)
181
+ end
176
182
  end
177
183
  }
178
- target.__send__(privilege, defi.msg)
184
+ target.__send__(defi.visibility, defi.msg)
185
+ end
186
+
187
+ # used for __mock_dispatch
188
+ def __mock_failed actual_call, disps=__mock_disps[actual_call.msg]
189
+ if expected = __mock_find_checked_difi(disps, actual_call)
190
+ Mock.__send__(:raise, # Too many times
191
+ Expected.new(object, expected, disps.size, disps.size+1))
192
+ else
193
+ Mock.__send__(:raise, # Wrong argument
194
+ Unexpected.new(object, disps, actual_call))
195
+ end
179
196
  end
180
197
 
181
198
  # used for __mock_dispatch_call
182
- def __mock_block_call context, block, actual_args, actual_block, splat
183
- return unless block
199
+ def __mock_block_call context, block, actual_call, peek_return=false
184
200
  # for AnyInstanceOf, we don't have the actual context at the time
185
201
  # we're defining it, so we update it here
186
- block.context = context if block.kind_of?(Block)
187
- if splat
188
- block.call(*actual_args, &actual_block)
189
- else # peek_return doesn't need splat
190
- block.call(actual_args, &actual_block)
202
+ if block.kind_of?(Block)
203
+ block.context = context
204
+ instance_exec_block = block.block
191
205
  end
206
+
207
+ if peek_return # actual_call is the actual return in this case
208
+ block.call(actual_call, &instance_exec_block)
209
+ else
210
+ actual_block = actual_call.block || instance_exec_block
211
+ if __mock_block_with_kargs?(instance_exec_block || block) &&
212
+ kargs = actual_call.args.last
213
+ block.call(*actual_call.args[0...-1], **kargs, &actual_block)
214
+ else
215
+ block.call(*actual_call.args, &actual_block)
216
+ end
217
+ end
218
+ end
219
+
220
+ # used for __mock_dispatch_call
221
+ def __mock_proxy_call context, disp, call, proxy_super
222
+ if disp.original_method # proxies for singleton methods with __send__
223
+ if __mock_method_with_kargs?(context, disp.original_method) &&
224
+ kargs = call.args.last
225
+ context.__send__(
226
+ disp.original_method, *call.args[0...-1], **kargs, &call.block)
227
+ else
228
+ context.__send__(disp.original_method, *call.args, &call.block)
229
+ end
230
+ else # proxies for instance methods with super
231
+ proxy_super.call(call, __mock_super_with_kargs?(context, call.msg))
232
+ end
233
+ end
234
+
235
+ def __mock_find_checked_difi defis, actual_call, meth=:find
236
+ defis.public_send(meth){ |d| __mock_check_args(d, actual_call) }
192
237
  end
193
238
 
194
- def __mock_check_args expected_args, actual_args
195
- if expected_args == [WithAnyArgs]
196
- true
197
- elsif expected_args.none?{ |arg| arg.kind_of?(Satisfy) }
239
+ def __mock_method_with_kargs? object, method_name
240
+ __mock_block_with_kargs?(
241
+ ::Kernel.instance_method(:method).bind(object).call(method_name))
242
+ end
243
+
244
+ def __mock_super_with_kargs? object, method_name
245
+ super_method =
246
+ ::Kernel.instance_method(:method).bind(object).call(method_name).
247
+ super_method
248
+
249
+ super_method && __mock_block_with_kargs?(super_method)
250
+ end
251
+
252
+ def __mock_block_with_kargs? block
253
+ # there's no Symbol#start_with? in older Ruby
254
+ block.parameters.dig(-1, 0).to_s.start_with?('key')
255
+ end
256
+
257
+ def __mock_check_args defi, actual_call
258
+ return true if defi.args.size == 1 && defi.args.first == WithAnyArgs
259
+
260
+ expected_args = defi.args
261
+ actual_args = actual_call.args
262
+
263
+ if expected_args.none?{ |arg| arg.kind_of?(Satisfying) }
198
264
  expected_args == actual_args
199
265
 
200
266
  elsif expected_args.size == actual_args.size
201
267
  expected_args.zip(actual_args).all?{ |(e, a)|
202
- if e.kind_of?(Satisfy) then e.match(a) else e == a end
268
+ if e.kind_of?(Satisfying) then e.match(a) else e == a end
203
269
  }
204
270
  else
205
271
  false