muack 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/muack/stub.rb ADDED
@@ -0,0 +1,22 @@
1
+
2
+ require 'muack/mock'
3
+
4
+ module Muack
5
+ class Stub < Mock
6
+ # used for Muack::Session#verify
7
+ def __mock_verify; true; end
8
+
9
+ # used for mocked object to dispatch mocked method
10
+ def __mock_dispatch msg, actual_args, actual_block
11
+ defi = __mock_defis[msg].find{ |d|
12
+ __mock_check_args(d.args, actual_args)
13
+ }
14
+ if defi
15
+ __mock_block_call(defi, actual_args, actual_block)
16
+ else
17
+ Mock.__send__(:raise, # Wrong argument
18
+ Unexpected.new(object, __mock_defis[msg], msg, actual_args))
19
+ end
20
+ end
21
+ end
22
+ end
data/lib/muack/test.rb ADDED
@@ -0,0 +1,26 @@
1
+
2
+ require 'bacon'
3
+ require 'muack'
4
+
5
+ Bacon.summary_on_exit
6
+ include Muack::API
7
+
8
+ Obj = Object.new
9
+ Str = 'Moo'
10
+ def Obj.inspect
11
+ 'obj'
12
+ end
13
+
14
+ Muack::EnsureReset = lambda{
15
+ [Obj, Str].each do |o|
16
+ o.methods.select{ |m|
17
+ m.to_s.start_with?('__muack_mock') || m.to_s.start_with?('say')
18
+ }.should.empty
19
+ end
20
+ }
21
+
22
+ module Kernel
23
+ def eq? rhs
24
+ self == rhs
25
+ end
26
+ end
@@ -0,0 +1,4 @@
1
+
2
+ module Muack
3
+ VERSION = '0.5.0'
4
+ end
data/muack.gemspec ADDED
@@ -0,0 +1,53 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = "muack"
5
+ s.version = "0.5.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Lin Jen-Shin (godfat)"]
9
+ s.date = "2013-06-24"
10
+ s.description = "Muack -- Yet another mocking library.\n\nBasically it's an [RR][] clone, but much faster under heavy use.\nIt's 32x times faster (750s vs 23s) for running [Rib][] tests.\n\n[RR]: https://github.com/rr/rr\n[Rib]: https://github.com/godfat/rib"
11
+ s.email = ["godfat (XD) godfat.org"]
12
+ s.files = [
13
+ ".gitmodules",
14
+ ".travis.yml",
15
+ "CHANGES.md",
16
+ "Gemfile",
17
+ "LICENSE",
18
+ "README.md",
19
+ "Rakefile",
20
+ "lib/muack.rb",
21
+ "lib/muack/any_instance_of.rb",
22
+ "lib/muack/definition.rb",
23
+ "lib/muack/failure.rb",
24
+ "lib/muack/mock.rb",
25
+ "lib/muack/modifier.rb",
26
+ "lib/muack/proxy.rb",
27
+ "lib/muack/satisfy.rb",
28
+ "lib/muack/session.rb",
29
+ "lib/muack/stub.rb",
30
+ "lib/muack/test.rb",
31
+ "lib/muack/version.rb",
32
+ "muack.gemspec",
33
+ "task/.gitignore",
34
+ "task/gemgem.rb",
35
+ "test/test_any_instance_of.rb",
36
+ "test/test_mock.rb",
37
+ "test/test_proxy.rb",
38
+ "test/test_readme.rb",
39
+ "test/test_satisfy.rb",
40
+ "test/test_stub.rb"]
41
+ s.homepage = "https://github.com/godfat/muack"
42
+ s.licenses = ["Apache License 2.0"]
43
+ s.require_paths = ["lib"]
44
+ s.rubygems_version = "2.0.3"
45
+ s.summary = "Muack -- Yet another mocking library."
46
+ s.test_files = [
47
+ "test/test_any_instance_of.rb",
48
+ "test/test_mock.rb",
49
+ "test/test_proxy.rb",
50
+ "test/test_readme.rb",
51
+ "test/test_satisfy.rb",
52
+ "test/test_stub.rb"]
53
+ end
data/task/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.rbc
data/task/gemgem.rb ADDED
@@ -0,0 +1,268 @@
1
+
2
+ require 'pathname'
3
+
4
+ module Gemgem
5
+ class << self
6
+ attr_accessor :dir, :spec
7
+ end
8
+
9
+ module_function
10
+ def create
11
+ yield(spec = Gem::Specification.new{ |s|
12
+ s.authors = ['Lin Jen-Shin (godfat)']
13
+ s.email = ['godfat (XD) godfat.org']
14
+
15
+ s.description = description.join
16
+ s.summary = description.first
17
+ s.license = readme['LICENSE'].sub(/.+\n\n/, '').lines.first.strip
18
+
19
+ s.rubygems_version = Gem::VERSION
20
+ s.date = Time.now.strftime('%Y-%m-%d')
21
+ s.files = gem_files
22
+ s.test_files = gem_files.grep(%r{^test/(.+?/)*test_.+?\.rb$})
23
+ s.executables = Dir['bin/*'].map{ |f| File.basename(f) }
24
+ s.require_paths = %w[lib]
25
+ })
26
+ spec.homepage ||= "https://github.com/godfat/#{spec.name}"
27
+ spec
28
+ end
29
+
30
+ def readme
31
+ path = %w[README.md README].find{ |name|
32
+ File.exist?("#{Gemgem.dir}/#{name}")
33
+ }
34
+ @readme ||=
35
+ if path
36
+ ps = "##{File.read(path)}".
37
+ scan(/((#+)[^\n]+\n\n.+?(?=(\n\n\2[^#\n]+\n)|\Z))/m).map(&:first)
38
+ ps.inject('HEADER' => ps.first){ |r, s, i|
39
+ r[s[/\w+/]] = s
40
+ r
41
+ }
42
+ else
43
+ {}
44
+ end
45
+ end
46
+
47
+ def description
48
+ @description ||= (readme['DESCRIPTION']||'').sub(/.+\n\n/, '').lines
49
+ end
50
+
51
+ def changes
52
+ path = %w[CHANGES.md CHANGES].find{ |name|
53
+ File.exist?("#{Gemgem.dir}/#{name}")
54
+ }
55
+ @changes ||=
56
+ if path
57
+ date = '\d+{4}\-\d+{2}\-\d{2}'
58
+ File.read(path).match(
59
+ /([^\n]+#{date}\n\n(.+?))(?=\n\n[^\n]+#{date}\n|\Z)/m)[1]
60
+ else
61
+ ''
62
+ end
63
+ end
64
+
65
+ def ann_md
66
+ "#{readme['HEADER'].sub(/([\w\-]+)/, "[\\1](#{spec.homepage})")}\n\n" \
67
+ "##{readme['DESCRIPTION'][/[^\n]+\n\n[^\n]+/]}\n\n" \
68
+ "### CHANGES:\n\n" \
69
+ "###{changes}\n\n" \
70
+ "##{readme['INSTALLATION']}\n\n" +
71
+ if readme['SYNOPSIS'] then "##{readme['SYNOPSIS'][/[^\n]+\n\n[^\n]+/]}"
72
+ else '' end
73
+ end
74
+
75
+ def ann_html
76
+ gem 'nokogiri'
77
+ gem 'kramdown'
78
+
79
+ IO.popen('kramdown', 'r+') do |md|
80
+ md.puts Gemgem.ann_md
81
+ md.close_write
82
+ require 'nokogiri'
83
+ html = Nokogiri::XML.parse("<gemgem>#{md.read}</gemgem>")
84
+ html.css('*').each{ |n| n.delete('id') }
85
+ html.root.children.to_html
86
+ end
87
+ end
88
+
89
+ def ann_email
90
+ "#{readme['HEADER'].sub(/([\w\-]+)/, "\\1 <#{spec.homepage}>")}\n\n" \
91
+ "#{readme['DESCRIPTION']}\n\n" \
92
+ "#{readme['INSTALLATION']}\n\n" +
93
+ if readme['SYNOPSIS'] then "##{readme['SYNOPSIS']}\n\n" else '' end +
94
+ "## CHANGES:\n\n" \
95
+ "##{changes}\n\n"
96
+ end
97
+
98
+ def gem_tag
99
+ "#{spec.name}-#{spec.version}"
100
+ end
101
+
102
+ def write
103
+ File.open("#{dir}/#{spec.name}.gemspec", 'w'){ |f|
104
+ f << split_lines(spec.to_ruby) }
105
+ end
106
+
107
+ def split_lines ruby
108
+ ruby.gsub(/(.+?)\s*=\s*\[(.+?)\]/){ |s|
109
+ if $2.index(',')
110
+ "#{$1} = [\n #{$2.split(',').map(&:strip).join(",\n ")}]"
111
+ else
112
+ s
113
+ end
114
+ }
115
+ end
116
+
117
+ def all_files
118
+ @all_files ||= find_files(Pathname.new(dir)).map{ |file|
119
+ if file.to_s =~ %r{\.git/|\.git$}
120
+ nil
121
+ else
122
+ file.to_s
123
+ end
124
+ }.compact.sort
125
+ end
126
+
127
+ def gem_files
128
+ @gem_files ||= all_files - ignored_files
129
+ end
130
+
131
+ def ignored_files
132
+ @ignored_file ||= all_files.select{ |path| ignore_patterns.find{ |ignore|
133
+ path =~ ignore && !git_files.include?(path)}}
134
+ end
135
+
136
+ def git_files
137
+ @git_files ||= if File.exist?("#{dir}/.git")
138
+ `git ls-files`.split("\n")
139
+ else
140
+ []
141
+ end
142
+ end
143
+
144
+ # protected
145
+ def find_files path
146
+ path.children.select(&:file?).map{|file| file.to_s[(dir.size+1)..-1]} +
147
+ path.children.select(&:directory?).map{|dir| find_files(dir)}.flatten
148
+ end
149
+
150
+ def ignore_patterns
151
+ @ignore_files ||= expand_patterns(
152
+ gitignore.split("\n").reject{ |pattern|
153
+ pattern.strip == ''
154
+ }).map{ |pattern| %r{^([^/]+/)*?#{Regexp.escape(pattern)}(/[^/]+)*?$} }
155
+ end
156
+
157
+ def expand_patterns pathes
158
+ pathes.map{ |path|
159
+ if path !~ /\*/
160
+ path
161
+ else
162
+ expand_patterns(
163
+ Dir[path] +
164
+ Pathname.new(File.dirname(path)).children.select(&:directory?).
165
+ map{ |prefix| "#{prefix}/#{File.basename(path)}" })
166
+ end
167
+ }.flatten
168
+ end
169
+
170
+ def gitignore
171
+ if File.exist?(path = "#{dir}/.gitignore")
172
+ File.read(path)
173
+ else
174
+ ''
175
+ end
176
+ end
177
+ end
178
+
179
+ namespace :gem do
180
+
181
+ desc 'Install gem'
182
+ task :install => [:build] do
183
+ sh("#{Gem.ruby} -S gem install pkg/#{Gemgem.gem_tag}.gem")
184
+ end
185
+
186
+ desc 'Build gem'
187
+ task :build => [:spec] do
188
+ sh("#{Gem.ruby} -S gem build #{Gemgem.spec.name}.gemspec")
189
+ sh("mkdir -p pkg")
190
+ sh("mv #{Gemgem.gem_tag}.gem pkg/")
191
+ end
192
+
193
+ desc 'Release gem'
194
+ task :release => [:spec, :check, :build] do
195
+ sh("git tag #{Gemgem.gem_tag}")
196
+ sh("git push")
197
+ sh("git push --tags")
198
+ sh("#{Gem.ruby} -S gem push pkg/#{Gemgem.gem_tag}.gem")
199
+ end
200
+
201
+ task :check do
202
+ ver = Gemgem.spec.version.to_s
203
+
204
+ if ENV['VERSION'].nil?
205
+ puts("\e[35mExpected " \
206
+ "\e[33mVERSION\e[35m=\e[33m#{ver}\e[0m")
207
+ exit(1)
208
+
209
+ elsif ENV['VERSION'] != ver
210
+ puts("\e[35mExpected \e[33mVERSION\e[35m=\e[33m#{ver} " \
211
+ "\e[35mbut got\n " \
212
+ "\e[33mVERSION\e[35m=\e[33m#{ENV['VERSION']}\e[0m")
213
+ exit(2)
214
+ end
215
+ end
216
+
217
+ end # of gem namespace
218
+
219
+ desc 'Run tests in memory'
220
+ task :test do
221
+ require 'bacon'
222
+ Bacon.extend(Bacon::TestUnitOutput)
223
+ Bacon.summary_on_exit
224
+ $LOAD_PATH.unshift('lib')
225
+ Dir['./test/**/test_*.rb'].each{ |file| require file[0..-4] }
226
+ end
227
+
228
+ desc 'Run tests with shell'
229
+ task 'test:shell', :RUBY_OPTS do |t, args|
230
+ files = Dir['test/**/test_*.rb'].join(' ')
231
+
232
+ cmd = [Gem.ruby, args[:RUBY_OPTS],
233
+ '-I', 'lib', '-S', 'bacon', '--quiet', files]
234
+
235
+ sh(cmd.compact.join(' '))
236
+ end
237
+
238
+ desc 'Generate ann markdown'
239
+ task 'ann:md' => ['gem:spec'] do
240
+ puts Gemgem.ann_md
241
+ end
242
+
243
+ desc 'Generate ann html'
244
+ task 'ann:html' => ['gem:spec'] do
245
+ puts Gemgem.ann_html
246
+ end
247
+
248
+ desc 'Generate ann email'
249
+ task 'ann:email' => ['gem:spec'] do
250
+ puts Gemgem.ann_email
251
+ end
252
+
253
+ desc 'Generate rdoc'
254
+ task :doc => ['gem:spec'] do
255
+ sh("yardoc -o rdoc --main README.md" \
256
+ " --files #{Gemgem.spec.extra_rdoc_files.join(',')}")
257
+ end
258
+
259
+ desc 'Remove ignored files'
260
+ task :clean => ['gem:spec'] do
261
+ trash = "~/.Trash/#{Gemgem.spec.name}/"
262
+ sh "mkdir -p #{trash}" unless File.exist?(File.expand_path(trash))
263
+ Gemgem.ignored_files.each{ |file| sh "mv #{file} #{trash}" }
264
+ end
265
+
266
+ task :default do
267
+ puts `#{Gem.ruby} -S #{$PROGRAM_NAME} -T`
268
+ end
@@ -0,0 +1,30 @@
1
+
2
+ require 'muack/test'
3
+
4
+ describe Muack::AnyInstanceOf do
5
+ should 'mock any_instance_of' do
6
+ klass = Class.new
7
+ any_instance_of(klass){ |instance| mock(instance).say{ true } }
8
+ obj = klass.new
9
+ obj.say .should.eq true
10
+ obj.respond_to?(:say).should.eq true
11
+ Muack.verify .should.eq true
12
+ obj.respond_to?(:say).should.eq false
13
+ end
14
+
15
+ should 'proxy any_instance_of' do
16
+ klass = Class.new{ def f; true; end }
17
+ any_instance_of(klass){ |instance| mock_proxy(instance).f }
18
+ obj = klass.new
19
+ obj.f.should.eq true
20
+ Muack.verify.should.eq true
21
+ end
22
+
23
+ should 'proxy any_instance_of with a block' do
24
+ klass = Class.new{ def f; 0; end }
25
+ any_instance_of(klass){ |instance| mock_proxy(instance).f{ |i| i+1 } }
26
+ obj = klass.new
27
+ obj.f.should.eq 1
28
+ Muack.verify.should.eq true
29
+ end
30
+ end
data/test/test_mock.rb ADDED
@@ -0,0 +1,170 @@
1
+
2
+ require 'muack/test'
3
+
4
+ describe Muack::Mock do
5
+ describe 'Muack.verify==true' do
6
+ after do
7
+ Muack.verify.should.eq true
8
+ Muack::EnsureReset.call
9
+ end
10
+
11
+ should 'mock with regular method' do
12
+ mock(Obj).say(true){ 'boo' }
13
+ Obj.say(true).should.eq 'boo'
14
+ end
15
+
16
+ should 'mock existing method' do
17
+ mock(Obj).to_s{ 'zoo' }
18
+ Obj.to_s.should.eq 'zoo'
19
+ end
20
+
21
+ should 'mock twice' do
22
+ mock(Obj).say(true){ Obj.saya }
23
+ mock(Obj).saya{ 'coo' }
24
+ Obj.say(true).should.eq 'coo'
25
+ end
26
+
27
+ should 'also mock with with' do
28
+ mock(Str).method_missing(:say, 0){ 0 }
29
+ Str.say(0).should.eq 0
30
+ Muack.verify.should.eq true
31
+ mock(Str).method_missing(:say, 1){ 1 }
32
+ lambda{ Str.say(2) }.should.raise(Muack::Unexpected)
33
+ Muack.reset
34
+ end
35
+
36
+ should 'mock multiple times' do
37
+ 3.times{ |i| mock(Obj).say(i){ i } }
38
+ 3.times{ |i| Obj.say(i).should.eq i }
39
+ end
40
+
41
+ should 'mock multiple times with times(n) modifier' do
42
+ mock(Obj).say{ 0 }.times(3)
43
+ 3.times{ |i| Obj.say.should.eq 0 }
44
+ end
45
+
46
+ should 'mock 0 times with times(0) modifier' do
47
+ mock(Obj).say{ 0 }.times(0).should.kind_of Muack::Modifier
48
+ end
49
+
50
+ should 'mix mock and stub' do
51
+ mock(Obj).say { 0 }
52
+ stub(Obj).saya{ 1 }
53
+ 3.times{ Obj.saya.should.eq 1 }
54
+ Obj.say .should.eq 0
55
+ end
56
+
57
+ should 'unnamed mock' do
58
+ mock.say{1}.object.say.should.eq 1
59
+ end
60
+
61
+ should 'inspect' do
62
+ mock(Obj).inspect.should.eq "#<Muack::Mock object=obj>"
63
+ end
64
+
65
+ should 'mock and call, mock and call' do
66
+ mock(Obj).say{0}
67
+ Obj.say.should.eq 0
68
+ mock(Obj).say{1}
69
+ Obj.say.should.eq 1
70
+ end
71
+ end
72
+
73
+ describe 'Muack.verify==false' do
74
+ after do
75
+ Muack.reset
76
+ Muack::EnsureReset.call
77
+ end
78
+
79
+ should 'raise Muack::Unexpected error if passing unexpected argument' do
80
+ mock(Obj).say(true){ 'boo' }
81
+ begin
82
+ Obj.say(false)
83
+ 'never'.should.eq 'reach'
84
+ rescue Muack::Unexpected => e
85
+ e.expected.should.eq 'obj.say(true)'
86
+ e.was .should.eq 'obj.say(false)'
87
+ e.message .should.eq "\nExpected: #{e.expected}\n but was: #{e.was}"
88
+ end
89
+ end
90
+
91
+ should 'have correct message for multiple mocks with the same name' do
92
+ 2.times{ mock(Obj).say }
93
+ begin
94
+ 3.times{ Obj.say }
95
+ 'never'.should.eq 'reach'
96
+ rescue Muack::Expected => e
97
+ e.expected.should.eq 'obj.say()'
98
+ e.expected_times.should.eq 2
99
+ e.actual_times .should.eq 3
100
+ e.message .should.eq "\nExpected: obj.say()\n " \
101
+ "called 2 times\n but was 3 times."
102
+ end
103
+ end
104
+
105
+ should 'raise if a mock with times(0) gets called' do
106
+ mock(Obj).say.times(0)
107
+ begin
108
+ Obj.say
109
+ 'never'.should.eq 'reach'
110
+ rescue Muack::Unexpected => e
111
+ e.expected.should.eq nil
112
+ e.was .should.eq 'obj.say()'
113
+ e.message .should.start_with "\nExpected: #{e.was}\n"
114
+ end
115
+ end
116
+
117
+ should 'raise if a mock with times(0) gets called with diff sig' do
118
+ mock(Obj).say.times(0)
119
+ begin
120
+ Obj.say(true)
121
+ 'never'.should.eq 'reach'
122
+ rescue Muack::Unexpected => e
123
+ e.expected.should.eq nil
124
+ e.was .should.eq 'obj.say(true)'
125
+ e.message .should.start_with "\nExpected: #{e.was}\n"
126
+ end
127
+ end
128
+
129
+ should 'raise Muack::Unexpected when calling with diff sig' do
130
+ mock(Obj).say(true){1}
131
+ Obj.say(true).should.eq 1
132
+ begin
133
+ Obj.say
134
+ 'never'.should.eq 'reach'
135
+ rescue Muack::Unexpected => e
136
+ e.expected.should.eq 'obj.say(true)'
137
+ e.was .should.eq 'obj.say()'
138
+ e.message .should.eq "\nExpected: #{e.expected}\n but was: #{e.was}"
139
+ end
140
+ end
141
+
142
+ should 'raise Muack::Expected error if mock methods not called' do
143
+ mock(Obj).say(true){ 'boo' }
144
+ begin
145
+ Muack.verify
146
+ rescue Muack::Expected => e
147
+ e.expected .should.eq 'obj.say(true)'
148
+ e.expected_times.should.eq 1
149
+ e.actual_times .should.eq 0
150
+ e.message .should.eq "\nExpected: obj.say(true)\n " \
151
+ "called 1 times\n but was 0 times."
152
+ end
153
+ end
154
+
155
+ should 'show first not enough calls' do
156
+ mock(Obj).say{ 'boo' }.times(2)
157
+ mock(Obj).saya .times(2)
158
+ begin
159
+ Obj.say
160
+ Muack.verify
161
+ rescue Muack::Expected => e
162
+ e.expected .should.eq 'obj.say()'
163
+ e.expected_times.should.eq 2
164
+ e.actual_times .should.eq 1
165
+ e.message .should.eq "\nExpected: obj.say()\n " \
166
+ "called 2 times\n but was 1 times."
167
+ end
168
+ end
169
+ end
170
+ end