muack 0.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/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