muack 0.7.3 → 1.0.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/.gitignore +0 -1
- data/.travis.yml +3 -5
- data/CHANGES.md +68 -0
- data/Gemfile +4 -0
- data/README.md +734 -287
- data/lib/muack/block.rb +17 -0
- data/lib/muack/definition.rb +3 -2
- data/lib/muack/mock.rb +61 -31
- data/lib/muack/modifier.rb +28 -16
- data/lib/muack/satisfy.rb +5 -5
- data/lib/muack/session.rb +1 -1
- data/lib/muack/test.rb +4 -0
- data/lib/muack/version.rb +1 -1
- data/muack.gemspec +13 -10
- data/task/README.md +54 -0
- data/task/gemgem.rb +9 -11
- data/test/test_any_instance_of.rb +32 -8
- data/test/test_from_readme.rb +40 -0
- data/test/test_mock.rb +37 -26
- data/test/test_modifier.rb +102 -0
- data/test/test_proxy.rb +18 -13
- data/test/test_satisfy.rb +7 -2
- data/test/test_stub.rb +11 -6
- metadata +18 -15
- data/task/.gitignore +0 -1
- data/test/test_readme.rb +0 -16
data/lib/muack/block.rb
ADDED
@@ -0,0 +1,17 @@
|
|
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
|
17
|
+
end
|
data/lib/muack/definition.rb
CHANGED
data/lib/muack/mock.rb
CHANGED
@@ -2,9 +2,12 @@
|
|
2
2
|
require 'muack/definition'
|
3
3
|
require 'muack/modifier'
|
4
4
|
require 'muack/failure'
|
5
|
+
require 'muack/block'
|
5
6
|
require 'muack/error'
|
6
7
|
|
7
8
|
module Muack
|
9
|
+
EmptyBlock = proc{}
|
10
|
+
|
8
11
|
class Mock < BasicObject
|
9
12
|
attr_reader :object
|
10
13
|
def initialize object
|
@@ -70,21 +73,29 @@ module Muack
|
|
70
73
|
end
|
71
74
|
|
72
75
|
# used for mocked object to dispatch mocked method
|
73
|
-
def __mock_dispatch_call disp, actual_args, actual_block
|
74
|
-
if disp.
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
94
|
+
|
95
|
+
if disp.peek_return
|
96
|
+
__mock_block_call(context, disp.peek_return, ret, EmptyBlock, false)
|
97
|
+
else
|
98
|
+
ret
|
88
99
|
end
|
89
100
|
end
|
90
101
|
|
@@ -105,9 +116,10 @@ module Muack
|
|
105
116
|
object.singleton_class.module_eval do
|
106
117
|
remove_method(defi.msg)
|
107
118
|
# restore original method
|
108
|
-
if instance_methods(false).include?(defi.original_method)
|
109
|
-
|
110
|
-
|
119
|
+
if instance_methods(false).include?(defi.original_method) ||
|
120
|
+
private_instance_methods(false).include?(defi.original_method)
|
121
|
+
alias_method(defi.msg, defi.original_method)
|
122
|
+
remove_method(defi.original_method)
|
111
123
|
end
|
112
124
|
end
|
113
125
|
end
|
@@ -120,16 +132,23 @@ module Muack
|
|
120
132
|
def __mock_inject_method defi
|
121
133
|
__mock_injected[defi.msg] = defi
|
122
134
|
target = object.singleton_class # would be the class in AnyInstanceOf
|
123
|
-
Mock.store_original_method(target, defi)
|
124
|
-
|
135
|
+
privilege = Mock.store_original_method(target, defi)
|
136
|
+
__mock_inject_mock_method(target, defi, privilege)
|
125
137
|
end
|
126
138
|
|
127
139
|
def self.store_original_method klass, defi
|
128
|
-
|
140
|
+
privilege = if klass.instance_methods(false).include?(defi.msg)
|
141
|
+
:public # TODO: forget about protected methods?
|
142
|
+
elsif klass.private_instance_methods(false).include?(defi.msg)
|
143
|
+
:private
|
144
|
+
end
|
145
|
+
|
146
|
+
return :public unless privilege
|
129
147
|
# store original method
|
130
148
|
original_method = find_new_name(klass, defi.msg)
|
131
149
|
klass.__send__(:alias_method, original_method, defi.msg)
|
132
150
|
defi.original_method = original_method
|
151
|
+
privilege
|
133
152
|
end
|
134
153
|
|
135
154
|
def self.find_new_name klass, message, level=0
|
@@ -137,7 +156,7 @@ module Muack
|
|
137
156
|
raise CannotFindInjectionName.new(level+1, message)
|
138
157
|
end
|
139
158
|
|
140
|
-
new_name = "
|
159
|
+
new_name = "__muack_#{name}_#{level}_#{message}".to_sym
|
141
160
|
if klass.instance_methods(false).include?(new_name)
|
142
161
|
find_new_name(klass, message, level+1)
|
143
162
|
else
|
@@ -145,18 +164,29 @@ module Muack
|
|
145
164
|
end
|
146
165
|
end
|
147
166
|
|
148
|
-
def __mock_inject_mock_method target, defi
|
167
|
+
def __mock_inject_mock_method target, defi, privilege=:public
|
149
168
|
mock = self # remember the context
|
150
|
-
target.__send__(:define_method, defi.msg){|*actual_args, &actual_block|
|
169
|
+
target.__send__(:define_method, defi.msg){ |*actual_args, &actual_block|
|
151
170
|
disp = mock.__mock_dispatch(defi.msg, actual_args)
|
152
|
-
mock.__mock_dispatch_call(disp, actual_args,
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
super(*actual_args, &actual_block)
|
157
|
-
end
|
158
|
-
}
|
171
|
+
mock.__mock_dispatch_call(self, disp, actual_args,
|
172
|
+
actual_block) do |args, &block|
|
173
|
+
super(*args, &block)
|
174
|
+
end
|
159
175
|
}
|
176
|
+
target.__send__(privilege, defi.msg)
|
177
|
+
end
|
178
|
+
|
179
|
+
# used for __mock_dispatch_call
|
180
|
+
def __mock_block_call context, block, actual_args, actual_block, splat
|
181
|
+
return unless block
|
182
|
+
# for AnyInstanceOf, we don't have actually context at the time
|
183
|
+
# we're defining it, so we update it here
|
184
|
+
block.context = context if block.kind_of?(Block)
|
185
|
+
if splat
|
186
|
+
block.call(*actual_args, &actual_block)
|
187
|
+
else # peek_return doesn't need splat
|
188
|
+
block.call(actual_args, &actual_block)
|
189
|
+
end
|
160
190
|
end
|
161
191
|
|
162
192
|
def __mock_check_args expected_args, actual_args
|
data/lib/muack/modifier.rb
CHANGED
@@ -1,8 +1,25 @@
|
|
1
1
|
|
2
|
+
require 'muack/block'
|
2
3
|
require 'muack/error'
|
3
4
|
|
4
5
|
module Muack
|
5
6
|
class Modifier < Struct.new(:mock, :defi)
|
7
|
+
# Public API
|
8
|
+
def times number
|
9
|
+
if mock.__mock_class == Stub
|
10
|
+
raise StubHasNoTimes.new(object, defi, number)
|
11
|
+
end
|
12
|
+
|
13
|
+
if number >= 1
|
14
|
+
(number - 1).times{ mock.__mock_defis_push(defi) }
|
15
|
+
elsif number == 0
|
16
|
+
mock.__mock_defis_pop(defi)
|
17
|
+
else
|
18
|
+
raise "What would you expect from calling a method #{number} times?"
|
19
|
+
end
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
6
23
|
# Public API
|
7
24
|
def with_any_args
|
8
25
|
defi.args = [WithAnyArgs]
|
@@ -10,30 +27,20 @@ module Muack
|
|
10
27
|
end
|
11
28
|
|
12
29
|
# Public API
|
13
|
-
def returns
|
14
|
-
defi.
|
30
|
+
def returns opts={}, &block
|
31
|
+
defi.returns = create_block(block, opts)
|
15
32
|
self
|
16
33
|
end
|
17
34
|
|
18
35
|
# Public API
|
19
|
-
def
|
20
|
-
defi.
|
36
|
+
def peek_args opts={}, &block
|
37
|
+
defi.peek_args = create_block(block, opts)
|
21
38
|
self
|
22
39
|
end
|
23
40
|
|
24
41
|
# Public API
|
25
|
-
def
|
26
|
-
|
27
|
-
raise StubHasNoTimes.new(object, defi, number)
|
28
|
-
end
|
29
|
-
|
30
|
-
if number >= 1
|
31
|
-
(number - 1).times{ mock.__mock_defis_push(defi) }
|
32
|
-
elsif number == 0
|
33
|
-
mock.__mock_defis_pop(defi)
|
34
|
-
else
|
35
|
-
raise "What would you expect from calling a method #{number} times?"
|
36
|
-
end
|
42
|
+
def peek_return opts={}, &block
|
43
|
+
defi.peek_return = create_block(block, opts)
|
37
44
|
self
|
38
45
|
end
|
39
46
|
|
@@ -41,5 +48,10 @@ module Muack
|
|
41
48
|
def object
|
42
49
|
mock.object
|
43
50
|
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def create_block block, opts
|
54
|
+
if opts[:instance_exec] then Block.new(block) else block end
|
55
|
+
end
|
44
56
|
end
|
45
57
|
end
|
data/lib/muack/satisfy.rb
CHANGED
@@ -5,10 +5,10 @@ module Muack
|
|
5
5
|
!!block.call(actual_arg)
|
6
6
|
end
|
7
7
|
|
8
|
-
def | rhs; Satisfy::
|
9
|
-
def & rhs; Satisfy::
|
8
|
+
def | rhs; Satisfy::Disj.new(self, rhs); end
|
9
|
+
def & rhs; Satisfy::Conj.new(self, rhs); end
|
10
10
|
|
11
|
-
class
|
11
|
+
class Disj < Satisfy
|
12
12
|
def initialize lhs, rhs
|
13
13
|
@lhs, @rhs = lhs, rhs
|
14
14
|
super(lambda{ |actual_arg| lhs.match(actual_arg) ||
|
@@ -19,7 +19,7 @@ module Muack
|
|
19
19
|
alias_method :inspect, :to_s
|
20
20
|
end
|
21
21
|
|
22
|
-
class
|
22
|
+
class Conj < Satisfy
|
23
23
|
def initialize lhs, rhs
|
24
24
|
@lhs, @rhs = lhs, rhs
|
25
25
|
super(lambda{ |actual_arg| lhs.match(actual_arg) &&
|
@@ -36,7 +36,7 @@ module Muack
|
|
36
36
|
alias_method :inspect, :to_s
|
37
37
|
|
38
38
|
def api_name
|
39
|
-
self.class.name[
|
39
|
+
(self.class.name || 'Unknown')[/(::)*(\w+)$/, 2].
|
40
40
|
gsub(/([A-Z][a-z]*)+?(?=[A-Z][a-z]*)/, '\\1_').downcase
|
41
41
|
end
|
42
42
|
|
data/lib/muack/session.rb
CHANGED
data/lib/muack/test.rb
CHANGED
data/lib/muack/version.rb
CHANGED
data/muack.gemspec
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
|
-
# stub: muack 0.
|
2
|
+
# stub: muack 1.0.0 ruby lib
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "muack"
|
6
|
-
s.version = "0.
|
6
|
+
s.version = "1.0.0"
|
7
7
|
|
8
8
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
|
+
s.require_paths = ["lib"]
|
9
10
|
s.authors = ["Lin Jen-Shin (godfat)"]
|
10
|
-
s.date = "
|
11
|
-
s.description = "Muack --
|
11
|
+
s.date = "2014-01-06"
|
12
|
+
s.description = "Muack -- A fast, small, yet powerful mocking library.\n\nInspired by [RR][], and it's 32x times faster (750s vs 23s) than RR\nfor running [Rib][] tests.\n\n[RR]: https://github.com/rr/rr\n[Rib]: https://github.com/godfat/rib"
|
12
13
|
s.email = ["godfat (XD) godfat.org"]
|
13
14
|
s.files = [
|
14
15
|
".gitignore",
|
@@ -21,6 +22,7 @@ Gem::Specification.new do |s|
|
|
21
22
|
"Rakefile",
|
22
23
|
"lib/muack.rb",
|
23
24
|
"lib/muack/any_instance_of.rb",
|
25
|
+
"lib/muack/block.rb",
|
24
26
|
"lib/muack/definition.rb",
|
25
27
|
"lib/muack/error.rb",
|
26
28
|
"lib/muack/failure.rb",
|
@@ -33,24 +35,25 @@ Gem::Specification.new do |s|
|
|
33
35
|
"lib/muack/test.rb",
|
34
36
|
"lib/muack/version.rb",
|
35
37
|
"muack.gemspec",
|
36
|
-
"task
|
38
|
+
"task/README.md",
|
37
39
|
"task/gemgem.rb",
|
38
40
|
"test/test_any_instance_of.rb",
|
41
|
+
"test/test_from_readme.rb",
|
39
42
|
"test/test_mock.rb",
|
43
|
+
"test/test_modifier.rb",
|
40
44
|
"test/test_proxy.rb",
|
41
|
-
"test/test_readme.rb",
|
42
45
|
"test/test_satisfy.rb",
|
43
46
|
"test/test_stub.rb"]
|
44
47
|
s.homepage = "https://github.com/godfat/muack"
|
45
48
|
s.licenses = ["Apache License 2.0"]
|
46
|
-
s.
|
47
|
-
s.
|
48
|
-
s.summary = "Muack -- Yet another mocking library."
|
49
|
+
s.rubygems_version = "2.2.0"
|
50
|
+
s.summary = "Muack -- A fast, small, yet powerful mocking library."
|
49
51
|
s.test_files = [
|
50
52
|
"test/test_any_instance_of.rb",
|
53
|
+
"test/test_from_readme.rb",
|
51
54
|
"test/test_mock.rb",
|
55
|
+
"test/test_modifier.rb",
|
52
56
|
"test/test_proxy.rb",
|
53
|
-
"test/test_readme.rb",
|
54
57
|
"test/test_satisfy.rb",
|
55
58
|
"test/test_stub.rb"]
|
56
59
|
end
|
data/task/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Gemgem
|
2
|
+
|
3
|
+
## DESCRIPTION:
|
4
|
+
|
5
|
+
Provided tasks:
|
6
|
+
|
7
|
+
rake clean # Remove ignored files
|
8
|
+
rake gem:build # Build gem
|
9
|
+
rake gem:install # Install gem
|
10
|
+
rake gem:release # Release gem
|
11
|
+
rake gem:spec # Generate gemspec
|
12
|
+
rake test # Run tests in memory
|
13
|
+
|
14
|
+
## REQUIREMENTS:
|
15
|
+
|
16
|
+
* Tested with MRI (official CRuby) 1.9.3, 2.0.0, Rubinius and JRuby.
|
17
|
+
|
18
|
+
## INSTALLATION:
|
19
|
+
|
20
|
+
git submodule add git://github.com/godfat/gemgem.git task
|
21
|
+
|
22
|
+
And in Rakefile:
|
23
|
+
|
24
|
+
``` ruby
|
25
|
+
begin
|
26
|
+
require "#{dir = File.dirname(__FILE__)}/task/gemgem"
|
27
|
+
rescue LoadError
|
28
|
+
sh 'git submodule update --init'
|
29
|
+
exec Gem.ruby, '-S', $PROGRAM_NAME, *ARGV
|
30
|
+
end
|
31
|
+
|
32
|
+
Gemgem.init(dir) do |s|
|
33
|
+
s.name = 'your-gem'
|
34
|
+
s.version = '0.1.0'
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
## LICENSE:
|
39
|
+
|
40
|
+
Apache License 2.0
|
41
|
+
|
42
|
+
Copyright (c) 2011-2013, Lin Jen-Shin (godfat)
|
43
|
+
|
44
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
45
|
+
you may not use this file except in compliance with the License.
|
46
|
+
You may obtain a copy of the License at
|
47
|
+
|
48
|
+
<http://www.apache.org/licenses/LICENSE-2.0>
|
49
|
+
|
50
|
+
Unless required by applicable law or agreed to in writing, software
|
51
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
52
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
53
|
+
See the License for the specific language governing permissions and
|
54
|
+
limitations under the License.
|
data/task/gemgem.rb
CHANGED
@@ -144,20 +144,18 @@ module Gemgem
|
|
144
144
|
end
|
145
145
|
|
146
146
|
def ignored_pattern
|
147
|
-
@ignored_pattern ||=
|
147
|
+
@ignored_pattern ||= if gitignore.empty?
|
148
|
+
/^$/
|
149
|
+
else
|
150
|
+
Regexp.new(expand_patterns(gitignore).join('|'))
|
151
|
+
end
|
148
152
|
end
|
149
153
|
|
150
154
|
def expand_patterns pathes
|
151
155
|
# http://git-scm.com/docs/gitignore
|
152
156
|
pathes.flat_map{ |path|
|
153
|
-
|
154
|
-
|
155
|
-
Regexp.escape(path).gsub(/\\\*/, '[^/]*')
|
156
|
-
when %r{^/}
|
157
|
-
"^#{Regexp.escape(path[1..-1])}"
|
158
|
-
else # we didn't implement negative pattern for now
|
159
|
-
Regexp.escape(path)
|
160
|
-
end
|
157
|
+
# we didn't implement negative pattern for now
|
158
|
+
Regexp.escape(path).sub(%r{^/}, '^').gsub(/\\\*/, '[^/]*')
|
161
159
|
}
|
162
160
|
end
|
163
161
|
|
@@ -226,7 +224,7 @@ end
|
|
226
224
|
|
227
225
|
end # of gem namespace
|
228
226
|
|
229
|
-
desc 'Run tests
|
227
|
+
desc 'Run tests'
|
230
228
|
task :test do
|
231
229
|
next if Gemgem.test_files.empty?
|
232
230
|
|
@@ -236,7 +234,7 @@ task :test do
|
|
236
234
|
Gemgem.test_files.each{ |file| require "#{Gemgem.dir}/#{file[0..-4]}" }
|
237
235
|
end
|
238
236
|
|
239
|
-
desc '
|
237
|
+
desc 'Trash ignored files'
|
240
238
|
task :clean => ['gem:spec'] do
|
241
239
|
next if Gemgem.ignored_files.empty?
|
242
240
|
|