betatest 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,11 @@
1
+ require "betatest/parallel"
2
+
3
+ class Betatest::Test
4
+ class << self
5
+ alias :old_test_order :test_order # :nodoc:
6
+
7
+ def test_order # :nodoc:
8
+ :parallel
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,220 @@
1
+ class MockExpectationError < StandardError; end # :nodoc:
2
+
3
+ module Betatest # :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 = "__betatest_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,40 @@
1
+ module Betatest
2
+ module Parallel
3
+ class Executor
4
+ attr_reader :size
5
+
6
+ def initialize size
7
+ @size = size
8
+ @queue = Queue.new
9
+ @pool = size.times.map {
10
+ Thread.new(@queue) do |queue|
11
+ Thread.current.abort_on_exception = true
12
+ while job = queue.pop
13
+ klass, method, reporter = job
14
+ result = Betatest.run_one_method klass, method
15
+ reporter.synchronize { reporter.record result }
16
+ end
17
+ end
18
+ }
19
+ end
20
+
21
+ def << work; @queue << work; end
22
+
23
+ def shutdown
24
+ size.times { @queue << nil }
25
+ @pool.each(&:join)
26
+ end
27
+ end
28
+
29
+ module Test
30
+ def _synchronize; Test.io_lock.synchronize { yield }; end
31
+
32
+ module ClassMethods
33
+ def run_one_method klass, method_name, reporter
34
+ Betatest.parallel_executor << [klass, method_name, reporter]
35
+ end
36
+ def test_order; :parallel; end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,4 @@
1
+ require "betatest"
2
+
3
+ Betatest.load_plugins
4
+ Betatest::PrideIO.pride!
@@ -0,0 +1,142 @@
1
+ require "betatest"
2
+
3
+ module Betatest
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(Betatest::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 betatest/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,294 @@
1
+ require 'betatest/unit'
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
+ self.class_eval <<-EOM
7
+ def #{new_name} *args
8
+ case
9
+ when #{!!dont_flip} then
10
+ Betatest::Spec.current.#{meth}(self, *args)
11
+ when Proc === self then
12
+ Betatest::Spec.current.#{meth}(*args, &self)
13
+ else
14
+ Betatest::Spec.current.#{meth}(args.first, self, *args[1..-1])
15
+ end
16
+ end
17
+ EOM
18
+ end
19
+ end
20
+
21
+ module Kernel # :nodoc:
22
+ ##
23
+ # Describe a series of expectations for a given target +desc+.
24
+ #
25
+ # Defines a test class subclassing from either Betatest::Spec or
26
+ # from the surrounding describe's class. The surrounding class may
27
+ # subclass Betatest::Spec manually in order to easily share code:
28
+ #
29
+ # class MySpec < Betatest::Spec
30
+ # # ... shared code ...
31
+ # end
32
+ #
33
+ # class TestStuff < MySpec
34
+ # it "does stuff" do
35
+ # # shared code available here
36
+ # end
37
+ # describe "inner stuff" do
38
+ # it "still does stuff" do
39
+ # # ...and here
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # For more information on getting started with writing specs, see:
45
+ #
46
+ # http://www.rubyinside.com/a-betatestspec-tutorial-elegant-spec-style-testing-that-comes-with-ruby-5354.html
47
+ #
48
+ # For some suggestions on how to improve your specs, try:
49
+ #
50
+ # http://betterspecs.org
51
+ #
52
+ # but do note that several items there are debatable or specific to
53
+ # rspec.
54
+ #
55
+ # For more information about expectations, see Betatest::Expectations.
56
+
57
+ def describe desc, additional_desc = nil, &block # :doc:
58
+ stack = Betatest::Spec.describe_stack
59
+ name = [stack.last, desc, additional_desc].compact.join("::")
60
+ sclas = stack.last || if Class === self && is_a?(Betatest::Spec::DSL) then
61
+ self
62
+ else
63
+ Betatest::Spec.spec_type desc
64
+ end
65
+
66
+ cls = sclas.create name, desc
67
+
68
+ stack.push cls
69
+ cls.class_eval(&block)
70
+ stack.pop
71
+ cls
72
+ end
73
+ private :describe
74
+ end
75
+
76
+ ##
77
+ # Betatest::Spec -- The faster, better, less-magical spec framework!
78
+ #
79
+ # For a list of expectations, see Betatest::Expectations.
80
+
81
+ class Betatest::Spec < Betatest::Test
82
+
83
+ def self.current # :nodoc:
84
+ Thread.current[:current_spec]
85
+ end
86
+
87
+ def initialize name # :nodoc:
88
+ super
89
+ Thread.current[:current_spec] = self
90
+ end
91
+
92
+ ##
93
+ # Oh look! A Betatest::Spec::DSL module! Eat your heart out DHH.
94
+
95
+ module DSL
96
+ ##
97
+ # Contains pairs of matchers and Spec classes to be used to
98
+ # calculate the superclass of a top-level describe. This allows for
99
+ # automatically customizable spec types.
100
+ #
101
+ # See: register_spec_type and spec_type
102
+
103
+ TYPES = [[//, Betatest::Spec]]
104
+
105
+ ##
106
+ # Register a new type of spec that matches the spec's description.
107
+ # This method can take either a Regexp and a spec class or a spec
108
+ # class and a block that takes the description and returns true if
109
+ # it matches.
110
+ #
111
+ # Eg:
112
+ #
113
+ # register_spec_type(/Controller$/, Betatest::Spec::Rails)
114
+ #
115
+ # or:
116
+ #
117
+ # register_spec_type(Betatest::Spec::RailsModel) do |desc|
118
+ # desc.superclass == ActiveRecord::Base
119
+ # end
120
+
121
+ def register_spec_type(*args, &block)
122
+ if block then
123
+ matcher, klass = block, args.first
124
+ else
125
+ matcher, klass = *args
126
+ end
127
+ TYPES.unshift [matcher, klass]
128
+ end
129
+
130
+ ##
131
+ # Figure out the spec class to use based on a spec's description. Eg:
132
+ #
133
+ # spec_type("BlahController") # => Betatest::Spec::Rails
134
+
135
+ def spec_type desc
136
+ TYPES.find { |matcher, klass|
137
+ if matcher.respond_to? :call then
138
+ matcher.call desc
139
+ else
140
+ matcher === desc.to_s
141
+ end
142
+ }.last
143
+ end
144
+
145
+ def describe_stack # :nodoc:
146
+ Thread.current[:describe_stack] ||= []
147
+ end
148
+
149
+ ##
150
+ # Returns the children of this spec.
151
+
152
+ def children
153
+ @children ||= []
154
+ end
155
+
156
+ def nuke_test_methods! # :nodoc:
157
+ self.public_instance_methods.grep(/^test_/).each do |name|
158
+ self.send :undef_method, name
159
+ end
160
+ end
161
+
162
+ ##
163
+ # Define a 'before' action. Inherits the way normal methods should.
164
+ #
165
+ # NOTE: +type+ is ignored and is only there to make porting easier.
166
+ #
167
+ # Equivalent to Betatest::Test#setup.
168
+
169
+ def before type = nil, &block
170
+ define_method :setup do
171
+ super()
172
+ self.instance_eval(&block)
173
+ end
174
+ end
175
+
176
+ ##
177
+ # Define an 'after' 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 Betatest::Test#teardown.
182
+
183
+ def after type = nil, &block
184
+ define_method :teardown do
185
+ self.instance_eval(&block)
186
+ super()
187
+ end
188
+ end
189
+
190
+ ##
191
+ # Define an expectation with name +desc+. Name gets morphed to a
192
+ # proper test method name. For some freakish reason, people who
193
+ # write specs don't like class inheritance, so this goes way out of
194
+ # its way to make sure that expectations aren't inherited.
195
+ #
196
+ # This is also aliased to #specify and doesn't require a +desc+ arg.
197
+ #
198
+ # Hint: If you _do_ want inheritance, use betatest/test. You can mix
199
+ # and match between assertions and expectations as much as you want.
200
+
201
+ def it desc = "anonymous", &block
202
+ block ||= proc { skip "(no tests defined)" }
203
+
204
+ @specs ||= 0
205
+ @specs += 1
206
+
207
+ name = "test_%04d_%s" % [ @specs, desc ]
208
+
209
+ define_method name, &block
210
+
211
+ self.children.each do |mod|
212
+ mod.send :undef_method, name if mod.public_method_defined? name
213
+ end
214
+
215
+ name
216
+ end
217
+
218
+ ##
219
+ # Essentially, define an accessor for +name+ with +block+.
220
+ #
221
+ # Why use let instead of def? I honestly don't know.
222
+
223
+ def let name, &block
224
+ name = name.to_s
225
+ pre, post = "let '#{name}' cannot ", ". Please use another name."
226
+ methods = Betatest::Spec.instance_methods.map(&:to_s) - %w[subject]
227
+ raise ArgumentError, "#{pre}begin with 'test'#{post}" if
228
+ name =~ /\Atest/
229
+ raise ArgumentError, "#{pre}override a method in Betatest::Spec#{post}" if
230
+ methods.include? name
231
+
232
+ define_method name do
233
+ @_memoized ||= {}
234
+ @_memoized.fetch(name) { |k| @_memoized[k] = instance_eval(&block) }
235
+ end
236
+ end
237
+
238
+ ##
239
+ # Another lazy man's accessor generator. Made even more lazy by
240
+ # setting the name for you to +subject+.
241
+
242
+ def subject &block
243
+ let :subject, &block
244
+ end
245
+
246
+ def create name, desc # :nodoc:
247
+ cls = Class.new(self) do
248
+ @name = name
249
+ @desc = desc
250
+
251
+ nuke_test_methods!
252
+ end
253
+
254
+ children << cls
255
+
256
+ cls
257
+ end
258
+
259
+ def name # :nodoc:
260
+ defined?(@name) ? @name : super
261
+ end
262
+
263
+ def to_s # :nodoc:
264
+ name # Can't alias due to 1.8.7, not sure why
265
+ end
266
+
267
+ # :stopdoc:
268
+ attr_reader :desc
269
+ alias :specify :it
270
+
271
+ module InstanceMethods
272
+ def before_setup
273
+ super
274
+ Thread.current[:current_spec] = self
275
+ end
276
+ end
277
+
278
+ def self.extended obj
279
+ obj.send :include, InstanceMethods
280
+ end
281
+
282
+ # :startdoc:
283
+ end
284
+
285
+ extend DSL
286
+
287
+ TYPES = DSL::TYPES # :nodoc:
288
+ end
289
+
290
+ require "betatest/expectations"
291
+
292
+ class Object # :nodoc:
293
+ include Betatest::Expectations unless ENV["MT_NO_EXPECTATIONS"]
294
+ end