muack 0.7.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|