spec 5.0.14
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.autotest +34 -0
- data/.gitignore +3 -0
- data/History.txt +911 -0
- data/Manifest.txt +26 -0
- data/README.txt +497 -0
- data/Rakefile +214 -0
- data/design_rationale.rb +52 -0
- data/lib/hoe/minitest.rb +26 -0
- data/lib/minitest/assertions.rb +649 -0
- data/lib/minitest/autorun.rb +12 -0
- data/lib/minitest/benchmark.rb +423 -0
- data/lib/minitest/expectations.rb +268 -0
- data/lib/minitest/hell.rb +11 -0
- data/lib/minitest/mock.rb +220 -0
- data/lib/minitest/parallel_each.rb +120 -0
- data/lib/minitest/pride.rb +4 -0
- data/lib/minitest/pride_plugin.rb +143 -0
- data/lib/minitest/spec.rb +292 -0
- data/lib/minitest/test.rb +272 -0
- data/lib/minitest/unit.rb +45 -0
- data/lib/minitest.rb +839 -0
- data/lib/spec.rb +3 -0
- data/readme.md +7 -0
- data/release_notes.md +49 -0
- data/spec.gemspec +36 -0
- data/test/manual/appium.rb +14 -0
- data/test/manual/appium_after_last.rb +24 -0
- data/test/manual/appium_before_first.rb +23 -0
- data/test/manual/assert.rb +61 -0
- data/test/manual/before_first_0.rb +27 -0
- data/test/manual/before_first_1.rb +29 -0
- data/test/manual/debug.rb +37 -0
- data/test/manual/do_end.rb +31 -0
- data/test/manual/raise.rb +61 -0
- data/test/manual/run2.rb +74 -0
- data/test/manual/run3.rb +91 -0
- data/test/manual/setup.rb +13 -0
- data/test/manual/simple.rb +19 -0
- data/test/manual/simple2.rb +20 -0
- data/test/manual/t.rb +11 -0
- data/test/manual/trace.rb +19 -0
- data/test/manual/trace2.rb +15 -0
- data/test/minitest/metametameta.rb +78 -0
- data/test/minitest/test_helper.rb +20 -0
- data/test/minitest/test_minitest_benchmark.rb +131 -0
- data/test/minitest/test_minitest_mock.rb +490 -0
- data/test/minitest/test_minitest_reporter.rb +270 -0
- data/test/minitest/test_minitest_spec.rb +794 -0
- data/test/minitest/test_minitest_unit.rb +1846 -0
- metadata +147 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
class MockExpectationError < StandardError; end # :nodoc:
|
2
|
+
|
3
|
+
module Minitest # :nodoc:
|
4
|
+
|
5
|
+
##
|
6
|
+
# A simple and clean mock object framework.
|
7
|
+
#
|
8
|
+
# All mock objects are an instance of Mock
|
9
|
+
|
10
|
+
class Mock
|
11
|
+
alias :__respond_to? :respond_to?
|
12
|
+
|
13
|
+
overridden_methods = %w(
|
14
|
+
===
|
15
|
+
inspect
|
16
|
+
object_id
|
17
|
+
public_send
|
18
|
+
respond_to_missing?
|
19
|
+
send
|
20
|
+
to_s
|
21
|
+
)
|
22
|
+
|
23
|
+
instance_methods.each do |m|
|
24
|
+
undef_method m unless overridden_methods.include?(m.to_s) || m =~ /^__/
|
25
|
+
end
|
26
|
+
|
27
|
+
overridden_methods.map(&:to_sym).each do |method_id|
|
28
|
+
define_method method_id do |*args, &b|
|
29
|
+
if @expected_calls.has_key? method_id then
|
30
|
+
method_missing(method_id, *args, &b)
|
31
|
+
else
|
32
|
+
super(*args, &b)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize # :nodoc:
|
38
|
+
@expected_calls = Hash.new { |calls, name| calls[name] = [] }
|
39
|
+
@actual_calls = Hash.new { |calls, name| calls[name] = [] }
|
40
|
+
end
|
41
|
+
|
42
|
+
##
|
43
|
+
# Expect that method +name+ is called, optionally with +args+ or a
|
44
|
+
# +blk+, and returns +retval+.
|
45
|
+
#
|
46
|
+
# @mock.expect(:meaning_of_life, 42)
|
47
|
+
# @mock.meaning_of_life # => 42
|
48
|
+
#
|
49
|
+
# @mock.expect(:do_something_with, true, [some_obj, true])
|
50
|
+
# @mock.do_something_with(some_obj, true) # => true
|
51
|
+
#
|
52
|
+
# @mock.expect(:do_something_else, true) do |a1, a2|
|
53
|
+
# a1 == "buggs" && a2 == :bunny
|
54
|
+
# end
|
55
|
+
#
|
56
|
+
# +args+ is compared to the expected args using case equality (ie, the
|
57
|
+
# '===' operator), allowing for less specific expectations.
|
58
|
+
#
|
59
|
+
# @mock.expect(:uses_any_string, true, [String])
|
60
|
+
# @mock.uses_any_string("foo") # => true
|
61
|
+
# @mock.verify # => true
|
62
|
+
#
|
63
|
+
# @mock.expect(:uses_one_string, true, ["foo"])
|
64
|
+
# @mock.uses_one_string("bar") # => true
|
65
|
+
# @mock.verify # => raises MockExpectationError
|
66
|
+
|
67
|
+
def expect(name, retval, args=[], &blk)
|
68
|
+
name = name.to_sym
|
69
|
+
|
70
|
+
if block_given?
|
71
|
+
raise ArgumentError, "args ignored when block given" unless args.empty?
|
72
|
+
@expected_calls[name] << { :retval => retval, :block => blk }
|
73
|
+
else
|
74
|
+
raise ArgumentError, "args must be an array" unless Array === args
|
75
|
+
@expected_calls[name] << { :retval => retval, :args => args }
|
76
|
+
end
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def __call name, data # :nodoc:
|
81
|
+
case data
|
82
|
+
when Hash then
|
83
|
+
"#{name}(#{data[:args].inspect[1..-2]}) => #{data[:retval].inspect}"
|
84
|
+
else
|
85
|
+
data.map { |d| __call name, d }.join ", "
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
##
|
90
|
+
# Verify that all methods were called as expected. Raises
|
91
|
+
# +MockExpectationError+ if the mock object was not called as
|
92
|
+
# expected.
|
93
|
+
|
94
|
+
def verify
|
95
|
+
@expected_calls.each do |name, calls|
|
96
|
+
calls.each do |expected|
|
97
|
+
msg1 = "expected #{__call name, expected}"
|
98
|
+
msg2 = "#{msg1}, got [#{__call name, @actual_calls[name]}]"
|
99
|
+
|
100
|
+
raise MockExpectationError, msg2 if
|
101
|
+
@actual_calls.has_key?(name) and
|
102
|
+
not @actual_calls[name].include?(expected)
|
103
|
+
|
104
|
+
raise MockExpectationError, msg1 unless
|
105
|
+
@actual_calls.has_key?(name) and
|
106
|
+
@actual_calls[name].include?(expected)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
true
|
110
|
+
end
|
111
|
+
|
112
|
+
def method_missing(sym, *args) # :nodoc:
|
113
|
+
unless @expected_calls.has_key?(sym) then
|
114
|
+
raise NoMethodError, "unmocked method %p, expected one of %p" %
|
115
|
+
[sym, @expected_calls.keys.sort_by(&:to_s)]
|
116
|
+
end
|
117
|
+
|
118
|
+
index = @actual_calls[sym].length
|
119
|
+
expected_call = @expected_calls[sym][index]
|
120
|
+
|
121
|
+
unless expected_call then
|
122
|
+
raise MockExpectationError, "No more expects available for %p: %p" %
|
123
|
+
[sym, args]
|
124
|
+
end
|
125
|
+
|
126
|
+
expected_args, retval, val_block =
|
127
|
+
expected_call.values_at(:args, :retval, :block)
|
128
|
+
|
129
|
+
if val_block then
|
130
|
+
raise MockExpectationError, "mocked method %p failed block w/ %p" %
|
131
|
+
[sym, args] unless val_block.call(args)
|
132
|
+
|
133
|
+
# keep "verify" happy
|
134
|
+
@actual_calls[sym] << expected_call
|
135
|
+
return retval
|
136
|
+
end
|
137
|
+
|
138
|
+
if expected_args.size != args.size then
|
139
|
+
raise ArgumentError, "mocked method %p expects %d arguments, got %d" %
|
140
|
+
[sym, expected_args.size, args.size]
|
141
|
+
end
|
142
|
+
|
143
|
+
fully_matched = expected_args.zip(args).all? { |mod, a|
|
144
|
+
mod === a or mod == a
|
145
|
+
}
|
146
|
+
|
147
|
+
unless fully_matched then
|
148
|
+
raise MockExpectationError, "mocked method %p called with unexpected arguments %p" %
|
149
|
+
[sym, args]
|
150
|
+
end
|
151
|
+
|
152
|
+
@actual_calls[sym] << {
|
153
|
+
:retval => retval,
|
154
|
+
:args => expected_args.zip(args).map { |mod, a| mod === a ? mod : a }
|
155
|
+
}
|
156
|
+
|
157
|
+
retval
|
158
|
+
end
|
159
|
+
|
160
|
+
def respond_to?(sym, include_private = false) # :nodoc:
|
161
|
+
return true if @expected_calls.has_key? sym.to_sym
|
162
|
+
return __respond_to?(sym, include_private)
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
class Object # :nodoc:
|
168
|
+
|
169
|
+
##
|
170
|
+
# Add a temporary stubbed method replacing +name+ for the duration
|
171
|
+
# of the +block+. If +val_or_callable+ responds to #call, then it
|
172
|
+
# returns the result of calling it, otherwise returns the value
|
173
|
+
# as-is. If stubbed method yields a block, +block_args+ will be
|
174
|
+
# passed along. Cleans up the stub at the end of the +block+. The
|
175
|
+
# method +name+ must exist before stubbing.
|
176
|
+
#
|
177
|
+
# def test_stale_eh
|
178
|
+
# obj_under_test = Something.new
|
179
|
+
# refute obj_under_test.stale?
|
180
|
+
#
|
181
|
+
# Time.stub :now, Time.at(0) do
|
182
|
+
# assert obj_under_test.stale?
|
183
|
+
# end
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
|
187
|
+
def stub name, val_or_callable, *block_args, &block
|
188
|
+
new_name = "__minitest_stub__#{name}"
|
189
|
+
|
190
|
+
metaclass = class << self; self; end
|
191
|
+
|
192
|
+
if respond_to? name and not methods.map(&:to_s).include? name.to_s then
|
193
|
+
metaclass.send :define_method, name do |*args|
|
194
|
+
super(*args)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
metaclass.send :alias_method, new_name, name
|
199
|
+
|
200
|
+
metaclass.send :define_method, name do |*args, &blk|
|
201
|
+
|
202
|
+
ret = if val_or_callable.respond_to? :call then
|
203
|
+
val_or_callable.call(*args)
|
204
|
+
else
|
205
|
+
val_or_callable
|
206
|
+
end
|
207
|
+
|
208
|
+
blk.call(*block_args) if blk
|
209
|
+
|
210
|
+
ret
|
211
|
+
end
|
212
|
+
|
213
|
+
yield self
|
214
|
+
ensure
|
215
|
+
metaclass.send :undef_method, name
|
216
|
+
metaclass.send :alias_method, name, new_name
|
217
|
+
metaclass.send :undef_method, new_name
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
##
|
2
|
+
# Provides a parallel #each that lets you enumerate using N threads.
|
3
|
+
# Use environment variable N to customize. Defaults to 2. Enumerable,
|
4
|
+
# so all the goodies come along (tho not all are wrapped yet to
|
5
|
+
# return another ParallelEach instance).
|
6
|
+
|
7
|
+
class Minitest::ParallelEach
|
8
|
+
require 'thread'
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
##
|
12
|
+
# How many Threads to use for this parallel #each.
|
13
|
+
|
14
|
+
N = (ENV['N'] || 2).to_i
|
15
|
+
|
16
|
+
##
|
17
|
+
# Create a new ParallelEach instance over +list+.
|
18
|
+
|
19
|
+
def initialize list
|
20
|
+
@queue = Queue.new # *sigh*... the Queue api sucks sooo much...
|
21
|
+
|
22
|
+
list.each { |i| @queue << i }
|
23
|
+
N.times { @queue << nil }
|
24
|
+
end
|
25
|
+
|
26
|
+
def select(&block) # :nodoc:
|
27
|
+
self.class.new super
|
28
|
+
end
|
29
|
+
|
30
|
+
alias find_all select # :nodoc:
|
31
|
+
|
32
|
+
##
|
33
|
+
# Starts N threads that yield each element to your block. Joins the
|
34
|
+
# threads at the end.
|
35
|
+
|
36
|
+
def each
|
37
|
+
threads = N.times.map {
|
38
|
+
Thread.new do
|
39
|
+
Thread.current.abort_on_exception = true
|
40
|
+
while job = @queue.pop
|
41
|
+
yield job
|
42
|
+
end
|
43
|
+
end
|
44
|
+
}
|
45
|
+
threads.map(&:join)
|
46
|
+
end
|
47
|
+
|
48
|
+
def count # :nodoc:
|
49
|
+
[@queue.size - N, 0].max
|
50
|
+
end
|
51
|
+
|
52
|
+
alias_method :size, :count # :nodoc:
|
53
|
+
end
|
54
|
+
|
55
|
+
module Minitest
|
56
|
+
class << self
|
57
|
+
remove_method :__run
|
58
|
+
end
|
59
|
+
|
60
|
+
class Test
|
61
|
+
@mutex = Mutex.new
|
62
|
+
|
63
|
+
def self.synchronize # :nodoc:
|
64
|
+
if @mutex then # see parallel_each.rb
|
65
|
+
@mutex.synchronize { yield }
|
66
|
+
else
|
67
|
+
yield
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
alias :simple_capture_io :capture_io
|
72
|
+
|
73
|
+
def capture_io(&b)
|
74
|
+
Test.synchronize do
|
75
|
+
simple_capture_io(&b)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
alias :simple_capture_subprocess_io :capture_subprocess_io
|
80
|
+
|
81
|
+
def capture_subprocess_io(&b)
|
82
|
+
Test.synchronize do
|
83
|
+
simple_capture_subprocess_io(&b)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Reporter
|
89
|
+
@mutex = Mutex.new
|
90
|
+
|
91
|
+
def self.synchronize # :nodoc:
|
92
|
+
if @mutex then # see parallel_each.rb
|
93
|
+
@mutex.synchronize { yield }
|
94
|
+
else
|
95
|
+
yield
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
alias :simple_record :record
|
100
|
+
|
101
|
+
def record result
|
102
|
+
Reporter.synchronize do
|
103
|
+
simple_record result
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
##
|
109
|
+
# Runs all the +suites+ for a given +type+. Runs suites declaring
|
110
|
+
# a test_order of +:parallel+ in parallel, and everything else
|
111
|
+
# serial.
|
112
|
+
|
113
|
+
def self.__run reporter, options
|
114
|
+
suites = Runnable.runnables
|
115
|
+
parallel, serial = suites.partition { |s| s.test_order == :parallel }
|
116
|
+
|
117
|
+
ParallelEach.new(parallel).map { |suite| suite.run reporter, options } +
|
118
|
+
serial.map { |suite| suite.run reporter, options }
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
require "minitest/autorun"
|
2
|
+
|
3
|
+
module Minitest
|
4
|
+
def self.plugin_pride_options opts, options # :nodoc:
|
5
|
+
opts.on "-p", "--pride", "Pride. Show your testing pride!" do
|
6
|
+
PrideIO.pride!
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.plugin_pride_init options # :nodoc:
|
11
|
+
if PrideIO.pride? then
|
12
|
+
klass = ENV["TERM"] =~ /^xterm|-256color$/ ? PrideLOL : PrideIO
|
13
|
+
io = klass.new options[:io]
|
14
|
+
|
15
|
+
self.reporter.reporters.grep(Minitest::Reporter).each do |rep|
|
16
|
+
rep.io = io
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Show your testing pride!
|
23
|
+
|
24
|
+
class PrideIO
|
25
|
+
##
|
26
|
+
# Activate the pride plugin. Called from both -p option and minitest/pride
|
27
|
+
|
28
|
+
def self.pride!
|
29
|
+
@pride = true
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Are we showing our testing pride?
|
34
|
+
|
35
|
+
def self.pride?
|
36
|
+
@pride ||= false
|
37
|
+
end
|
38
|
+
|
39
|
+
# Start an escape sequence
|
40
|
+
ESC = "\e["
|
41
|
+
|
42
|
+
# End the escape sequence
|
43
|
+
NND = "#{ESC}0m"
|
44
|
+
|
45
|
+
# The IO we're going to pipe through.
|
46
|
+
attr_reader :io
|
47
|
+
|
48
|
+
def initialize io # :nodoc:
|
49
|
+
@io = io
|
50
|
+
# stolen from /System/Library/Perl/5.10.0/Term/ANSIColor.pm
|
51
|
+
# also reference http://en.wikipedia.org/wiki/ANSI_escape_code
|
52
|
+
@colors ||= (31..36).to_a
|
53
|
+
@size = @colors.size
|
54
|
+
@index = 0
|
55
|
+
# io.sync = true
|
56
|
+
end
|
57
|
+
|
58
|
+
##
|
59
|
+
# Wrap print to colorize the output.
|
60
|
+
|
61
|
+
def print o
|
62
|
+
case o
|
63
|
+
when "." then
|
64
|
+
io.print pride o
|
65
|
+
when "E", "F" then
|
66
|
+
io.print "#{ESC}41m#{ESC}37m#{o}#{NND}"
|
67
|
+
when "S" then
|
68
|
+
io.print pride o
|
69
|
+
else
|
70
|
+
io.print o
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def puts(*o) # :nodoc:
|
75
|
+
o.map! { |s|
|
76
|
+
s.to_s.sub(/Finished/) {
|
77
|
+
@index = 0
|
78
|
+
'Fabulous run'.split(//).map { |c|
|
79
|
+
pride(c)
|
80
|
+
}.join
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
io.puts(*o)
|
85
|
+
end
|
86
|
+
|
87
|
+
##
|
88
|
+
# Color a string.
|
89
|
+
|
90
|
+
def pride string
|
91
|
+
string = "*" if string == "."
|
92
|
+
c = @colors[@index % @size]
|
93
|
+
@index += 1
|
94
|
+
"#{ESC}#{c}m#{string}#{NND}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def method_missing msg, *args # :nodoc:
|
98
|
+
io.send(msg, *args)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
##
|
103
|
+
# If you thought the PrideIO was colorful...
|
104
|
+
#
|
105
|
+
# (Inspired by lolcat, but with clean math)
|
106
|
+
|
107
|
+
class PrideLOL < PrideIO
|
108
|
+
PI_3 = Math::PI / 3 # :nodoc:
|
109
|
+
|
110
|
+
def initialize io # :nodoc:
|
111
|
+
# walk red, green, and blue around a circle separated by equal thirds.
|
112
|
+
#
|
113
|
+
# To visualize, type this into wolfram-alpha:
|
114
|
+
#
|
115
|
+
# plot (3*sin(x)+3), (3*sin(x+2*pi/3)+3), (3*sin(x+4*pi/3)+3)
|
116
|
+
|
117
|
+
# 6 has wide pretty gradients. 3 == lolcat, about half the width
|
118
|
+
@colors = (0...(6 * 7)).map { |n|
|
119
|
+
n *= 1.0 / 6
|
120
|
+
r = (3 * Math.sin(n ) + 3).to_i
|
121
|
+
g = (3 * Math.sin(n + 2 * PI_3) + 3).to_i
|
122
|
+
b = (3 * Math.sin(n + 4 * PI_3) + 3).to_i
|
123
|
+
|
124
|
+
# Then we take rgb and encode them in a single number using base 6.
|
125
|
+
# For some mysterious reason, we add 16... to clear the bottom 4 bits?
|
126
|
+
# Yes... they're ugly.
|
127
|
+
|
128
|
+
36 * r + 6 * g + b + 16
|
129
|
+
}
|
130
|
+
|
131
|
+
super
|
132
|
+
end
|
133
|
+
|
134
|
+
##
|
135
|
+
# Make the string even more colorful. Damnit.
|
136
|
+
|
137
|
+
def pride string
|
138
|
+
c = @colors[@index % @size]
|
139
|
+
@index += 1
|
140
|
+
"#{ESC}38;5;#{c}m#{string}#{NND}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|