opal 0.7.0.beta3 → 0.7.0.rc1
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/.gitmodules +4 -0
- data/.travis.yml +7 -3
- data/.yardopts +6 -0
- data/CHANGELOG.md +28 -0
- data/Gemfile +1 -1
- data/README.md +3 -12
- data/Rakefile +4 -150
- data/bin/opal-mspec +1 -1
- data/docs/compiler_directives.md +127 -0
- data/examples/rack/.gitignore +1 -0
- data/examples/rack/app/user.rb +1 -0
- data/lib/mspec/opal/special_calls.rb +15 -2
- data/lib/opal/builder.rb +15 -8
- data/lib/opal/compiler.rb +75 -4
- data/lib/opal/erb.rb +22 -2
- data/lib/opal/fragment.rb +17 -5
- data/lib/opal/nodes/def.rb +174 -53
- data/lib/opal/nodes/if.rb +14 -0
- data/lib/opal/nodes/module.rb +0 -1
- data/lib/opal/nodes/rescue.rb +10 -2
- data/lib/opal/nodes/scope.rb +0 -17
- data/lib/opal/parser.rb +83 -19
- data/lib/opal/parser/grammar.rb +2511 -2414
- data/lib/opal/parser/grammar.y +71 -9
- data/lib/opal/parser/lexer.rb +44 -12
- data/lib/opal/parser/parser_scope.rb +3 -0
- data/lib/opal/parser/sexp.rb +7 -1
- data/lib/opal/paths.rb +5 -5
- data/lib/opal/sprockets/environment.rb +2 -10
- data/lib/opal/sprockets/path_reader.rb +1 -1
- data/lib/opal/sprockets/processor.rb +1 -0
- data/lib/opal/sprockets/server.rb +2 -0
- data/lib/opal/util.rb +7 -2
- data/lib/opal/version.rb +1 -1
- data/opal.gemspec +1 -0
- data/opal/README.md +1 -1
- data/opal/corelib/dir.rb +1 -1
- data/opal/corelib/enumerable.rb +3 -1
- data/opal/corelib/error.rb +3 -0
- data/opal/corelib/file.rb +2 -0
- data/opal/corelib/hash.rb +3 -0
- data/opal/corelib/io.rb +15 -1
- data/opal/corelib/kernel.rb +8 -0
- data/opal/corelib/module.rb +42 -17
- data/opal/corelib/runtime.js +223 -49
- data/opal/corelib/string.rb +1 -1
- data/opal/corelib/struct.rb +1 -7
- data/spec/README.md +8 -0
- data/spec/filters/bugs/language.rb +1 -0
- data/spec/filters/bugs/module.rb +4 -0
- data/spec/filters/unsupported/frozen.rb +2 -0
- data/spec/lib/compiler/pre_processed_conditionals_spec.rb +87 -0
- data/spec/lib/compiler_spec.rb +1 -67
- data/spec/lib/fixtures/file_with_directives.js +2 -0
- data/spec/lib/fixtures/required_file.js +1 -0
- data/spec/lib/parser/def_spec.rb +29 -16
- data/spec/lib/parser/variables_spec.rb +5 -5
- data/spec/lib/sprockets/path_reader_spec.rb +24 -8
- data/spec/lib/sprockets/server_spec.rb +10 -3
- data/spec/opal/core/date_spec.rb +14 -0
- data/spec/opal/core/language/versions/def_2_0_spec.rb +62 -0
- data/spec/opal/core/language_spec.rb +23 -0
- data/spec/opal/core/runtime/donate_spec.rb +53 -0
- data/spec/opal/stdlib/native/native_alias_spec.rb +19 -0
- data/spec/opal/stdlib/native/native_class_spec.rb +18 -0
- data/spec/opal/stdlib/native/native_module_spec.rb +13 -0
- data/spec/rubyspecs +2 -0
- data/stdlib/buffer.rb +1 -0
- data/stdlib/date.rb +18 -0
- data/stdlib/encoding.rb +3 -2
- data/stdlib/minitest.rb +780 -0
- data/stdlib/minitest/assertions.rb +662 -0
- data/stdlib/minitest/autorun.rb +12 -0
- data/stdlib/minitest/benchmark.rb +426 -0
- data/stdlib/minitest/expectations.rb +281 -0
- data/stdlib/minitest/hell.rb +11 -0
- data/stdlib/minitest/mock.rb +220 -0
- data/stdlib/minitest/parallel.rb +65 -0
- data/stdlib/minitest/pride.rb +4 -0
- data/stdlib/minitest/pride_plugin.rb +142 -0
- data/stdlib/minitest/spec.rb +310 -0
- data/stdlib/minitest/test.rb +293 -0
- data/stdlib/minitest/unit.rb +45 -0
- data/stdlib/native.rb +12 -3
- data/stdlib/nodejs/process.rb +16 -2
- data/stdlib/promise.rb +99 -0
- data/stdlib/test/unit.rb +10 -0
- data/stdlib/thread.rb +4 -0
- data/tasks/building.rake +58 -0
- data/tasks/documentation.rake +38 -0
- data/tasks/documenting.rake +37 -0
- data/tasks/testing.rake +102 -0
- metadata +57 -2
@@ -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, &block) # :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, &block)
|
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,65 @@
|
|
1
|
+
module Minitest
|
2
|
+
module Parallel
|
3
|
+
|
4
|
+
##
|
5
|
+
# The engine used to run multiple tests in parallel.
|
6
|
+
|
7
|
+
class Executor
|
8
|
+
|
9
|
+
##
|
10
|
+
# The size of the pool of workers.
|
11
|
+
|
12
|
+
attr_reader :size
|
13
|
+
|
14
|
+
##
|
15
|
+
# Create a parallel test executor of with +size+ workers.
|
16
|
+
|
17
|
+
def initialize size
|
18
|
+
@size = size
|
19
|
+
@queue = Queue.new
|
20
|
+
# @pool = size.times.map {
|
21
|
+
# Thread.new(@queue) do |queue|
|
22
|
+
# Thread.current.abort_on_exception = true
|
23
|
+
# while job = queue.pop
|
24
|
+
# klass, method, reporter = job
|
25
|
+
# result = Minitest.run_one_method klass, method
|
26
|
+
# reporter.synchronize { reporter.record result }
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
# }
|
30
|
+
end
|
31
|
+
|
32
|
+
##
|
33
|
+
# Add a job to the queue
|
34
|
+
|
35
|
+
def << work; @queue << work; end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Shuts down the pool of workers by signalling them to quit and
|
39
|
+
# waiting for them all to finish what they're currently working
|
40
|
+
# on.
|
41
|
+
|
42
|
+
def shutdown
|
43
|
+
# size.times { @queue << nil }
|
44
|
+
# @pool.each(&:join)
|
45
|
+
@queue.each do |job|
|
46
|
+
klass, method, reporter = job
|
47
|
+
result = Minitest.run_one_method klass, method
|
48
|
+
reporter.record result
|
49
|
+
# reporter.synchronize { reporter.record result }
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module Test
|
55
|
+
def _synchronize; Test.io_lock.synchronize { yield }; end # :nodoc:
|
56
|
+
|
57
|
+
module ClassMethods
|
58
|
+
def run_one_method klass, method_name, reporter # :nodoc:
|
59
|
+
Minitest.parallel_executor << [klass, method_name, reporter]
|
60
|
+
end
|
61
|
+
def test_order; :parallel; end # :nodoc:
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "minitest"
|
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
|
+
end
|
56
|
+
|
57
|
+
##
|
58
|
+
# Wrap print to colorize the output.
|
59
|
+
|
60
|
+
def print o
|
61
|
+
case o
|
62
|
+
when "." then
|
63
|
+
io.print pride o
|
64
|
+
when "E", "F" then
|
65
|
+
io.print "#{ESC}41m#{ESC}37m#{o}#{NND}"
|
66
|
+
when "S" then
|
67
|
+
io.print pride o
|
68
|
+
else
|
69
|
+
io.print o
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def puts(*o) # :nodoc:
|
74
|
+
o.map! { |s|
|
75
|
+
s.to_s.sub(/Finished/) {
|
76
|
+
@index = 0
|
77
|
+
'Fabulous run'.split(//).map { |c|
|
78
|
+
pride(c)
|
79
|
+
}.join
|
80
|
+
}
|
81
|
+
}
|
82
|
+
|
83
|
+
io.puts(*o)
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Color a string.
|
88
|
+
|
89
|
+
def pride string
|
90
|
+
string = "*" if string == "."
|
91
|
+
c = @colors[@index % @size]
|
92
|
+
@index += 1
|
93
|
+
"#{ESC}#{c}m#{string}#{NND}"
|
94
|
+
end
|
95
|
+
|
96
|
+
def method_missing msg, *args # :nodoc:
|
97
|
+
io.send(msg, *args)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
##
|
102
|
+
# If you thought the PrideIO was colorful...
|
103
|
+
#
|
104
|
+
# (Inspired by lolcat, but with clean math)
|
105
|
+
|
106
|
+
class PrideLOL < PrideIO
|
107
|
+
PI_3 = Math::PI / 3 # :nodoc:
|
108
|
+
|
109
|
+
def initialize io # :nodoc:
|
110
|
+
# walk red, green, and blue around a circle separated by equal thirds.
|
111
|
+
#
|
112
|
+
# To visualize, type this into wolfram-alpha:
|
113
|
+
#
|
114
|
+
# plot (3*sin(x)+3), (3*sin(x+2*pi/3)+3), (3*sin(x+4*pi/3)+3)
|
115
|
+
|
116
|
+
# 6 has wide pretty gradients. 3 == lolcat, about half the width
|
117
|
+
@colors = (0...(6 * 7)).map { |n|
|
118
|
+
n *= 1.0 / 6
|
119
|
+
r = (3 * Math.sin(n ) + 3).to_i
|
120
|
+
g = (3 * Math.sin(n + 2 * PI_3) + 3).to_i
|
121
|
+
b = (3 * Math.sin(n + 4 * PI_3) + 3).to_i
|
122
|
+
|
123
|
+
# Then we take rgb and encode them in a single number using base 6.
|
124
|
+
# For some mysterious reason, we add 16... to clear the bottom 4 bits?
|
125
|
+
# Yes... they're ugly.
|
126
|
+
|
127
|
+
36 * r + 6 * g + b + 16
|
128
|
+
}
|
129
|
+
|
130
|
+
super
|
131
|
+
end
|
132
|
+
|
133
|
+
##
|
134
|
+
# Make the string even more colorful. Damnit.
|
135
|
+
|
136
|
+
def pride string
|
137
|
+
c = @colors[@index % @size]
|
138
|
+
@index += 1
|
139
|
+
"#{ESC}38;5;#{c}m#{string}#{NND}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
require "minitest/test"
|
2
|
+
|
3
|
+
class Module # :nodoc:
|
4
|
+
def infect_an_assertion meth, new_name, dont_flip = false # :nodoc:
|
5
|
+
# warn "%-22p -> %p %p" % [meth, new_name, dont_flip]
|
6
|
+
dont_flip = !!dont_flip
|
7
|
+
self.class_eval do
|
8
|
+
define_method(new_name) do |*args|
|
9
|
+
case
|
10
|
+
when dont_flip
|
11
|
+
Minitest::Spec.current.send(meth, self, *args)
|
12
|
+
when Proc === self
|
13
|
+
Minitest::Spec.current.send(meth, *args, &self)
|
14
|
+
else
|
15
|
+
Minitest::Spec.current.send(meth, args.first, self, *args[1..-1])
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# <<-EOM
|
21
|
+
# def #{new_name} *args
|
22
|
+
# case
|
23
|
+
# when #{dont_flip} then
|
24
|
+
# Minitest::Spec.current.#{meth}(self, *args)
|
25
|
+
# when Proc === self then
|
26
|
+
# Minitest::Spec.current.#{meth}(*args, &self)
|
27
|
+
# else
|
28
|
+
# Minitest::Spec.current.#{meth}(args.first, self, *args[1..-1])
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
# EOM
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module Kernel # :nodoc:
|
36
|
+
##
|
37
|
+
# Describe a series of expectations for a given target +desc+.
|
38
|
+
#
|
39
|
+
# Defines a test class subclassing from either Minitest::Spec or
|
40
|
+
# from the surrounding describe's class. The surrounding class may
|
41
|
+
# subclass Minitest::Spec manually in order to easily share code:
|
42
|
+
#
|
43
|
+
# class MySpec < Minitest::Spec
|
44
|
+
# # ... shared code ...
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# class TestStuff < MySpec
|
48
|
+
# it "does stuff" do
|
49
|
+
# # shared code available here
|
50
|
+
# end
|
51
|
+
# describe "inner stuff" do
|
52
|
+
# it "still does stuff" do
|
53
|
+
# # ...and here
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# For more information on getting started with writing specs, see:
|
59
|
+
#
|
60
|
+
# http://www.rubyinside.com/a-minitestspec-tutorial-elegant-spec-style-testing-that-comes-with-ruby-5354.html
|
61
|
+
#
|
62
|
+
# For some suggestions on how to improve your specs, try:
|
63
|
+
#
|
64
|
+
# http://betterspecs.org
|
65
|
+
#
|
66
|
+
# but do note that several items there are debatable or specific to
|
67
|
+
# rspec.
|
68
|
+
#
|
69
|
+
# For more information about expectations, see Minitest::Expectations.
|
70
|
+
|
71
|
+
def describe desc, *additional_desc, &block # :doc:
|
72
|
+
stack = Minitest::Spec.describe_stack
|
73
|
+
name = [stack.last, desc, *additional_desc].compact.join("::")
|
74
|
+
sclas = stack.last || if Class === self && is_a?(Minitest::Spec::DSL) then
|
75
|
+
self
|
76
|
+
else
|
77
|
+
Minitest::Spec.spec_type desc, *additional_desc
|
78
|
+
end
|
79
|
+
|
80
|
+
cls = sclas.create name, desc
|
81
|
+
|
82
|
+
stack.push cls
|
83
|
+
cls.class_eval(&block)
|
84
|
+
stack.pop
|
85
|
+
cls
|
86
|
+
end
|
87
|
+
private :describe
|
88
|
+
end
|
89
|
+
|
90
|
+
##
|
91
|
+
# Minitest::Spec -- The faster, better, less-magical spec framework!
|
92
|
+
#
|
93
|
+
# For a list of expectations, see Minitest::Expectations.
|
94
|
+
|
95
|
+
class Minitest::Spec < Minitest::Test
|
96
|
+
|
97
|
+
def self.current # :nodoc:
|
98
|
+
Thread.current[:current_spec]
|
99
|
+
end
|
100
|
+
|
101
|
+
def initialize name # :nodoc:
|
102
|
+
super
|
103
|
+
Thread.current[:current_spec] = self
|
104
|
+
end
|
105
|
+
|
106
|
+
##
|
107
|
+
# Oh look! A Minitest::Spec::DSL module! Eat your heart out DHH.
|
108
|
+
|
109
|
+
module DSL
|
110
|
+
##
|
111
|
+
# Contains pairs of matchers and Spec classes to be used to
|
112
|
+
# calculate the superclass of a top-level describe. This allows for
|
113
|
+
# automatically customizable spec types.
|
114
|
+
#
|
115
|
+
# See: register_spec_type and spec_type
|
116
|
+
|
117
|
+
TYPES = [[//, Minitest::Spec]]
|
118
|
+
|
119
|
+
##
|
120
|
+
# Register a new type of spec that matches the spec's description.
|
121
|
+
# This method can take either a Regexp and a spec class or a spec
|
122
|
+
# class and a block that takes the description and returns true if
|
123
|
+
# it matches.
|
124
|
+
#
|
125
|
+
# Eg:
|
126
|
+
#
|
127
|
+
# register_spec_type(/Controller$/, Minitest::Spec::Rails)
|
128
|
+
#
|
129
|
+
# or:
|
130
|
+
#
|
131
|
+
# register_spec_type(Minitest::Spec::RailsModel) do |desc|
|
132
|
+
# desc.superclass == ActiveRecord::Base
|
133
|
+
# end
|
134
|
+
|
135
|
+
def register_spec_type(*args, &block)
|
136
|
+
if block then
|
137
|
+
matcher, klass = block, args.first
|
138
|
+
else
|
139
|
+
matcher, klass = *args
|
140
|
+
end
|
141
|
+
TYPES.unshift [matcher, klass]
|
142
|
+
end
|
143
|
+
|
144
|
+
##
|
145
|
+
# Figure out the spec class to use based on a spec's description. Eg:
|
146
|
+
#
|
147
|
+
# spec_type("BlahController") # => Minitest::Spec::Rails
|
148
|
+
|
149
|
+
def spec_type desc, *additional
|
150
|
+
TYPES.find { |matcher, klass|
|
151
|
+
if matcher.respond_to? :call then
|
152
|
+
matcher.call desc, *additional
|
153
|
+
else
|
154
|
+
matcher === desc.to_s
|
155
|
+
end
|
156
|
+
}.last
|
157
|
+
end
|
158
|
+
|
159
|
+
def describe_stack # :nodoc:
|
160
|
+
Thread.current[:describe_stack] ||= []
|
161
|
+
end
|
162
|
+
|
163
|
+
##
|
164
|
+
# Returns the children of this spec.
|
165
|
+
|
166
|
+
def children
|
167
|
+
@children ||= []
|
168
|
+
end
|
169
|
+
|
170
|
+
def nuke_test_methods! # :nodoc:
|
171
|
+
self.public_instance_methods.grep(/^test_/).each do |name|
|
172
|
+
self.send :undef_method, name
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
##
|
177
|
+
# Define a 'before' action. Inherits the way normal methods should.
|
178
|
+
#
|
179
|
+
# NOTE: +type+ is ignored and is only there to make porting easier.
|
180
|
+
#
|
181
|
+
# Equivalent to Minitest::Test#setup.
|
182
|
+
|
183
|
+
def before type = nil, &block
|
184
|
+
define_method :setup do
|
185
|
+
super()
|
186
|
+
self.instance_eval(&block)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
##
|
191
|
+
# Define an 'after' action. Inherits the way normal methods should.
|
192
|
+
#
|
193
|
+
# NOTE: +type+ is ignored and is only there to make porting easier.
|
194
|
+
#
|
195
|
+
# Equivalent to Minitest::Test#teardown.
|
196
|
+
|
197
|
+
def after type = nil, &block
|
198
|
+
define_method :teardown do
|
199
|
+
self.instance_eval(&block)
|
200
|
+
super()
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
##
|
205
|
+
# Define an expectation with name +desc+. Name gets morphed to a
|
206
|
+
# proper test method name. For some freakish reason, people who
|
207
|
+
# write specs don't like class inheritance, so this goes way out of
|
208
|
+
# its way to make sure that expectations aren't inherited.
|
209
|
+
#
|
210
|
+
# This is also aliased to #specify and doesn't require a +desc+ arg.
|
211
|
+
#
|
212
|
+
# Hint: If you _do_ want inheritance, use minitest/test. You can mix
|
213
|
+
# and match between assertions and expectations as much as you want.
|
214
|
+
|
215
|
+
def it desc = "anonymous", &block
|
216
|
+
block ||= proc { skip "(no tests defined)" }
|
217
|
+
|
218
|
+
@specs ||= 0
|
219
|
+
@specs += 1
|
220
|
+
|
221
|
+
name = "test_%04d_%s" % [ @specs, desc ]
|
222
|
+
|
223
|
+
undef_klasses = self.children.reject { |c| c.public_method_defined? name }
|
224
|
+
|
225
|
+
define_method name, &block
|
226
|
+
|
227
|
+
undef_klasses.each do |undef_klass|
|
228
|
+
undef_klass.send :undef_method, name
|
229
|
+
end
|
230
|
+
|
231
|
+
name
|
232
|
+
end
|
233
|
+
|
234
|
+
##
|
235
|
+
# Essentially, define an accessor for +name+ with +block+.
|
236
|
+
#
|
237
|
+
# Why use let instead of def? I honestly don't know.
|
238
|
+
|
239
|
+
def let name, &block
|
240
|
+
name = name.to_s
|
241
|
+
pre, post = "let '#{name}' cannot ", ". Please use another name."
|
242
|
+
methods = Minitest::Spec.instance_methods.map(&:to_s) - %w[subject]
|
243
|
+
raise ArgumentError, "#{pre}begin with 'test'#{post}" if
|
244
|
+
name =~ /\Atest/
|
245
|
+
raise ArgumentError, "#{pre}override a method in Minitest::Spec#{post}" if
|
246
|
+
methods.include? name
|
247
|
+
|
248
|
+
define_method name do
|
249
|
+
@_memoized ||= {}
|
250
|
+
@_memoized.fetch(name) { |k| @_memoized[k] = instance_eval(&block) }
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
##
|
255
|
+
# Another lazy man's accessor generator. Made even more lazy by
|
256
|
+
# setting the name for you to +subject+.
|
257
|
+
|
258
|
+
def subject &block
|
259
|
+
let :subject, &block
|
260
|
+
end
|
261
|
+
|
262
|
+
def create name, desc # :nodoc:
|
263
|
+
cls = Class.new(self) do
|
264
|
+
@name = name
|
265
|
+
@desc = desc
|
266
|
+
|
267
|
+
nuke_test_methods!
|
268
|
+
end
|
269
|
+
|
270
|
+
children << cls
|
271
|
+
|
272
|
+
cls
|
273
|
+
end
|
274
|
+
|
275
|
+
def name # :nodoc:
|
276
|
+
defined?(@name) ? @name : super
|
277
|
+
end
|
278
|
+
|
279
|
+
def to_s # :nodoc:
|
280
|
+
name # Can't alias due to 1.8.7, not sure why
|
281
|
+
end
|
282
|
+
|
283
|
+
# :stopdoc:
|
284
|
+
attr_reader :desc
|
285
|
+
alias :specify :it
|
286
|
+
|
287
|
+
module InstanceMethods
|
288
|
+
def before_setup
|
289
|
+
super
|
290
|
+
Thread.current[:current_spec] = self
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def self.extended obj
|
295
|
+
obj.send :include, InstanceMethods
|
296
|
+
end
|
297
|
+
|
298
|
+
# :startdoc:
|
299
|
+
end
|
300
|
+
|
301
|
+
extend DSL
|
302
|
+
|
303
|
+
TYPES = DSL::TYPES # :nodoc:
|
304
|
+
end
|
305
|
+
|
306
|
+
require "minitest/expectations"
|
307
|
+
|
308
|
+
class Object # :nodoc:
|
309
|
+
include Minitest::Expectations unless ENV["MT_NO_EXPECTATIONS"]
|
310
|
+
end
|