betatest 0.0.1

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.
@@ -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