mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Restored test/unit
git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@19739 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
parent
3e66bda38e
commit
30e76a6af3
21 changed files with 3307 additions and 0 deletions
14
lib/test/unit/assertionfailederror.rb
Normal file
14
lib/test/unit/assertionfailederror.rb
Normal file
|
@ -0,0 +1,14 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
|
||||
# Thrown by Test::Unit::Assertions when an assertion fails.
|
||||
class AssertionFailedError < StandardError
|
||||
end
|
||||
end
|
||||
end
|
622
lib/test/unit/assertions.rb
Normal file
622
lib/test/unit/assertions.rb
Normal file
|
@ -0,0 +1,622 @@
|
|||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'test/unit/assertionfailederror'
|
||||
require 'test/unit/util/backtracefilter'
|
||||
|
||||
module Test # :nodoc:
|
||||
module Unit # :nodoc:
|
||||
|
||||
##
|
||||
# Test::Unit::Assertions contains the standard Test::Unit assertions.
|
||||
# Assertions is included in Test::Unit::TestCase.
|
||||
#
|
||||
# To include it in your own code and use its functionality, you simply
|
||||
# need to rescue Test::Unit::AssertionFailedError. Additionally you may
|
||||
# override add_assertion to get notified whenever an assertion is made.
|
||||
#
|
||||
# Notes:
|
||||
# * The message to each assertion, if given, will be propagated with the
|
||||
# failure.
|
||||
# * It is easy to add your own assertions based on assert_block().
|
||||
#
|
||||
# = Example Custom Assertion
|
||||
#
|
||||
# def deny(boolean, message = nil)
|
||||
# message = build_message message, '<?> is not false or nil.', boolean
|
||||
# assert_block message do
|
||||
# not boolean
|
||||
# end
|
||||
# end
|
||||
|
||||
module Assertions
|
||||
|
||||
##
|
||||
# The assertion upon which all other assertions are based. Passes if the
|
||||
# block yields true.
|
||||
#
|
||||
# Example:
|
||||
# assert_block "Couldn't do the thing" do
|
||||
# do_the_thing
|
||||
# end
|
||||
|
||||
public
|
||||
def assert_block(message="assert_block failed.") # :yields:
|
||||
_wrap_assertion do
|
||||
if (! yield)
|
||||
raise AssertionFailedError.new(message.to_s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Asserts that +boolean+ is not false or nil.
|
||||
#
|
||||
# Example:
|
||||
# assert [1, 2].include?(5)
|
||||
|
||||
public
|
||||
def assert(boolean, message=nil)
|
||||
_wrap_assertion do
|
||||
assert_block("assert should not be called with a block.") { !block_given? }
|
||||
assert_block(build_message(message, "<?> is not true.", boolean)) { boolean }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +expected+ == +actual.
|
||||
#
|
||||
# Note that the ordering of arguments is important, since a helpful
|
||||
# error message is generated when this one fails that tells you the
|
||||
# values of expected and actual.
|
||||
#
|
||||
# Example:
|
||||
# assert_equal 'MY STRING', 'my string'.upcase
|
||||
|
||||
public
|
||||
def assert_equal(expected, actual, message=nil)
|
||||
full_message = build_message(message, <<EOT, expected, actual)
|
||||
<?> expected but was
|
||||
<?>.
|
||||
EOT
|
||||
assert_block(full_message) { expected == actual }
|
||||
end
|
||||
|
||||
private
|
||||
def _check_exception_class(args) # :nodoc:
|
||||
args.partition do |klass|
|
||||
next if klass.instance_of?(Module)
|
||||
assert(Exception >= klass, "Should expect a class of exception, #{klass}")
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def _expected_exception?(actual_exception, exceptions, modules) # :nodoc:
|
||||
exceptions.include?(actual_exception.class) or
|
||||
modules.any? {|mod| actual_exception.is_a?(mod)}
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if the block raises one of the given exceptions.
|
||||
#
|
||||
# Example:
|
||||
# assert_raise RuntimeError, LoadError do
|
||||
# raise 'Boom!!!'
|
||||
# end
|
||||
|
||||
public
|
||||
def assert_raise(*args)
|
||||
_wrap_assertion do
|
||||
if Module === args.last
|
||||
message = ""
|
||||
else
|
||||
message = args.pop
|
||||
end
|
||||
exceptions, modules = _check_exception_class(args)
|
||||
expected = args.size == 1 ? args.first : args
|
||||
actual_exception = nil
|
||||
full_message = build_message(message, "<?> exception expected but none was thrown.", expected)
|
||||
assert_block(full_message) do
|
||||
begin
|
||||
yield
|
||||
rescue Exception => actual_exception
|
||||
break
|
||||
end
|
||||
false
|
||||
end
|
||||
full_message = build_message(message, "<?> exception expected but was\n?", expected, actual_exception)
|
||||
assert_block(full_message) {_expected_exception?(actual_exception, exceptions, modules)}
|
||||
actual_exception
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Alias of assert_raise.
|
||||
#
|
||||
# Will be deprecated in 1.9, and removed in 2.0.
|
||||
|
||||
public
|
||||
def assert_raises(*args, &block)
|
||||
assert_raise(*args, &block)
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +object+ .instance_of? +klass+
|
||||
#
|
||||
# Example:
|
||||
# assert_instance_of String, 'foo'
|
||||
|
||||
public
|
||||
def assert_instance_of(klass, object, message="")
|
||||
_wrap_assertion do
|
||||
assert_equal(Class, klass.class, "assert_instance_of takes a Class as its first argument")
|
||||
full_message = build_message(message, <<EOT, object, klass, object.class)
|
||||
<?> expected to be an instance of
|
||||
<?> but was
|
||||
<?>.
|
||||
EOT
|
||||
assert_block(full_message){object.instance_of?(klass)}
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +object+ is nil.
|
||||
#
|
||||
# Example:
|
||||
# assert_nil [1, 2].uniq!
|
||||
|
||||
public
|
||||
def assert_nil(object, message="")
|
||||
assert_equal(nil, object, message)
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +object+ .kind_of? +klass+
|
||||
#
|
||||
# Example:
|
||||
# assert_kind_of Object, 'foo'
|
||||
|
||||
public
|
||||
def assert_kind_of(klass, object, message="")
|
||||
_wrap_assertion do
|
||||
assert(klass.kind_of?(Module), "The first parameter to assert_kind_of should be a kind_of Module.")
|
||||
full_message = build_message(message, "<?>\nexpected to be kind_of\\?\n<?> but was\n<?>.", object, klass, object.class)
|
||||
assert_block(full_message){object.kind_of?(klass)}
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +object+ .respond_to? +method+
|
||||
#
|
||||
# Example:
|
||||
# assert_respond_to 'bugbear', :slice
|
||||
|
||||
public
|
||||
def assert_respond_to(object, method, message="")
|
||||
_wrap_assertion do
|
||||
full_message = build_message(nil, "<?>\ngiven as the method name argument to #assert_respond_to must be a Symbol or #respond_to\\?(:to_str).", method)
|
||||
|
||||
assert_block(full_message) do
|
||||
method.kind_of?(Symbol) || method.respond_to?(:to_str)
|
||||
end
|
||||
full_message = build_message(message, <<EOT, object, object.class, method)
|
||||
<?>
|
||||
of type <?>
|
||||
expected to respond_to\\?<?>.
|
||||
EOT
|
||||
assert_block(full_message) { object.respond_to?(method) }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +string+ =~ +pattern+.
|
||||
#
|
||||
# Example:
|
||||
# assert_match(/\d+/, 'five, 6, seven')
|
||||
|
||||
public
|
||||
def assert_match(pattern, string, message="")
|
||||
_wrap_assertion do
|
||||
pattern = case(pattern)
|
||||
when String
|
||||
Regexp.new(Regexp.escape(pattern))
|
||||
else
|
||||
pattern
|
||||
end
|
||||
full_message = build_message(message, "<?> expected to be =~\n<?>.", string, pattern)
|
||||
assert_block(full_message) { string =~ pattern }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +actual+ .equal? +expected+ (i.e. they are the same
|
||||
# instance).
|
||||
#
|
||||
# Example:
|
||||
# o = Object.new
|
||||
# assert_same o, o
|
||||
|
||||
public
|
||||
def assert_same(expected, actual, message="")
|
||||
full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
|
||||
<?>
|
||||
with id <?> expected to be equal\\? to
|
||||
<?>
|
||||
with id <?>.
|
||||
EOT
|
||||
assert_block(full_message) { actual.equal?(expected) }
|
||||
end
|
||||
|
||||
##
|
||||
# Compares the +object1+ with +object2+ using +operator+.
|
||||
#
|
||||
# Passes if object1.__send__(operator, object2) is true.
|
||||
#
|
||||
# Example:
|
||||
# assert_operator 5, :>=, 4
|
||||
|
||||
public
|
||||
def assert_operator(object1, operator, object2, message="")
|
||||
_wrap_assertion do
|
||||
full_message = build_message(nil, "<?>\ngiven as the operator for #assert_operator must be a Symbol or #respond_to\\?(:to_str).", operator)
|
||||
assert_block(full_message){operator.kind_of?(Symbol) || operator.respond_to?(:to_str)}
|
||||
full_message = build_message(message, <<EOT, object1, AssertionMessage.literal(operator), object2)
|
||||
<?> expected to be
|
||||
?
|
||||
<?>.
|
||||
EOT
|
||||
assert_block(full_message) { object1.__send__(operator, object2) }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if block does not raise an exception.
|
||||
#
|
||||
# Example:
|
||||
# assert_nothing_raised do
|
||||
# [1, 2].uniq
|
||||
# end
|
||||
|
||||
public
|
||||
def assert_nothing_raised(*args)
|
||||
_wrap_assertion do
|
||||
if Module === args.last
|
||||
message = ""
|
||||
else
|
||||
message = args.pop
|
||||
end
|
||||
exceptions, modules = _check_exception_class(args)
|
||||
begin
|
||||
yield
|
||||
rescue Exception => e
|
||||
if ((args.empty? && !e.instance_of?(AssertionFailedError)) ||
|
||||
_expected_exception?(e, exceptions, modules))
|
||||
assert_block(build_message(message, "Exception raised:\n?", e)){false}
|
||||
else
|
||||
raise
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Flunk always fails.
|
||||
#
|
||||
# Example:
|
||||
# flunk 'Not done testing yet.'
|
||||
|
||||
public
|
||||
def flunk(message="Flunked")
|
||||
assert_block(build_message(message)){false}
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if ! +actual+ .equal? +expected+
|
||||
#
|
||||
# Example:
|
||||
# assert_not_same Object.new, Object.new
|
||||
|
||||
public
|
||||
def assert_not_same(expected, actual, message="")
|
||||
full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
|
||||
<?>
|
||||
with id <?> expected to not be equal\\? to
|
||||
<?>
|
||||
with id <?>.
|
||||
EOT
|
||||
assert_block(full_message) { !actual.equal?(expected) }
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +expected+ != +actual+
|
||||
#
|
||||
# Example:
|
||||
# assert_not_equal 'some string', 5
|
||||
|
||||
public
|
||||
def assert_not_equal(expected, actual, message="")
|
||||
full_message = build_message(message, "<?> expected to be != to\n<?>.", expected, actual)
|
||||
assert_block(full_message) { expected != actual }
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if ! +object+ .nil?
|
||||
#
|
||||
# Example:
|
||||
# assert_not_nil '1 two 3'.sub!(/two/, '2')
|
||||
|
||||
public
|
||||
def assert_not_nil(object, message="")
|
||||
full_message = build_message(message, "<?> expected to not be nil.", object)
|
||||
assert_block(full_message){!object.nil?}
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +regexp+ !~ +string+
|
||||
#
|
||||
# Example:
|
||||
# assert_no_match(/two/, 'one 2 three')
|
||||
|
||||
public
|
||||
def assert_no_match(regexp, string, message="")
|
||||
_wrap_assertion do
|
||||
assert_instance_of(Regexp, regexp, "The first argument to assert_no_match should be a Regexp.")
|
||||
full_message = build_message(message, "<?> expected to not match\n<?>.", regexp, string)
|
||||
assert_block(full_message) { regexp !~ string }
|
||||
end
|
||||
end
|
||||
|
||||
UncaughtThrow = {
|
||||
ArgumentError => /^uncaught throw (.+)$/,
|
||||
} #`
|
||||
|
||||
##
|
||||
# Passes if the block throws +expected_object+
|
||||
#
|
||||
# Example:
|
||||
# assert_throws :done do
|
||||
# throw :done
|
||||
# end
|
||||
|
||||
public
|
||||
def assert_throws(expected_object, message="", &proc)
|
||||
_wrap_assertion do
|
||||
assert_block("Should have passed a block to assert_throws."){block_given?}
|
||||
caught = true
|
||||
begin
|
||||
catch(expected_object) do
|
||||
proc.call
|
||||
caught = false
|
||||
end
|
||||
full_message = build_message(message, "<?> should have been thrown.", expected_object)
|
||||
assert_block(full_message){caught}
|
||||
rescue ArgumentError => error
|
||||
if UncaughtThrow[error.class] !~ error.message
|
||||
raise error
|
||||
end
|
||||
full_message = build_message(message, "<?> expected to be thrown but\n<#$1> was thrown.", expected_object)
|
||||
flunk(full_message)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if block does not throw anything.
|
||||
#
|
||||
# Example:
|
||||
# assert_nothing_thrown do
|
||||
# [1, 2].uniq
|
||||
# end
|
||||
|
||||
public
|
||||
def assert_nothing_thrown(message="", &proc)
|
||||
_wrap_assertion do
|
||||
assert(block_given?, "Should have passed a block to assert_nothing_thrown")
|
||||
begin
|
||||
proc.call
|
||||
rescue ArgumentError => error
|
||||
if UncaughtThrow[error.class] !~ error.message
|
||||
raise error
|
||||
end
|
||||
full_message = build_message(message, "<#$1> was thrown when nothing was expected")
|
||||
flunk(full_message)
|
||||
end
|
||||
assert(true, "Expected nothing to be thrown")
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if +expected_float+ and +actual_float+ are equal
|
||||
# within +delta+ tolerance.
|
||||
#
|
||||
# Example:
|
||||
# assert_in_delta 0.05, (50000.0 / 10**6), 0.00001
|
||||
|
||||
public
|
||||
def assert_in_delta(expected_float, actual_float, delta, message="")
|
||||
_wrap_assertion do
|
||||
{expected_float => "first float", actual_float => "second float", delta => "delta"}.each do |float, name|
|
||||
assert_respond_to(float, :to_f, "The arguments must respond to to_f; the #{name} did not")
|
||||
end
|
||||
assert_operator(delta, :>=, 0.0, "The delta should not be negative")
|
||||
full_message = build_message(message, <<EOT, expected_float, actual_float, delta)
|
||||
<?> and
|
||||
<?> expected to be within
|
||||
<?> of each other.
|
||||
EOT
|
||||
assert_block(full_message) { (expected_float.to_f - actual_float.to_f).abs <= delta.to_f }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Passes if the method send returns a true value.
|
||||
#
|
||||
# +send_array+ is composed of:
|
||||
# * A receiver
|
||||
# * A method
|
||||
# * Arguments to the method
|
||||
#
|
||||
# Example:
|
||||
# assert_send [[1, 2], :include?, 4]
|
||||
|
||||
public
|
||||
def assert_send(send_array, message="")
|
||||
_wrap_assertion do
|
||||
assert_instance_of(Array, send_array, "assert_send requires an array of send information")
|
||||
assert(send_array.size >= 2, "assert_send requires at least a receiver and a message name")
|
||||
full_message = build_message(message, <<EOT, send_array[0], AssertionMessage.literal(send_array[1].to_s), send_array[2..-1])
|
||||
<?> expected to respond to
|
||||
<?(?)> with a true value.
|
||||
EOT
|
||||
assert_block(full_message) { send_array[0].__send__(send_array[1], *send_array[2..-1]) }
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Builds a failure message. +head+ is added before the +template+ and
|
||||
# +arguments+ replaces the '?'s positionally in the template.
|
||||
|
||||
public
|
||||
def build_message(head, template=nil, *arguments) # :nodoc:
|
||||
template &&= template.chomp
|
||||
return AssertionMessage.new(head, template, arguments)
|
||||
end
|
||||
|
||||
private
|
||||
def _wrap_assertion # :nodoc:
|
||||
@_assertion_wrapped ||= false
|
||||
unless (@_assertion_wrapped)
|
||||
@_assertion_wrapped = true
|
||||
begin
|
||||
add_assertion
|
||||
return yield
|
||||
ensure
|
||||
@_assertion_wrapped = false
|
||||
end
|
||||
else
|
||||
return yield
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Called whenever an assertion is made. Define this in classes that
|
||||
# include Test::Unit::Assertions to record assertion counts.
|
||||
|
||||
private
|
||||
def add_assertion
|
||||
end
|
||||
|
||||
##
|
||||
# Select whether or not to use the pretty-printer. If this option is set
|
||||
# to false before any assertions are made, pp.rb will not be required.
|
||||
|
||||
public
|
||||
def self.use_pp=(value)
|
||||
AssertionMessage.use_pp = value
|
||||
end
|
||||
|
||||
# :stopdoc:
|
||||
|
||||
class AssertionMessage
|
||||
@use_pp = true
|
||||
class << self
|
||||
attr_accessor :use_pp
|
||||
end
|
||||
|
||||
class Literal
|
||||
def initialize(value)
|
||||
@value = value
|
||||
end
|
||||
|
||||
def inspect
|
||||
@value.to_s
|
||||
end
|
||||
end
|
||||
|
||||
class Template
|
||||
def self.create(string)
|
||||
parts = (string ? string.scan(/(?=[^\\])\?|(?:\\\?|[^\?])+/m) : [])
|
||||
self.new(parts)
|
||||
end
|
||||
|
||||
attr_reader :count
|
||||
|
||||
def initialize(parts)
|
||||
@parts = parts
|
||||
@count = parts.find_all{|e| e == '?'}.size
|
||||
end
|
||||
|
||||
def result(parameters)
|
||||
raise "The number of parameters does not match the number of substitutions." if(parameters.size != count)
|
||||
params = parameters.dup
|
||||
@parts.collect{|e| e == '?' ? params.shift : e.gsub(/\\\?/m, '?')}.join('')
|
||||
end
|
||||
end
|
||||
|
||||
def self.literal(value)
|
||||
Literal.new(value)
|
||||
end
|
||||
|
||||
include Util::BacktraceFilter
|
||||
|
||||
def initialize(head, template_string, parameters)
|
||||
@head = head
|
||||
@template_string = template_string
|
||||
@parameters = parameters
|
||||
end
|
||||
|
||||
def convert(object)
|
||||
case object
|
||||
when Exception
|
||||
<<EOM.chop
|
||||
Class: <#{convert(object.class)}>
|
||||
Message: <#{convert(object.message)}>
|
||||
---Backtrace---
|
||||
#{filter_backtrace(object.backtrace).join("\n")}
|
||||
---------------
|
||||
EOM
|
||||
else
|
||||
if(self.class.use_pp)
|
||||
begin
|
||||
require 'pp'
|
||||
rescue LoadError
|
||||
self.class.use_pp = false
|
||||
return object.inspect
|
||||
end unless(defined?(PP))
|
||||
PP.pp(object, '').chomp
|
||||
else
|
||||
object.inspect
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def template
|
||||
@template ||= Template.create(@template_string)
|
||||
end
|
||||
|
||||
def add_period(string)
|
||||
(string =~ /\.\Z/ ? string : string + '.')
|
||||
end
|
||||
|
||||
def to_s
|
||||
message_parts = []
|
||||
if (@head)
|
||||
head = @head.to_s
|
||||
unless(head.empty?)
|
||||
message_parts << add_period(head)
|
||||
end
|
||||
end
|
||||
tail = template.result(@parameters.collect{|e| convert(e)})
|
||||
message_parts << tail unless(tail.empty?)
|
||||
message_parts.join("\n")
|
||||
end
|
||||
end
|
||||
|
||||
# :startdoc:
|
||||
|
||||
end
|
||||
end
|
||||
end
|
217
lib/test/unit/autorunner.rb
Normal file
217
lib/test/unit/autorunner.rb
Normal file
|
@ -0,0 +1,217 @@
|
|||
require 'test/unit'
|
||||
require 'test/unit/ui/testrunnerutilities'
|
||||
require 'optparse'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
class AutoRunner
|
||||
def self.run(force_standalone=false, default_dir=nil, argv=ARGV, &block)
|
||||
r = new(force_standalone || standalone?, &block)
|
||||
r.base = default_dir
|
||||
r.process_args(argv)
|
||||
r.run
|
||||
end
|
||||
|
||||
def self.standalone?
|
||||
return false unless("-e" == $0)
|
||||
TestCase::DECENDANT_CLASSES.empty?
|
||||
end
|
||||
|
||||
RUNNERS = {
|
||||
:console => proc do |r|
|
||||
require 'test/unit/ui/console/testrunner'
|
||||
Test::Unit::UI::Console::TestRunner
|
||||
end,
|
||||
:gtk => proc do |r|
|
||||
require 'test/unit/ui/gtk/testrunner'
|
||||
Test::Unit::UI::GTK::TestRunner
|
||||
end,
|
||||
:gtk2 => proc do |r|
|
||||
require 'test/unit/ui/gtk2/testrunner'
|
||||
Test::Unit::UI::GTK2::TestRunner
|
||||
end,
|
||||
:fox => proc do |r|
|
||||
require 'test/unit/ui/fox/testrunner'
|
||||
Test::Unit::UI::Fox::TestRunner
|
||||
end,
|
||||
:tk => proc do |r|
|
||||
require 'test/unit/ui/tk/testrunner'
|
||||
Test::Unit::UI::Tk::TestRunner
|
||||
end,
|
||||
}
|
||||
|
||||
OUTPUT_LEVELS = [
|
||||
[:silent, UI::SILENT],
|
||||
[:progress, UI::PROGRESS_ONLY],
|
||||
[:normal, UI::NORMAL],
|
||||
[:verbose, UI::VERBOSE],
|
||||
]
|
||||
|
||||
COLLECTORS = {
|
||||
:objectspace => proc do |r|
|
||||
require 'test/unit/collector/objectspace'
|
||||
c = Collector::ObjectSpace.new
|
||||
c.filter = r.filters
|
||||
c.collect($0.sub(/\.rb\Z/, ''))
|
||||
end,
|
||||
:dir => proc do |r|
|
||||
require 'test/unit/collector/dir'
|
||||
c = Collector::Dir.new
|
||||
c.filter = r.filters
|
||||
c.pattern.concat(r.pattern) if(r.pattern)
|
||||
c.exclude.concat(r.exclude) if(r.exclude)
|
||||
c.base = r.base
|
||||
$:.push(r.base) if r.base
|
||||
c.collect(*(r.to_run.empty? ? ['.'] : r.to_run))
|
||||
end,
|
||||
}
|
||||
|
||||
attr_reader :suite
|
||||
attr_accessor :output_level, :filters, :to_run, :pattern, :exclude, :base, :workdir
|
||||
attr_writer :runner, :collector
|
||||
|
||||
def initialize(standalone)
|
||||
Unit.run = true
|
||||
@standalone = standalone
|
||||
@runner = RUNNERS[:console]
|
||||
@collector = COLLECTORS[(standalone ? :dir : :objectspace)]
|
||||
@filters = []
|
||||
@to_run = []
|
||||
@output_level = UI::NORMAL
|
||||
@workdir = false
|
||||
yield(self) if(block_given?)
|
||||
end
|
||||
|
||||
def process_args(args = ARGV)
|
||||
begin
|
||||
options.order!(args) {|arg| @to_run << arg}
|
||||
rescue OptionParser::ParseError => e
|
||||
puts e
|
||||
puts options
|
||||
$! = nil
|
||||
abort
|
||||
else
|
||||
@filters << proc{false} unless(@filters.empty?)
|
||||
end
|
||||
not @to_run.empty?
|
||||
end
|
||||
|
||||
def options
|
||||
@options ||= OptionParser.new do |o|
|
||||
o.banner = "Test::Unit automatic runner."
|
||||
o.banner << "\nUsage: #{$0} [options] [-- untouched arguments]"
|
||||
|
||||
o.on
|
||||
o.on('-r', '--runner=RUNNER', RUNNERS,
|
||||
"Use the given RUNNER.",
|
||||
"(" + keyword_display(RUNNERS) + ")") do |r|
|
||||
@runner = r
|
||||
end
|
||||
|
||||
if(@standalone)
|
||||
o.on('-b', '--basedir=DIR', "Base directory of test suites.") do |b|
|
||||
@base = b
|
||||
end
|
||||
|
||||
o.on('-w', '--workdir=DIR', "Working directory to run tests.") do |w|
|
||||
@workdir = w
|
||||
end
|
||||
|
||||
o.on('-a', '--add=TORUN', Array,
|
||||
"Add TORUN to the list of things to run;",
|
||||
"can be a file or a directory.") do |a|
|
||||
@to_run.concat(a)
|
||||
end
|
||||
|
||||
@pattern = []
|
||||
o.on('-p', '--pattern=PATTERN', Regexp,
|
||||
"Match files to collect against PATTERN.") do |e|
|
||||
@pattern << e
|
||||
end
|
||||
|
||||
@exclude = []
|
||||
o.on('-x', '--exclude=PATTERN', Regexp,
|
||||
"Ignore files to collect against PATTERN.") do |e|
|
||||
@exclude << e
|
||||
end
|
||||
end
|
||||
|
||||
o.on('-n', '--name=NAME', String,
|
||||
"Runs tests matching NAME.",
|
||||
"(patterns may be used).") do |n|
|
||||
n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
|
||||
case n
|
||||
when Regexp
|
||||
@filters << proc{|t| n =~ t.method_name ? true : nil}
|
||||
else
|
||||
@filters << proc{|t| n == t.method_name ? true : nil}
|
||||
end
|
||||
end
|
||||
|
||||
o.on('-t', '--testcase=TESTCASE', String,
|
||||
"Runs tests in TestCases matching TESTCASE.",
|
||||
"(patterns may be used).") do |n|
|
||||
n = (%r{\A/(.*)/\Z} =~ n ? Regexp.new($1) : n)
|
||||
case n
|
||||
when Regexp
|
||||
@filters << proc{|t| n =~ t.class.name ? true : nil}
|
||||
else
|
||||
@filters << proc{|t| n == t.class.name ? true : nil}
|
||||
end
|
||||
end
|
||||
|
||||
o.on('-I', "--load-path=DIR[#{File::PATH_SEPARATOR}DIR...]",
|
||||
"Appends directory list to $LOAD_PATH.") do |dirs|
|
||||
$LOAD_PATH.concat(dirs.split(File::PATH_SEPARATOR))
|
||||
end
|
||||
|
||||
o.on('-v', '--verbose=[LEVEL]', OUTPUT_LEVELS,
|
||||
"Set the output level (default is verbose).",
|
||||
"(" + keyword_display(OUTPUT_LEVELS) + ")") do |l|
|
||||
@output_level = l || UI::VERBOSE
|
||||
end
|
||||
|
||||
o.on('--',
|
||||
"Stop processing options so that the",
|
||||
"remaining options will be passed to the",
|
||||
"test."){o.terminate}
|
||||
|
||||
o.on('-h', '--help', 'Display this help.'){puts o; exit}
|
||||
|
||||
o.on_tail
|
||||
o.on_tail('Deprecated options:')
|
||||
|
||||
o.on_tail('--console', 'Console runner (use --runner).') do
|
||||
warn("Deprecated option (--console).")
|
||||
@runner = RUNNERS[:console]
|
||||
end
|
||||
|
||||
o.on_tail('--gtk', 'GTK runner (use --runner).') do
|
||||
warn("Deprecated option (--gtk).")
|
||||
@runner = RUNNERS[:gtk]
|
||||
end
|
||||
|
||||
o.on_tail('--fox', 'Fox runner (use --runner).') do
|
||||
warn("Deprecated option (--fox).")
|
||||
@runner = RUNNERS[:fox]
|
||||
end
|
||||
|
||||
o.on_tail
|
||||
end
|
||||
end
|
||||
|
||||
def keyword_display(array)
|
||||
list = array.collect {|e, *| e.to_s}
|
||||
Array === array or list.sort!
|
||||
list.collect {|e| e.sub(/^(.)([A-Za-z]+)(?=\w*$)/, '\\1[\\2]')}.join(", ")
|
||||
end
|
||||
|
||||
def run
|
||||
@suite = @collector[self]
|
||||
result = @runner[self] or return false
|
||||
Dir.chdir(@workdir) if @workdir
|
||||
result.run(@suite, @output_level).passed?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
43
lib/test/unit/collector.rb
Normal file
43
lib/test/unit/collector.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
module Test
|
||||
module Unit
|
||||
module Collector
|
||||
def initialize
|
||||
@filters = []
|
||||
end
|
||||
|
||||
def filter=(filters)
|
||||
@filters = case(filters)
|
||||
when Proc
|
||||
[filters]
|
||||
when Array
|
||||
filters
|
||||
end
|
||||
end
|
||||
|
||||
def add_suite(destination, suite)
|
||||
to_delete = suite.tests.find_all{|t| !include?(t)}
|
||||
to_delete.each{|t| suite.delete(t)}
|
||||
destination << suite unless(suite.size == 0)
|
||||
end
|
||||
|
||||
def include?(test)
|
||||
return true if(@filters.empty?)
|
||||
@filters.each do |filter|
|
||||
result = filter[test]
|
||||
if(result.nil?)
|
||||
next
|
||||
elsif(!result)
|
||||
return false
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
true
|
||||
end
|
||||
|
||||
def sort(suites)
|
||||
suites.sort_by{|s| s.name}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
113
lib/test/unit/collector/dir.rb
Normal file
113
lib/test/unit/collector/dir.rb
Normal file
|
@ -0,0 +1,113 @@
|
|||
require 'test/unit/testsuite'
|
||||
require 'test/unit/collector'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module Collector
|
||||
class Dir
|
||||
include Collector
|
||||
|
||||
attr_reader :pattern, :exclude
|
||||
attr_accessor :base
|
||||
|
||||
def initialize(dir=::Dir, file=::File, object_space=nil, req=nil)
|
||||
super()
|
||||
@dir = dir
|
||||
@file = file
|
||||
@object_space = object_space
|
||||
@req = req
|
||||
@pattern = [/\btest_.*\.rb\Z/m]
|
||||
@exclude = []
|
||||
end
|
||||
|
||||
def collect(*from)
|
||||
basedir = @base
|
||||
$:.push(basedir) if basedir
|
||||
if(from.empty?)
|
||||
recursive_collect('.', find_test_cases)
|
||||
elsif(from.size == 1)
|
||||
recursive_collect(from.first, find_test_cases)
|
||||
else
|
||||
suites = []
|
||||
from.each do |f|
|
||||
suite = recursive_collect(f, find_test_cases)
|
||||
suites << suite unless(suite.tests.empty?)
|
||||
end
|
||||
suite = TestSuite.new("[#{from.join(', ')}]")
|
||||
sort(suites).each{|s| suite << s}
|
||||
suite
|
||||
end
|
||||
ensure
|
||||
$:.delete_at($:.rindex(basedir)) if basedir
|
||||
end
|
||||
|
||||
def find_test_cases(ignore=[])
|
||||
cases = []
|
||||
if @object_space
|
||||
@object_space.each_object(Class) do |c|
|
||||
cases << c if(c < TestCase && !ignore.include?(c))
|
||||
end
|
||||
else
|
||||
TestCase::DECENDANT_CLASSES.each do |c|
|
||||
cases << c if !ignore.include?(c)
|
||||
end
|
||||
end
|
||||
ignore.concat(cases)
|
||||
cases
|
||||
end
|
||||
|
||||
def recursive_collect(name, already_gathered)
|
||||
sub_suites = []
|
||||
path = realdir(name)
|
||||
if @file.directory?(path)
|
||||
dir_name = name unless name == '.'
|
||||
@dir.entries(path).each do |e|
|
||||
next if(e == '.' || e == '..')
|
||||
e_name = dir_name ? @file.join(dir_name, e) : e
|
||||
if @file.directory?(realdir(e_name))
|
||||
next if /\A(?:CVS|\.svn)\z/ =~ e
|
||||
sub_suite = recursive_collect(e_name, already_gathered)
|
||||
sub_suites << sub_suite unless(sub_suite.empty?)
|
||||
else
|
||||
next if /~\z/ =~ e_name or /\A\.\#/ =~ e
|
||||
if @pattern and !@pattern.empty?
|
||||
next unless @pattern.any? {|pat| pat =~ e_name}
|
||||
end
|
||||
if @exclude and !@exclude.empty?
|
||||
next if @exclude.any? {|pat| pat =~ e_name}
|
||||
end
|
||||
collect_file(e_name, sub_suites, already_gathered)
|
||||
end
|
||||
end
|
||||
else
|
||||
collect_file(name, sub_suites, already_gathered)
|
||||
end
|
||||
suite = TestSuite.new(@file.basename(name))
|
||||
sort(sub_suites).each{|s| suite << s}
|
||||
suite
|
||||
end
|
||||
|
||||
def collect_file(name, suites, already_gathered)
|
||||
dir = @file.dirname(@file.expand_path(name, @base))
|
||||
$:.unshift(dir)
|
||||
if(@req)
|
||||
@req.require(name)
|
||||
else
|
||||
require(name)
|
||||
end
|
||||
find_test_cases(already_gathered).each{|t| add_suite(suites, t.suite)}
|
||||
ensure
|
||||
$:.delete_at($:.index(dir)) if dir
|
||||
end
|
||||
|
||||
def realdir(path)
|
||||
if @base
|
||||
@file.join(@base, path)
|
||||
else
|
||||
path
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
40
lib/test/unit/collector/objectspace.rb
Normal file
40
lib/test/unit/collector/objectspace.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'test/unit/collector'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module Collector
|
||||
class ObjectSpace
|
||||
include Test::Unit::Collector
|
||||
|
||||
NAME = 'collected from the subclasses of TestCase'
|
||||
|
||||
def initialize(source=nil)
|
||||
super()
|
||||
@source = source
|
||||
end
|
||||
|
||||
def collect(name=NAME)
|
||||
suite = TestSuite.new(name)
|
||||
sub_suites = []
|
||||
if @source
|
||||
@source.each_object(Class) do |klass|
|
||||
if(Test::Unit::TestCase > klass)
|
||||
add_suite(sub_suites, klass.suite)
|
||||
end
|
||||
end
|
||||
else
|
||||
TestCase::DECENDANT_CLASSES.each do |klass|
|
||||
add_suite(sub_suites, klass.suite)
|
||||
end
|
||||
end
|
||||
sort(sub_suites).each{|s| suite << s}
|
||||
suite
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
56
lib/test/unit/error.rb
Normal file
56
lib/test/unit/error.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'test/unit/util/backtracefilter'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
|
||||
# Encapsulates an error in a test. Created by
|
||||
# Test::Unit::TestCase when it rescues an exception thrown
|
||||
# during the processing of a test.
|
||||
class Error
|
||||
include Util::BacktraceFilter
|
||||
|
||||
attr_reader(:test_name, :exception)
|
||||
|
||||
SINGLE_CHARACTER = 'E'
|
||||
|
||||
# Creates a new Error with the given test_name and
|
||||
# exception.
|
||||
def initialize(test_name, exception)
|
||||
@test_name = test_name
|
||||
@exception = exception
|
||||
end
|
||||
|
||||
# Returns a single character representation of an error.
|
||||
def single_character_display
|
||||
SINGLE_CHARACTER
|
||||
end
|
||||
|
||||
# Returns the message associated with the error.
|
||||
def message
|
||||
"#{@exception.class.name}: #{@exception.message}"
|
||||
end
|
||||
|
||||
# Returns a brief version of the error description.
|
||||
def short_display
|
||||
"#@test_name: #{message.split("\n")[0]}"
|
||||
end
|
||||
|
||||
# Returns a verbose version of the error description.
|
||||
def long_display
|
||||
backtrace = filter_backtrace(@exception.backtrace).join("\n ")
|
||||
"Error:\n#@test_name:\n#{message}\n #{backtrace}"
|
||||
end
|
||||
|
||||
# Overridden to return long_display.
|
||||
def to_s
|
||||
long_display
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
51
lib/test/unit/failure.rb
Normal file
51
lib/test/unit/failure.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
|
||||
# Encapsulates a test failure. Created by Test::Unit::TestCase
|
||||
# when an assertion fails.
|
||||
class Failure
|
||||
attr_reader :test_name, :location, :message
|
||||
|
||||
SINGLE_CHARACTER = 'F'
|
||||
|
||||
# Creates a new Failure with the given location and
|
||||
# message.
|
||||
def initialize(test_name, location, message)
|
||||
@test_name = test_name
|
||||
@location = location
|
||||
@message = message
|
||||
end
|
||||
|
||||
# Returns a single character representation of a failure.
|
||||
def single_character_display
|
||||
SINGLE_CHARACTER
|
||||
end
|
||||
|
||||
# Returns a brief version of the error description.
|
||||
def short_display
|
||||
"#@test_name: #{@message.split("\n")[0]}"
|
||||
end
|
||||
|
||||
# Returns a verbose version of the error description.
|
||||
def long_display
|
||||
location_display = if(location.size == 1)
|
||||
location[0].sub(/\A(.+:\d+).*/, ' [\\1]')
|
||||
else
|
||||
"\n [#{location.join("\n ")}]"
|
||||
end
|
||||
"Failure:\n#@test_name#{location_display}:\n#@message"
|
||||
end
|
||||
|
||||
# Overridden to return long_display.
|
||||
def to_s
|
||||
long_display
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
163
lib/test/unit/testcase.rb
Normal file
163
lib/test/unit/testcase.rb
Normal file
|
@ -0,0 +1,163 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'test/unit/assertions'
|
||||
require 'test/unit/failure'
|
||||
require 'test/unit/error'
|
||||
require 'test/unit/testsuite'
|
||||
require 'test/unit/assertionfailederror'
|
||||
require 'test/unit/util/backtracefilter'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
|
||||
# Ties everything together. If you subclass and add your own
|
||||
# test methods, it takes care of making them into tests and
|
||||
# wrapping those tests into a suite. It also does the
|
||||
# nitty-gritty of actually running an individual test and
|
||||
# collecting its results into a Test::Unit::TestResult object.
|
||||
class TestCase
|
||||
include Assertions
|
||||
include Util::BacktraceFilter
|
||||
|
||||
attr_reader :method_name
|
||||
|
||||
STARTED = name + "::STARTED"
|
||||
FINISHED = name + "::FINISHED"
|
||||
|
||||
##
|
||||
# These exceptions are not caught by #run.
|
||||
|
||||
PASSTHROUGH_EXCEPTIONS = [NoMemoryError, SignalException, Interrupt,
|
||||
SystemExit]
|
||||
|
||||
DECENDANT_CLASSES = []
|
||||
def self.inherited(decendant)
|
||||
DECENDANT_CLASSES << decendant
|
||||
end
|
||||
|
||||
# Creates a new instance of the fixture for running the
|
||||
# test represented by test_method_name.
|
||||
def initialize(test_method_name)
|
||||
unless(respond_to?(test_method_name) && method(test_method_name).arity == 0)
|
||||
throw :invalid_test
|
||||
end
|
||||
@method_name = test_method_name
|
||||
@test_passed = true
|
||||
end
|
||||
|
||||
# Rolls up all of the test* methods in the fixture into
|
||||
# one suite, creating a new instance of the fixture for
|
||||
# each method.
|
||||
def self.suite
|
||||
method_names = public_instance_methods(true).map { |m| m.to_s }
|
||||
tests = method_names.delete_if {|method_name| method_name !~ /^test./}
|
||||
suite = TestSuite.new(name)
|
||||
tests.sort.each do
|
||||
|test|
|
||||
catch(:invalid_test) do
|
||||
suite << new(test)
|
||||
end
|
||||
end
|
||||
if (suite.empty?)
|
||||
catch(:invalid_test) do
|
||||
suite << new(:default_test)
|
||||
end
|
||||
end
|
||||
return suite
|
||||
end
|
||||
|
||||
# Runs the individual test method represented by this
|
||||
# instance of the fixture, collecting statistics, failures
|
||||
# and errors in result.
|
||||
def run(result)
|
||||
yield(STARTED, name)
|
||||
@_result = result
|
||||
begin
|
||||
setup
|
||||
__send__(@method_name)
|
||||
rescue AssertionFailedError => e
|
||||
add_failure(e.message, e.backtrace)
|
||||
rescue Exception
|
||||
raise if PASSTHROUGH_EXCEPTIONS.include? $!.class
|
||||
add_error($!)
|
||||
ensure
|
||||
begin
|
||||
teardown
|
||||
rescue AssertionFailedError => e
|
||||
add_failure(e.message, e.backtrace)
|
||||
rescue Exception
|
||||
raise if PASSTHROUGH_EXCEPTIONS.include? $!.class
|
||||
add_error($!)
|
||||
end
|
||||
end
|
||||
result.add_run
|
||||
yield(FINISHED, name)
|
||||
end
|
||||
|
||||
# Called before every test method runs. Can be used
|
||||
# to set up fixture information.
|
||||
def setup
|
||||
end
|
||||
|
||||
# Called after every test method runs. Can be used to tear
|
||||
# down fixture information.
|
||||
def teardown
|
||||
end
|
||||
|
||||
def default_test
|
||||
flunk("No tests were specified")
|
||||
end
|
||||
|
||||
# Returns whether this individual test passed or
|
||||
# not. Primarily for use in teardown so that artifacts
|
||||
# can be left behind if the test fails.
|
||||
def passed?
|
||||
return @test_passed
|
||||
end
|
||||
private :passed?
|
||||
|
||||
def size # :nodoc:
|
||||
1
|
||||
end
|
||||
|
||||
def add_assertion # :nodoc:
|
||||
@_result.add_assertion
|
||||
end
|
||||
private :add_assertion
|
||||
|
||||
def add_failure(message, all_locations=caller()) # :nodoc:
|
||||
@test_passed = false
|
||||
@_result.add_failure(Failure.new(name, filter_backtrace(all_locations), message))
|
||||
end
|
||||
private :add_failure
|
||||
|
||||
def add_error(exception) # :nodoc:
|
||||
@test_passed = false
|
||||
@_result.add_error(Error.new(name, exception))
|
||||
end
|
||||
private :add_error
|
||||
|
||||
# Returns a human-readable name for the specific test that
|
||||
# this instance of TestCase represents.
|
||||
def name
|
||||
"#{@method_name}(#{self.class.name})"
|
||||
end
|
||||
|
||||
# Overridden to return #name.
|
||||
def to_s
|
||||
name
|
||||
end
|
||||
|
||||
# It's handy to be able to compare TestCase instances.
|
||||
def ==(other)
|
||||
return false unless(other.kind_of?(self.class))
|
||||
return false unless(@method_name == other.method_name)
|
||||
self.class == other.class
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
81
lib/test/unit/testresult.rb
Normal file
81
lib/test/unit/testresult.rb
Normal file
|
@ -0,0 +1,81 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'test/unit/util/observable'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
|
||||
# Collects Test::Unit::Failure and Test::Unit::Error so that
|
||||
# they can be displayed to the user. To this end, observers
|
||||
# can be added to it, allowing the dynamic updating of, say, a
|
||||
# UI.
|
||||
class TestResult
|
||||
include Util::Observable
|
||||
|
||||
CHANGED = "CHANGED"
|
||||
FAULT = "FAULT"
|
||||
|
||||
attr_reader(:run_count, :assertion_count)
|
||||
|
||||
# Constructs a new, empty TestResult.
|
||||
def initialize
|
||||
@run_count, @assertion_count = 0, 0
|
||||
@failures, @errors = Array.new, Array.new
|
||||
end
|
||||
|
||||
# Records a test run.
|
||||
def add_run
|
||||
@run_count += 1
|
||||
notify_listeners(CHANGED, self)
|
||||
end
|
||||
|
||||
# Records a Test::Unit::Failure.
|
||||
def add_failure(failure)
|
||||
@failures << failure
|
||||
notify_listeners(FAULT, failure)
|
||||
notify_listeners(CHANGED, self)
|
||||
end
|
||||
|
||||
# Records a Test::Unit::Error.
|
||||
def add_error(error)
|
||||
@errors << error
|
||||
notify_listeners(FAULT, error)
|
||||
notify_listeners(CHANGED, self)
|
||||
end
|
||||
|
||||
# Records an individual assertion.
|
||||
def add_assertion
|
||||
@assertion_count += 1
|
||||
notify_listeners(CHANGED, self)
|
||||
end
|
||||
|
||||
# Returns a string contain the recorded runs, assertions,
|
||||
# failures and errors in this TestResult.
|
||||
def to_s
|
||||
"#{run_count} tests, #{assertion_count} assertions, #{failure_count} failures, #{error_count} errors"
|
||||
end
|
||||
|
||||
# Returns whether or not this TestResult represents
|
||||
# successful completion.
|
||||
def passed?
|
||||
return @failures.empty? && @errors.empty?
|
||||
end
|
||||
|
||||
# Returns the number of failures this TestResult has
|
||||
# recorded.
|
||||
def failure_count
|
||||
return @failures.size
|
||||
end
|
||||
|
||||
# Returns the number of errors this TestResult has
|
||||
# recorded.
|
||||
def error_count
|
||||
return @errors.size
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
76
lib/test/unit/testsuite.rb
Normal file
76
lib/test/unit/testsuite.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
|
||||
# A collection of tests which can be #run.
|
||||
#
|
||||
# Note: It is easy to confuse a TestSuite instance with
|
||||
# something that has a static suite method; I know because _I_
|
||||
# have trouble keeping them straight. Think of something that
|
||||
# has a suite method as simply providing a way to get a
|
||||
# meaningful TestSuite instance.
|
||||
class TestSuite
|
||||
attr_reader :name, :tests
|
||||
|
||||
STARTED = name + "::STARTED"
|
||||
FINISHED = name + "::FINISHED"
|
||||
|
||||
# Creates a new TestSuite with the given name.
|
||||
def initialize(name="Unnamed TestSuite")
|
||||
@name = name
|
||||
@tests = []
|
||||
end
|
||||
|
||||
# Runs the tests and/or suites contained in this
|
||||
# TestSuite.
|
||||
def run(result, &progress_block)
|
||||
yield(STARTED, name)
|
||||
@tests.each do |test|
|
||||
test.run(result, &progress_block)
|
||||
end
|
||||
yield(FINISHED, name)
|
||||
end
|
||||
|
||||
# Adds the test to the suite.
|
||||
def <<(test)
|
||||
@tests << test
|
||||
self
|
||||
end
|
||||
|
||||
def delete(test)
|
||||
@tests.delete(test)
|
||||
end
|
||||
|
||||
# Retuns the rolled up number of tests in this suite;
|
||||
# i.e. if the suite contains other suites, it counts the
|
||||
# tests within those suites, not the suites themselves.
|
||||
def size
|
||||
total_size = 0
|
||||
@tests.each { |test| total_size += test.size }
|
||||
total_size
|
||||
end
|
||||
|
||||
def empty?
|
||||
tests.empty?
|
||||
end
|
||||
|
||||
# Overridden to return the name given the suite at
|
||||
# creation.
|
||||
def to_s
|
||||
@name
|
||||
end
|
||||
|
||||
# It's handy to be able to compare TestSuite instances.
|
||||
def ==(other)
|
||||
return false unless(other.kind_of?(self.class))
|
||||
return false unless(@name == other.name)
|
||||
@tests == other.tests
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
130
lib/test/unit/ui/console/testrunner.rb
Normal file
130
lib/test/unit/ui/console/testrunner.rb
Normal file
|
@ -0,0 +1,130 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'test/unit/ui/testrunnermediator'
|
||||
require 'test/unit/ui/testrunnerutilities'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module UI
|
||||
module Console # :nodoc:
|
||||
|
||||
# Runs a Test::Unit::TestSuite on the console.
|
||||
class TestRunner
|
||||
extend TestRunnerUtilities
|
||||
|
||||
# Creates a new TestRunner for running the passed
|
||||
# suite. If quiet_mode is true, the output while
|
||||
# running is limited to progress dots, errors and
|
||||
# failures, and the final result. io specifies
|
||||
# where runner output should go to; defaults to
|
||||
# STDOUT.
|
||||
def initialize(suite, output_level=NORMAL, io=STDOUT)
|
||||
if (suite.respond_to?(:suite))
|
||||
@suite = suite.suite
|
||||
else
|
||||
@suite = suite
|
||||
end
|
||||
@output_level = output_level
|
||||
@io = io
|
||||
@already_outputted = false
|
||||
@faults = []
|
||||
end
|
||||
|
||||
# Begins the test run.
|
||||
def start
|
||||
setup_mediator
|
||||
attach_to_mediator
|
||||
return start_mediator
|
||||
end
|
||||
|
||||
private
|
||||
def setup_mediator # :nodoc:
|
||||
@mediator = create_mediator(@suite)
|
||||
suite_name = @suite.to_s
|
||||
if ( @suite.kind_of?(Module) )
|
||||
suite_name = @suite.name
|
||||
end
|
||||
output("Loaded suite #{suite_name}")
|
||||
end
|
||||
|
||||
def create_mediator(suite) # :nodoc:
|
||||
return TestRunnerMediator.new(suite)
|
||||
end
|
||||
|
||||
def attach_to_mediator # :nodoc:
|
||||
@mediator.add_listener(TestResult::FAULT, &method(:add_fault))
|
||||
@mediator.add_listener(TestRunnerMediator::STARTED, &method(:started))
|
||||
@mediator.add_listener(TestRunnerMediator::FINISHED, &method(:finished))
|
||||
@mediator.add_listener(TestCase::STARTED, &method(:test_started))
|
||||
@mediator.add_listener(TestCase::FINISHED, &method(:test_finished))
|
||||
end
|
||||
|
||||
def start_mediator # :nodoc:
|
||||
return @mediator.run_suite
|
||||
end
|
||||
|
||||
def add_fault(fault) # :nodoc:
|
||||
@faults << fault
|
||||
output_single(fault.single_character_display, PROGRESS_ONLY)
|
||||
@already_outputted = true
|
||||
end
|
||||
|
||||
def started(result)
|
||||
@result = result
|
||||
output("Started")
|
||||
end
|
||||
|
||||
def finished(elapsed_time)
|
||||
nl
|
||||
output("Finished in #{elapsed_time} seconds.")
|
||||
@faults.each_with_index do |fault, index|
|
||||
nl
|
||||
output("%3d) %s" % [index + 1, fault.long_display])
|
||||
end
|
||||
nl
|
||||
output(@result)
|
||||
end
|
||||
|
||||
def test_started(name)
|
||||
$program_name = $0
|
||||
alias $0 $program_name
|
||||
$PROGRAM_NAME += "\0#{name}"
|
||||
output_single(name + ": ", VERBOSE)
|
||||
end
|
||||
|
||||
def test_finished(name)
|
||||
output_single(".", PROGRESS_ONLY) unless (@already_outputted)
|
||||
nl(VERBOSE)
|
||||
@already_outputted = false
|
||||
end
|
||||
|
||||
def nl(level=NORMAL)
|
||||
output("", level)
|
||||
end
|
||||
|
||||
def output(something, level=NORMAL)
|
||||
@io.puts(something) if (output?(level))
|
||||
@io.flush
|
||||
end
|
||||
|
||||
def output_single(something, level=NORMAL)
|
||||
@io.write(something) if (output?(level))
|
||||
@io.flush
|
||||
end
|
||||
|
||||
def output?(level)
|
||||
level <= @output_level
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
Test::Unit::UI::Console::TestRunner.start_command_line_test
|
||||
end
|
268
lib/test/unit/ui/fox/testrunner.rb
Normal file
268
lib/test/unit/ui/fox/testrunner.rb
Normal file
|
@ -0,0 +1,268 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'fox'
|
||||
require 'test/unit/ui/testrunnermediator'
|
||||
require 'test/unit/ui/testrunnerutilities'
|
||||
|
||||
include Fox
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module UI
|
||||
module Fox # :nodoc:
|
||||
|
||||
# Runs a Test::Unit::TestSuite in a Fox UI. Obviously,
|
||||
# this one requires you to have Fox
|
||||
# (http://www.fox-toolkit.org/fox.html) and the Ruby
|
||||
# Fox extension (http://fxruby.sourceforge.net/)
|
||||
# installed.
|
||||
class TestRunner
|
||||
|
||||
extend TestRunnerUtilities
|
||||
|
||||
RED_STYLE = FXRGBA(0xFF,0,0,0xFF) #0xFF000000
|
||||
GREEN_STYLE = FXRGBA(0,0xFF,0,0xFF) #0x00FF0000
|
||||
|
||||
# Creates a new TestRunner for running the passed
|
||||
# suite.
|
||||
def initialize(suite, output_level = NORMAL)
|
||||
if (suite.respond_to?(:suite))
|
||||
@suite = suite.suite
|
||||
else
|
||||
@suite = suite
|
||||
end
|
||||
|
||||
@result = nil
|
||||
@red = false
|
||||
end
|
||||
|
||||
# Begins the test run.
|
||||
def start
|
||||
setup_ui
|
||||
setup_mediator
|
||||
attach_to_mediator
|
||||
start_ui
|
||||
@result
|
||||
end
|
||||
|
||||
def setup_mediator # :nodoc:
|
||||
@mediator = TestRunnerMediator.new(@suite)
|
||||
suite_name = @suite.to_s
|
||||
if ( @suite.kind_of?(Module) )
|
||||
suite_name = @suite.name
|
||||
end
|
||||
@suite_name_entry.text = suite_name
|
||||
end
|
||||
|
||||
def attach_to_mediator # :nodoc:
|
||||
@mediator.add_listener(TestRunnerMediator::RESET, &method(:reset_ui))
|
||||
@mediator.add_listener(TestResult::FAULT, &method(:add_fault))
|
||||
@mediator.add_listener(TestResult::CHANGED, &method(:result_changed))
|
||||
@mediator.add_listener(TestRunnerMediator::STARTED, &method(:started))
|
||||
@mediator.add_listener(TestCase::STARTED, &method(:test_started))
|
||||
@mediator.add_listener(TestRunnerMediator::FINISHED, &method(:finished))
|
||||
end
|
||||
|
||||
def start_ui # :nodoc:
|
||||
@application.create
|
||||
@window.show(PLACEMENT_SCREEN)
|
||||
@application.addTimeout(1) do
|
||||
@mediator.run_suite
|
||||
end
|
||||
@application.run
|
||||
end
|
||||
|
||||
def stop # :nodoc:
|
||||
@application.exit(0)
|
||||
end
|
||||
|
||||
def reset_ui(count) # :nodoc:
|
||||
@test_progress_bar.barColor = GREEN_STYLE
|
||||
@test_progress_bar.total = count
|
||||
@test_progress_bar.progress = 0
|
||||
@red = false
|
||||
|
||||
@test_count_label.text = "0"
|
||||
@assertion_count_label.text = "0"
|
||||
@failure_count_label.text = "0"
|
||||
@error_count_label.text = "0"
|
||||
|
||||
@fault_list.clearItems
|
||||
end
|
||||
|
||||
def add_fault(fault) # :nodoc:
|
||||
if ( ! @red )
|
||||
@test_progress_bar.barColor = RED_STYLE
|
||||
@red = true
|
||||
end
|
||||
item = FaultListItem.new(fault)
|
||||
@fault_list.appendItem(item)
|
||||
end
|
||||
|
||||
def show_fault(fault) # :nodoc:
|
||||
raw_show_fault(fault.long_display)
|
||||
end
|
||||
|
||||
def raw_show_fault(string) # :nodoc:
|
||||
@detail_text.setText(string)
|
||||
end
|
||||
|
||||
def clear_fault # :nodoc:
|
||||
raw_show_fault("")
|
||||
end
|
||||
|
||||
def result_changed(result) # :nodoc:
|
||||
@test_progress_bar.progress = result.run_count
|
||||
|
||||
@test_count_label.text = result.run_count.to_s
|
||||
@assertion_count_label.text = result.assertion_count.to_s
|
||||
@failure_count_label.text = result.failure_count.to_s
|
||||
@error_count_label.text = result.error_count.to_s
|
||||
|
||||
# repaint now!
|
||||
@info_panel.repaint
|
||||
@application.flush
|
||||
end
|
||||
|
||||
def started(result) # :nodoc:
|
||||
@result = result
|
||||
output_status("Started...")
|
||||
end
|
||||
|
||||
def test_started(test_name)
|
||||
output_status("Running #{test_name}...")
|
||||
end
|
||||
|
||||
def finished(elapsed_time)
|
||||
output_status("Finished in #{elapsed_time} seconds")
|
||||
end
|
||||
|
||||
def output_status(string)
|
||||
@status_entry.text = string
|
||||
@status_entry.repaint
|
||||
end
|
||||
|
||||
def setup_ui # :nodoc:
|
||||
@application = create_application
|
||||
create_tooltip(@application)
|
||||
|
||||
@window = create_window(@application)
|
||||
|
||||
@status_entry = create_entry(@window)
|
||||
|
||||
main_panel = create_main_panel(@window)
|
||||
|
||||
suite_panel = create_suite_panel(main_panel)
|
||||
create_label(suite_panel, "Suite:")
|
||||
@suite_name_entry = create_entry(suite_panel)
|
||||
create_button(suite_panel, "&Run\tRun the current suite", proc { @mediator.run_suite })
|
||||
|
||||
@test_progress_bar = create_progress_bar(main_panel)
|
||||
|
||||
@info_panel = create_info_panel(main_panel)
|
||||
create_label(@info_panel, "Tests:")
|
||||
@test_count_label = create_label(@info_panel, "0")
|
||||
create_label(@info_panel, "Assertions:")
|
||||
@assertion_count_label = create_label(@info_panel, "0")
|
||||
create_label(@info_panel, "Failures:")
|
||||
@failure_count_label = create_label(@info_panel, "0")
|
||||
create_label(@info_panel, "Errors:")
|
||||
@error_count_label = create_label(@info_panel, "0")
|
||||
|
||||
list_panel = create_list_panel(main_panel)
|
||||
@fault_list = create_fault_list(list_panel)
|
||||
|
||||
detail_panel = create_detail_panel(main_panel)
|
||||
@detail_text = create_text(detail_panel)
|
||||
end
|
||||
|
||||
def create_application # :nodoc:
|
||||
app = FXApp.new("TestRunner", "Test::Unit")
|
||||
app.init([])
|
||||
app
|
||||
end
|
||||
|
||||
def create_window(app)
|
||||
FXMainWindow.new(app, "Test::Unit TestRunner", nil, nil, DECOR_ALL, 0, 0, 450)
|
||||
end
|
||||
|
||||
def create_tooltip(app)
|
||||
FXTooltip.new(app)
|
||||
end
|
||||
|
||||
def create_main_panel(parent) # :nodoc:
|
||||
panel = FXVerticalFrame.new(parent, LAYOUT_FILL_X | LAYOUT_FILL_Y)
|
||||
panel.vSpacing = 10
|
||||
panel
|
||||
end
|
||||
|
||||
def create_suite_panel(parent) # :nodoc:
|
||||
FXHorizontalFrame.new(parent, LAYOUT_SIDE_LEFT | LAYOUT_FILL_X)
|
||||
end
|
||||
|
||||
def create_button(parent, text, action) # :nodoc:
|
||||
FXButton.new(parent, text).connect(SEL_COMMAND, &action)
|
||||
end
|
||||
|
||||
def create_progress_bar(parent) # :nodoc:
|
||||
FXProgressBar.new(parent, nil, 0, PROGRESSBAR_NORMAL | LAYOUT_FILL_X)
|
||||
end
|
||||
|
||||
def create_info_panel(parent) # :nodoc:
|
||||
FXMatrix.new(parent, 1, MATRIX_BY_ROWS | LAYOUT_FILL_X)
|
||||
end
|
||||
|
||||
def create_label(parent, text)
|
||||
FXLabel.new(parent, text, nil, JUSTIFY_CENTER_X | LAYOUT_FILL_COLUMN)
|
||||
end
|
||||
|
||||
def create_list_panel(parent) # :nodoc:
|
||||
FXHorizontalFrame.new(parent, LAYOUT_FILL_X | FRAME_SUNKEN | FRAME_THICK)
|
||||
end
|
||||
|
||||
def create_fault_list(parent) # :nodoc:
|
||||
list = FXList.new(parent, 10, nil, 0, LIST_SINGLESELECT | LAYOUT_FILL_X) #, 0, 0, 0, 150)
|
||||
list.connect(SEL_COMMAND) do |sender, sel, ptr|
|
||||
if sender.retrieveItem(sender.currentItem).selected?
|
||||
show_fault(sender.retrieveItem(sender.currentItem).fault)
|
||||
else
|
||||
clear_fault
|
||||
end
|
||||
end
|
||||
list
|
||||
end
|
||||
|
||||
def create_detail_panel(parent) # :nodoc:
|
||||
FXHorizontalFrame.new(parent, LAYOUT_FILL_X | LAYOUT_FILL_Y | FRAME_SUNKEN | FRAME_THICK)
|
||||
end
|
||||
|
||||
def create_text(parent) # :nodoc:
|
||||
FXText.new(parent, nil, 0, TEXT_READONLY | LAYOUT_FILL_X | LAYOUT_FILL_Y)
|
||||
end
|
||||
|
||||
def create_entry(parent) # :nodoc:
|
||||
entry = FXTextField.new(parent, 30, nil, 0, TEXTFIELD_NORMAL | LAYOUT_SIDE_BOTTOM | LAYOUT_FILL_X)
|
||||
entry.disable
|
||||
entry
|
||||
end
|
||||
end
|
||||
|
||||
class FaultListItem < FXListItem # :nodoc: all
|
||||
attr_reader(:fault)
|
||||
def initialize(fault)
|
||||
super(fault.short_display)
|
||||
@fault = fault
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
Test::Unit::UI::Fox::TestRunner.start_command_line_test
|
||||
end
|
416
lib/test/unit/ui/gtk/testrunner.rb
Normal file
416
lib/test/unit/ui/gtk/testrunner.rb
Normal file
|
@ -0,0 +1,416 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'gtk'
|
||||
require 'test/unit/ui/testrunnermediator'
|
||||
require 'test/unit/ui/testrunnerutilities'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module UI
|
||||
module GTK # :nodoc:
|
||||
|
||||
# Runs a Test::Unit::TestSuite in a Gtk UI. Obviously,
|
||||
# this one requires you to have Gtk
|
||||
# (http://www.gtk.org/) and the Ruby Gtk extension
|
||||
# (http://ruby-gnome.sourceforge.net/) installed.
|
||||
class TestRunner
|
||||
extend TestRunnerUtilities
|
||||
|
||||
# Creates a new TestRunner for running the passed
|
||||
# suite.
|
||||
def initialize(suite, output_level = NORMAL)
|
||||
if (suite.respond_to?(:suite))
|
||||
@suite = suite.suite
|
||||
else
|
||||
@suite = suite
|
||||
end
|
||||
@result = nil
|
||||
|
||||
@runner = Thread.current
|
||||
@restart_signal = Class.new(Exception)
|
||||
@viewer = Thread.start do
|
||||
@runner.join rescue @runner.run
|
||||
Gtk.main
|
||||
end
|
||||
@viewer.join rescue nil # wait deadlock to handshake
|
||||
end
|
||||
|
||||
# Begins the test run.
|
||||
def start
|
||||
setup_mediator
|
||||
setup_ui
|
||||
attach_to_mediator
|
||||
start_ui
|
||||
@result
|
||||
end
|
||||
|
||||
private
|
||||
def setup_mediator # :nodoc:
|
||||
@mediator = TestRunnerMediator.new(@suite)
|
||||
suite_name = @suite.to_s
|
||||
if ( @suite.kind_of?(Module) )
|
||||
suite_name = @suite.name
|
||||
end
|
||||
suite_name_entry.set_text(suite_name)
|
||||
end
|
||||
|
||||
def attach_to_mediator # :nodoc:
|
||||
run_button.signal_connect("clicked", nil, &method(:run_test))
|
||||
@mediator.add_listener(TestRunnerMediator::RESET, &method(:reset_ui))
|
||||
@mediator.add_listener(TestResult::FAULT, &method(:add_fault))
|
||||
@mediator.add_listener(TestResult::CHANGED, &method(:result_changed))
|
||||
@mediator.add_listener(TestRunnerMediator::STARTED, &method(:started))
|
||||
@mediator.add_listener(TestCase::STARTED, &method(:test_started))
|
||||
@mediator.add_listener(TestCase::FINISHED, &method(:test_finished))
|
||||
@mediator.add_listener(TestRunnerMediator::FINISHED, &method(:finished))
|
||||
end
|
||||
|
||||
def run_test(*)
|
||||
@runner.raise(@restart_signal)
|
||||
end
|
||||
|
||||
def start_ui # :nodoc:
|
||||
@viewer.run
|
||||
running = false
|
||||
begin
|
||||
loop do
|
||||
if (running ^= true)
|
||||
run_button.child.text = "Stop"
|
||||
@mediator.run_suite
|
||||
else
|
||||
run_button.child.text = "Run"
|
||||
@viewer.join
|
||||
break
|
||||
end
|
||||
end
|
||||
rescue @restart_signal
|
||||
retry
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
def stop(*) # :nodoc:
|
||||
Gtk.main_quit
|
||||
end
|
||||
|
||||
def reset_ui(count) # :nodoc:
|
||||
test_progress_bar.set_style(green_style)
|
||||
test_progress_bar.configure(0, 0, count)
|
||||
@red = false
|
||||
|
||||
run_count_label.set_text("0")
|
||||
assertion_count_label.set_text("0")
|
||||
failure_count_label.set_text("0")
|
||||
error_count_label.set_text("0")
|
||||
|
||||
fault_list.remove_items(fault_list.children)
|
||||
end
|
||||
|
||||
def add_fault(fault) # :nodoc:
|
||||
if ( ! @red )
|
||||
test_progress_bar.set_style(red_style)
|
||||
@red = true
|
||||
end
|
||||
item = FaultListItem.new(fault)
|
||||
item.show
|
||||
fault_list.append_items([item])
|
||||
end
|
||||
|
||||
def show_fault(fault) # :nodoc:
|
||||
raw_show_fault(fault.long_display)
|
||||
end
|
||||
|
||||
def raw_show_fault(string) # :nodoc:
|
||||
fault_detail_label.set_text(string)
|
||||
outer_detail_sub_panel.queue_resize
|
||||
end
|
||||
|
||||
def clear_fault # :nodoc:
|
||||
raw_show_fault("")
|
||||
end
|
||||
|
||||
def result_changed(result) # :nodoc:
|
||||
run_count_label.set_text(result.run_count.to_s)
|
||||
assertion_count_label.set_text(result.assertion_count.to_s)
|
||||
failure_count_label.set_text(result.failure_count.to_s)
|
||||
error_count_label.set_text(result.error_count.to_s)
|
||||
end
|
||||
|
||||
def started(result) # :nodoc:
|
||||
@result = result
|
||||
output_status("Started...")
|
||||
end
|
||||
|
||||
def test_started(test_name)
|
||||
output_status("Running #{test_name}...")
|
||||
end
|
||||
|
||||
def test_finished(test_name)
|
||||
test_progress_bar.set_value(test_progress_bar.get_value + 1)
|
||||
end
|
||||
|
||||
def finished(elapsed_time)
|
||||
output_status("Finished in #{elapsed_time} seconds")
|
||||
end
|
||||
|
||||
def output_status(string) # :nodoc:
|
||||
status_entry.set_text(string)
|
||||
end
|
||||
|
||||
def setup_ui # :nodoc:
|
||||
main_window.signal_connect("destroy", nil, &method(:stop))
|
||||
main_window.show_all
|
||||
fault_list.signal_connect("select-child", nil) {
|
||||
| list, item, data |
|
||||
show_fault(item.fault)
|
||||
}
|
||||
fault_list.signal_connect("unselect-child", nil) {
|
||||
clear_fault
|
||||
}
|
||||
@red = false
|
||||
end
|
||||
|
||||
def main_window # :nodoc:
|
||||
lazy_initialize(:main_window) {
|
||||
@main_window = Gtk::Window.new(Gtk::WINDOW_TOPLEVEL)
|
||||
@main_window.set_title("Test::Unit TestRunner")
|
||||
@main_window.set_usize(800, 600)
|
||||
@main_window.set_uposition(20, 20)
|
||||
@main_window.set_policy(true, true, false)
|
||||
@main_window.add(main_panel)
|
||||
}
|
||||
end
|
||||
|
||||
def main_panel # :nodoc:
|
||||
lazy_initialize(:main_panel) {
|
||||
@main_panel = Gtk::VBox.new(false, 0)
|
||||
@main_panel.pack_start(suite_panel, false, false, 0)
|
||||
@main_panel.pack_start(progress_panel, false, false, 0)
|
||||
@main_panel.pack_start(info_panel, false, false, 0)
|
||||
@main_panel.pack_start(list_panel, false, false, 0)
|
||||
@main_panel.pack_start(detail_panel, true, true, 0)
|
||||
@main_panel.pack_start(status_panel, false, false, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def suite_panel # :nodoc:
|
||||
lazy_initialize(:suite_panel) {
|
||||
@suite_panel = Gtk::HBox.new(false, 10)
|
||||
@suite_panel.border_width(10)
|
||||
@suite_panel.pack_start(Gtk::Label.new("Suite:"), false, false, 0)
|
||||
@suite_panel.pack_start(suite_name_entry, true, true, 0)
|
||||
@suite_panel.pack_start(run_button, false, false, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def suite_name_entry # :nodoc:
|
||||
lazy_initialize(:suite_name_entry) {
|
||||
@suite_name_entry = Gtk::Entry.new
|
||||
@suite_name_entry.set_editable(false)
|
||||
}
|
||||
end
|
||||
|
||||
def run_button # :nodoc:
|
||||
lazy_initialize(:run_button) {
|
||||
@run_button = Gtk::Button.new("Run")
|
||||
}
|
||||
end
|
||||
|
||||
def progress_panel # :nodoc:
|
||||
lazy_initialize(:progress_panel) {
|
||||
@progress_panel = Gtk::HBox.new(false, 10)
|
||||
@progress_panel.border_width(10)
|
||||
@progress_panel.pack_start(test_progress_bar, true, true, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def test_progress_bar # :nodoc:
|
||||
lazy_initialize(:test_progress_bar) {
|
||||
@test_progress_bar = EnhancedProgressBar.new
|
||||
@test_progress_bar.set_usize(@test_progress_bar.allocation.width,
|
||||
info_panel.size_request.height)
|
||||
@test_progress_bar.set_style(green_style)
|
||||
}
|
||||
end
|
||||
|
||||
def green_style # :nodoc:
|
||||
lazy_initialize(:green_style) {
|
||||
@green_style = Gtk::Style.new
|
||||
@green_style.set_bg(Gtk::STATE_PRELIGHT, 0x0000, 0xFFFF, 0x0000)
|
||||
}
|
||||
end
|
||||
|
||||
def red_style # :nodoc:
|
||||
lazy_initialize(:red_style) {
|
||||
@red_style = Gtk::Style.new
|
||||
@red_style.set_bg(Gtk::STATE_PRELIGHT, 0xFFFF, 0x0000, 0x0000)
|
||||
}
|
||||
end
|
||||
|
||||
def info_panel # :nodoc:
|
||||
lazy_initialize(:info_panel) {
|
||||
@info_panel = Gtk::HBox.new(false, 0)
|
||||
@info_panel.border_width(10)
|
||||
@info_panel.pack_start(Gtk::Label.new("Runs:"), false, false, 0)
|
||||
@info_panel.pack_start(run_count_label, true, false, 0)
|
||||
@info_panel.pack_start(Gtk::Label.new("Assertions:"), false, false, 0)
|
||||
@info_panel.pack_start(assertion_count_label, true, false, 0)
|
||||
@info_panel.pack_start(Gtk::Label.new("Failures:"), false, false, 0)
|
||||
@info_panel.pack_start(failure_count_label, true, false, 0)
|
||||
@info_panel.pack_start(Gtk::Label.new("Errors:"), false, false, 0)
|
||||
@info_panel.pack_start(error_count_label, true, false, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def run_count_label # :nodoc:
|
||||
lazy_initialize(:run_count_label) {
|
||||
@run_count_label = Gtk::Label.new("0")
|
||||
@run_count_label.set_justify(Gtk::JUSTIFY_LEFT)
|
||||
}
|
||||
end
|
||||
|
||||
def assertion_count_label # :nodoc:
|
||||
lazy_initialize(:assertion_count_label) {
|
||||
@assertion_count_label = Gtk::Label.new("0")
|
||||
@assertion_count_label.set_justify(Gtk::JUSTIFY_LEFT)
|
||||
}
|
||||
end
|
||||
|
||||
def failure_count_label # :nodoc:
|
||||
lazy_initialize(:failure_count_label) {
|
||||
@failure_count_label = Gtk::Label.new("0")
|
||||
@failure_count_label.set_justify(Gtk::JUSTIFY_LEFT)
|
||||
}
|
||||
end
|
||||
|
||||
def error_count_label # :nodoc:
|
||||
lazy_initialize(:error_count_label) {
|
||||
@error_count_label = Gtk::Label.new("0")
|
||||
@error_count_label.set_justify(Gtk::JUSTIFY_LEFT)
|
||||
}
|
||||
end
|
||||
|
||||
def list_panel # :nodoc:
|
||||
lazy_initialize(:list_panel) {
|
||||
@list_panel = Gtk::HBox.new
|
||||
@list_panel.border_width(10)
|
||||
@list_panel.pack_start(list_scrolled_window, true, true, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def list_scrolled_window # :nodoc:
|
||||
lazy_initialize(:list_scrolled_window) {
|
||||
@list_scrolled_window = Gtk::ScrolledWindow.new
|
||||
@list_scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
|
||||
@list_scrolled_window.set_usize(@list_scrolled_window.allocation.width, 150)
|
||||
@list_scrolled_window.add_with_viewport(fault_list)
|
||||
}
|
||||
end
|
||||
|
||||
def fault_list # :nodoc:
|
||||
lazy_initialize(:fault_list) {
|
||||
@fault_list = Gtk::List.new
|
||||
}
|
||||
end
|
||||
|
||||
def detail_panel # :nodoc:
|
||||
lazy_initialize(:detail_panel) {
|
||||
@detail_panel = Gtk::HBox.new
|
||||
@detail_panel.border_width(10)
|
||||
@detail_panel.pack_start(detail_scrolled_window, true, true, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def detail_scrolled_window # :nodoc:
|
||||
lazy_initialize(:detail_scrolled_window) {
|
||||
@detail_scrolled_window = Gtk::ScrolledWindow.new
|
||||
@detail_scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
|
||||
@detail_scrolled_window.set_usize(400, @detail_scrolled_window.allocation.height)
|
||||
@detail_scrolled_window.add_with_viewport(outer_detail_sub_panel)
|
||||
}
|
||||
end
|
||||
|
||||
def outer_detail_sub_panel # :nodoc:
|
||||
lazy_initialize(:outer_detail_sub_panel) {
|
||||
@outer_detail_sub_panel = Gtk::VBox.new
|
||||
@outer_detail_sub_panel.pack_start(inner_detail_sub_panel, false, false, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def inner_detail_sub_panel # :nodoc:
|
||||
lazy_initialize(:inner_detail_sub_panel) {
|
||||
@inner_detail_sub_panel = Gtk::HBox.new
|
||||
@inner_detail_sub_panel.pack_start(fault_detail_label, false, false, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def fault_detail_label # :nodoc:
|
||||
lazy_initialize(:fault_detail_label) {
|
||||
@fault_detail_label = EnhancedLabel.new("")
|
||||
style = Gtk::Style.new
|
||||
font = Gdk::Font.font_load("-*-Courier New-medium-r-normal--*-120-*-*-*-*-*-*")
|
||||
begin
|
||||
style.set_font(font)
|
||||
rescue ArgumentError; end
|
||||
@fault_detail_label.set_style(style)
|
||||
@fault_detail_label.set_justify(Gtk::JUSTIFY_LEFT)
|
||||
@fault_detail_label.set_line_wrap(false)
|
||||
}
|
||||
end
|
||||
|
||||
def status_panel # :nodoc:
|
||||
lazy_initialize(:status_panel) {
|
||||
@status_panel = Gtk::HBox.new
|
||||
@status_panel.border_width(10)
|
||||
@status_panel.pack_start(status_entry, true, true, 0)
|
||||
}
|
||||
end
|
||||
|
||||
def status_entry # :nodoc:
|
||||
lazy_initialize(:status_entry) {
|
||||
@status_entry = Gtk::Entry.new
|
||||
@status_entry.set_editable(false)
|
||||
}
|
||||
end
|
||||
|
||||
def lazy_initialize(symbol) # :nodoc:
|
||||
if (!instance_eval("defined?(@#{symbol.to_s})"))
|
||||
yield
|
||||
end
|
||||
return instance_eval("@" + symbol.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
class EnhancedProgressBar < Gtk::ProgressBar # :nodoc: all
|
||||
def set_style(style)
|
||||
super
|
||||
hide
|
||||
show
|
||||
end
|
||||
end
|
||||
|
||||
class EnhancedLabel < Gtk::Label # :nodoc: all
|
||||
def set_text(text)
|
||||
super(text.gsub(/\n\t/, "\n" + (" " * 4)))
|
||||
end
|
||||
end
|
||||
|
||||
class FaultListItem < Gtk::ListItem # :nodoc: all
|
||||
attr_reader(:fault)
|
||||
def initialize(fault)
|
||||
super(fault.short_display)
|
||||
@fault = fault
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
Test::Unit::UI::GTK::TestRunner.start_command_line_test
|
||||
end
|
465
lib/test/unit/ui/gtk2/testrunner.rb
Normal file
465
lib/test/unit/ui/gtk2/testrunner.rb
Normal file
|
@ -0,0 +1,465 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Kenta MURATA.
|
||||
# Copyright:: Copyright (c) 2000-2002 Kenta MURATA. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require "gtk2"
|
||||
require "test/unit/ui/testrunnermediator"
|
||||
require "test/unit/ui/testrunnerutilities"
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module UI
|
||||
module GTK2 # :nodoc: all
|
||||
|
||||
Gtk.init
|
||||
|
||||
class EnhancedLabel < Gtk::Label # :nodoc: all
|
||||
def set_text(text)
|
||||
super(text.gsub(/\n\t/, "\n "))
|
||||
end
|
||||
end
|
||||
|
||||
class FaultList < Gtk::TreeView # :nodoc: all
|
||||
def initialize
|
||||
@faults = []
|
||||
@model = Gtk::ListStore.new(String, String)
|
||||
super(@model)
|
||||
column = Gtk::TreeViewColumn.new
|
||||
column.visible = false
|
||||
append_column(column)
|
||||
renderer = Gtk::CellRendererText.new
|
||||
column = Gtk::TreeViewColumn.new("Failures", renderer, {:text => 1})
|
||||
append_column(column)
|
||||
selection.mode = Gtk::SELECTION_SINGLE
|
||||
set_rules_hint(true)
|
||||
set_headers_visible(false)
|
||||
end # def initialize
|
||||
|
||||
def add_fault(fault)
|
||||
@faults.push(fault)
|
||||
iter = @model.append
|
||||
iter.set_value(0, (@faults.length - 1).to_s)
|
||||
iter.set_value(1, fault.short_display)
|
||||
end # def add_fault(fault)
|
||||
|
||||
def get_fault(iter)
|
||||
@faults[iter.get_value(0).to_i]
|
||||
end # def get_fault
|
||||
|
||||
def clear
|
||||
model.clear
|
||||
end # def clear
|
||||
end
|
||||
|
||||
class TestRunner
|
||||
extend TestRunnerUtilities
|
||||
|
||||
def lazy_initialize(symbol) # :nodoc:
|
||||
if !instance_eval("defined?(@#{symbol})") then
|
||||
yield
|
||||
end
|
||||
return instance_eval("@#{symbol}")
|
||||
end
|
||||
private :lazy_initialize
|
||||
|
||||
def status_entry # :nodoc:
|
||||
lazy_initialize(:status_entry) do
|
||||
@status_entry = Gtk::Entry.new
|
||||
@status_entry.editable = false
|
||||
end
|
||||
end
|
||||
private :status_entry
|
||||
|
||||
def status_panel # :nodoc:
|
||||
lazy_initialize(:status_panel) do
|
||||
@status_panel = Gtk::HBox.new
|
||||
@status_panel.border_width = 10
|
||||
@status_panel.pack_start(status_entry, true, true, 0)
|
||||
end
|
||||
end
|
||||
private :status_panel
|
||||
|
||||
def fault_detail_label # :nodoc:
|
||||
lazy_initialize(:fault_detail_label) do
|
||||
@fault_detail_label = EnhancedLabel.new("")
|
||||
# style = Gtk::Style.new
|
||||
# font = Gdk::Font.
|
||||
# font_load("-*-Courier 10 Pitch-medium-r-normal--*-120-*-*-*-*-*-*")
|
||||
# style.set_font(font)
|
||||
# @fault_detail_label.style = style
|
||||
@fault_detail_label.justify = Gtk::JUSTIFY_LEFT
|
||||
@fault_detail_label.wrap = false
|
||||
end
|
||||
end
|
||||
private :fault_detail_label
|
||||
|
||||
def inner_detail_sub_panel # :nodoc:
|
||||
lazy_initialize(:inner_detail_sub_panel) do
|
||||
@inner_detail_sub_panel = Gtk::HBox.new
|
||||
@inner_detail_sub_panel.pack_start(fault_detail_label, false, false, 0)
|
||||
end
|
||||
end
|
||||
private :inner_detail_sub_panel
|
||||
|
||||
def outer_detail_sub_panel # :nodoc:
|
||||
lazy_initialize(:outer_detail_sub_panel) do
|
||||
@outer_detail_sub_panel = Gtk::VBox.new
|
||||
@outer_detail_sub_panel.pack_start(inner_detail_sub_panel, false, false, 0)
|
||||
end
|
||||
end
|
||||
private :outer_detail_sub_panel
|
||||
|
||||
def detail_scrolled_window # :nodoc:
|
||||
lazy_initialize(:detail_scrolled_window) do
|
||||
@detail_scrolled_window = Gtk::ScrolledWindow.new
|
||||
@detail_scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
|
||||
@detail_scrolled_window.
|
||||
set_size_request(400, @detail_scrolled_window.allocation.height)
|
||||
@detail_scrolled_window.add_with_viewport(outer_detail_sub_panel)
|
||||
end
|
||||
end
|
||||
private :detail_scrolled_window
|
||||
|
||||
def detail_panel # :nodoc:
|
||||
lazy_initialize(:detail_panel) do
|
||||
@detail_panel = Gtk::HBox.new
|
||||
@detail_panel.border_width = 10
|
||||
@detail_panel.pack_start(detail_scrolled_window, true, true, 0)
|
||||
end
|
||||
end
|
||||
private :detail_panel
|
||||
|
||||
def fault_list # :nodoc:
|
||||
lazy_initialize(:fault_list) do
|
||||
@fault_list = FaultList.new
|
||||
end
|
||||
end
|
||||
private :fault_list
|
||||
|
||||
def list_scrolled_window # :nodoc:
|
||||
lazy_initialize(:list_scrolled_window) do
|
||||
@list_scrolled_window = Gtk::ScrolledWindow.new
|
||||
@list_scrolled_window.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC)
|
||||
@list_scrolled_window.
|
||||
set_size_request(@list_scrolled_window.allocation.width, 150)
|
||||
@list_scrolled_window.add_with_viewport(fault_list)
|
||||
end
|
||||
end
|
||||
private :list_scrolled_window
|
||||
|
||||
def list_panel # :nodoc:
|
||||
lazy_initialize(:list_panel) do
|
||||
@list_panel = Gtk::HBox.new
|
||||
@list_panel.border_width = 10
|
||||
@list_panel.pack_start(list_scrolled_window, true, true, 0)
|
||||
end
|
||||
end
|
||||
private :list_panel
|
||||
|
||||
def error_count_label # :nodoc:
|
||||
lazy_initialize(:error_count_label) do
|
||||
@error_count_label = Gtk::Label.new("0")
|
||||
@error_count_label.justify = Gtk::JUSTIFY_LEFT
|
||||
end
|
||||
end
|
||||
private :error_count_label
|
||||
|
||||
def failure_count_label # :nodoc:
|
||||
lazy_initialize(:failure_count_label) do
|
||||
@failure_count_label = Gtk::Label.new("0")
|
||||
@failure_count_label.justify = Gtk::JUSTIFY_LEFT
|
||||
end
|
||||
end
|
||||
private :failure_count_label
|
||||
|
||||
def assertion_count_label # :nodoc:
|
||||
lazy_initialize(:assertion_count_label) do
|
||||
@assertion_count_label = Gtk::Label.new("0")
|
||||
@assertion_count_label.justify = Gtk::JUSTIFY_LEFT
|
||||
end
|
||||
end
|
||||
private :assertion_count_label
|
||||
|
||||
def run_count_label # :nodoc:
|
||||
lazy_initialize(:run_count_label) do
|
||||
@run_count_label = Gtk::Label.new("0")
|
||||
@run_count_label.justify = Gtk::JUSTIFY_LEFT
|
||||
end
|
||||
end
|
||||
private :run_count_label
|
||||
|
||||
def info_panel # :nodoc:
|
||||
lazy_initialize(:info_panel) do
|
||||
@info_panel = Gtk::HBox.new(false, 0)
|
||||
@info_panel.border_width = 10
|
||||
@info_panel.pack_start(Gtk::Label.new("Runs:"), false, false, 0)
|
||||
@info_panel.pack_start(run_count_label, true, false, 0)
|
||||
@info_panel.pack_start(Gtk::Label.new("Assertions:"), false, false, 0)
|
||||
@info_panel.pack_start(assertion_count_label, true, false, 0)
|
||||
@info_panel.pack_start(Gtk::Label.new("Failures:"), false, false, 0)
|
||||
@info_panel.pack_start(failure_count_label, true, false, 0)
|
||||
@info_panel.pack_start(Gtk::Label.new("Errors:"), false, false, 0)
|
||||
@info_panel.pack_start(error_count_label, true, false, 0)
|
||||
end
|
||||
end # def info_panel
|
||||
private :info_panel
|
||||
|
||||
def green_style # :nodoc:
|
||||
lazy_initialize(:green_style) do
|
||||
@green_style = Gtk::Style.new
|
||||
@green_style.set_bg(Gtk::STATE_PRELIGHT, 0x0000, 0xFFFF, 0x0000)
|
||||
end
|
||||
end # def green_style
|
||||
private :green_style
|
||||
|
||||
def red_style # :nodoc:
|
||||
lazy_initialize(:red_style) do
|
||||
@red_style = Gtk::Style.new
|
||||
@red_style.set_bg(Gtk::STATE_PRELIGHT, 0xFFFF, 0x0000, 0x0000)
|
||||
end
|
||||
end # def red_style
|
||||
private :red_style
|
||||
|
||||
def test_progress_bar # :nodoc:
|
||||
lazy_initialize(:test_progress_bar) {
|
||||
@test_progress_bar = Gtk::ProgressBar.new
|
||||
@test_progress_bar.fraction = 0.0
|
||||
@test_progress_bar.
|
||||
set_size_request(@test_progress_bar.allocation.width,
|
||||
info_panel.size_request[1])
|
||||
@test_progress_bar.style = green_style
|
||||
}
|
||||
end # def test_progress_bar
|
||||
private :test_progress_bar
|
||||
|
||||
def progress_panel # :nodoc:
|
||||
lazy_initialize(:progress_panel) do
|
||||
@progress_panel = Gtk::HBox.new(false, 10)
|
||||
@progress_panel.border_width = 10
|
||||
@progress_panel.pack_start(test_progress_bar, true, true, 0)
|
||||
end
|
||||
end # def progress_panel
|
||||
|
||||
def run_button # :nodoc:
|
||||
lazy_initialize(:run_button) do
|
||||
@run_button = Gtk::Button.new("Run")
|
||||
end
|
||||
end # def run_button
|
||||
|
||||
def suite_name_entry # :nodoc:
|
||||
lazy_initialize(:suite_name_entry) do
|
||||
@suite_name_entry = Gtk::Entry.new
|
||||
@suite_name_entry.editable = false
|
||||
end
|
||||
end # def suite_name_entry
|
||||
private :suite_name_entry
|
||||
|
||||
def suite_panel # :nodoc:
|
||||
lazy_initialize(:suite_panel) do
|
||||
@suite_panel = Gtk::HBox.new(false, 10)
|
||||
@suite_panel.border_width = 10
|
||||
@suite_panel.pack_start(Gtk::Label.new("Suite:"), false, false, 0)
|
||||
@suite_panel.pack_start(suite_name_entry, true, true, 0)
|
||||
@suite_panel.pack_start(run_button, false, false, 0)
|
||||
end
|
||||
end # def suite_panel
|
||||
private :suite_panel
|
||||
|
||||
def main_panel # :nodoc:
|
||||
lazy_initialize(:main_panel) do
|
||||
@main_panel = Gtk::VBox.new(false, 0)
|
||||
@main_panel.pack_start(suite_panel, false, false, 0)
|
||||
@main_panel.pack_start(progress_panel, false, false, 0)
|
||||
@main_panel.pack_start(info_panel, false, false, 0)
|
||||
@main_panel.pack_start(list_panel, false, false, 0)
|
||||
@main_panel.pack_start(detail_panel, true, true, 0)
|
||||
@main_panel.pack_start(status_panel, false, false, 0)
|
||||
end
|
||||
end # def main_panel
|
||||
private :main_panel
|
||||
|
||||
def main_window # :nodoc:
|
||||
lazy_initialize(:main_window) do
|
||||
@main_window = Gtk::Window.new(Gtk::Window::TOPLEVEL)
|
||||
@main_window.set_title("Test::Unit TestRunner")
|
||||
@main_window.set_default_size(800, 600)
|
||||
@main_window.set_resizable(true)
|
||||
@main_window.add(main_panel)
|
||||
end
|
||||
end # def main_window
|
||||
private :main_window
|
||||
|
||||
def setup_ui # :nodoc:
|
||||
main_window.signal_connect("destroy", nil) { stop }
|
||||
main_window.show_all
|
||||
fault_list.selection.signal_connect("changed", nil) do
|
||||
|selection, data|
|
||||
if selection.selected then
|
||||
show_fault(fault_list.get_fault(selection.selected))
|
||||
else
|
||||
clear_fault
|
||||
end
|
||||
end
|
||||
end # def setup_ui
|
||||
private :setup_ui
|
||||
|
||||
def output_status(string) # :nodoc:
|
||||
status_entry.set_text(string)
|
||||
end # def output_status(string)
|
||||
private :output_status
|
||||
|
||||
def finished(elapsed_time) # :nodoc:
|
||||
test_progress_bar.fraction = 1.0
|
||||
output_status("Finished in #{elapsed_time} seconds")
|
||||
end # def finished(elapsed_time)
|
||||
private :finished
|
||||
|
||||
def test_started(test_name) # :nodoc:
|
||||
output_status("Running #{test_name}...")
|
||||
end # def test_started(test_name)
|
||||
private :test_started
|
||||
|
||||
def started(result) # :nodoc:
|
||||
@result = result
|
||||
output_status("Started...")
|
||||
end # def started(result)
|
||||
private :started
|
||||
|
||||
def test_finished(result) # :nodoc:
|
||||
test_progress_bar.fraction += 1.0 / @count
|
||||
end # def test_finished(result)
|
||||
|
||||
def result_changed(result) # :nodoc:
|
||||
run_count_label.label = result.run_count.to_s
|
||||
assertion_count_label.label = result.assertion_count.to_s
|
||||
failure_count_label.label = result.failure_count.to_s
|
||||
error_count_label.label = result.error_count.to_s
|
||||
end # def result_changed(result)
|
||||
private :result_changed
|
||||
|
||||
def clear_fault # :nodoc:
|
||||
raw_show_fault("")
|
||||
end # def clear_fault
|
||||
private :clear_fault
|
||||
|
||||
def raw_show_fault(string) # :nodoc:
|
||||
fault_detail_label.set_text(string)
|
||||
outer_detail_sub_panel.queue_resize
|
||||
end # def raw_show_fault(string)
|
||||
private :raw_show_fault
|
||||
|
||||
def show_fault(fault) # :nodoc:
|
||||
raw_show_fault(fault.long_display)
|
||||
end # def show_fault(fault)
|
||||
private :show_fault
|
||||
|
||||
def add_fault(fault) # :nodoc:
|
||||
if not @red then
|
||||
test_progress_bar.style = red_style
|
||||
@red = true
|
||||
end
|
||||
fault_list.add_fault(fault)
|
||||
end # def add_fault(fault)
|
||||
private :add_fault
|
||||
|
||||
def reset_ui(count) # :nodoc:
|
||||
test_progress_bar.style = green_style
|
||||
test_progress_bar.fraction = 0.0
|
||||
@count = count + 1
|
||||
@red = false
|
||||
|
||||
run_count_label.set_text("0")
|
||||
assertion_count_label.set_text("0")
|
||||
failure_count_label.set_text("0")
|
||||
error_count_label.set_text("0")
|
||||
|
||||
fault_list.clear
|
||||
end # def reset_ui(count)
|
||||
private :reset_ui
|
||||
|
||||
def stop # :nodoc:
|
||||
Gtk.main_quit
|
||||
end # def stop
|
||||
private :stop
|
||||
|
||||
def run_test
|
||||
@runner.raise(@restart_signal)
|
||||
end
|
||||
private :run_test
|
||||
|
||||
def start_ui # :nodoc
|
||||
@viewer.run
|
||||
running = false
|
||||
begin
|
||||
loop do
|
||||
if (running ^= true)
|
||||
run_button.child.text = "Stop"
|
||||
@mediator.run_suite
|
||||
else
|
||||
run_button.child.text = "Run"
|
||||
@viewer.join
|
||||
break
|
||||
end
|
||||
end
|
||||
rescue @restart_signal
|
||||
retry
|
||||
rescue
|
||||
end
|
||||
end # def start_ui
|
||||
private :start_ui
|
||||
|
||||
def attach_to_mediator
|
||||
run_button.signal_connect("clicked", nil) { run_test }
|
||||
@mediator.add_listener(TestRunnerMediator::RESET, &method(:reset_ui))
|
||||
@mediator.add_listener(TestRunnerMediator::STARTED, &method(:started))
|
||||
@mediator.add_listener(TestRunnerMediator::FINISHED, &method(:finished))
|
||||
@mediator.add_listener(TestResult::FAULT, &method(:add_fault))
|
||||
@mediator.add_listener(TestResult::CHANGED, &method(:result_changed))
|
||||
@mediator.add_listener(TestCase::STARTED, &method(:test_started))
|
||||
@mediator.add_listener(TestCase::FINISHED, &method(:test_finished))
|
||||
end # def attach_to_mediator
|
||||
private :attach_to_mediator
|
||||
|
||||
def setup_mediator
|
||||
@mediator = TestRunnerMediator.new(@suite)
|
||||
suite_name = @suite.to_s
|
||||
if @suite.kind_of?(Module) then
|
||||
suite_name = @suite.name
|
||||
end
|
||||
suite_name_entry.set_text(suite_name)
|
||||
end # def setup_mediator
|
||||
private :setup_mediator
|
||||
|
||||
def start
|
||||
setup_mediator
|
||||
setup_ui
|
||||
attach_to_mediator
|
||||
start_ui
|
||||
@result
|
||||
end # def start
|
||||
|
||||
def initialize(suite, output_level = NORMAL)
|
||||
if suite.respond_to?(:suite) then
|
||||
@suite = suite.suite
|
||||
else
|
||||
@suite = suite
|
||||
end
|
||||
@result = nil
|
||||
|
||||
@runner = Thread.current
|
||||
@restart_signal = Class.new(Exception)
|
||||
@viewer = Thread.start do
|
||||
@runner.join rescue @runner.run
|
||||
Gtk.main
|
||||
end
|
||||
@viewer.join rescue nil # wait deadlock to handshake
|
||||
end # def initialize(suite)
|
||||
|
||||
end # class TestRunner
|
||||
|
||||
end # module GTK2
|
||||
end # module UI
|
||||
end # module Unit
|
||||
end # module Test
|
68
lib/test/unit/ui/testrunnermediator.rb
Normal file
68
lib/test/unit/ui/testrunnermediator.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'test/unit'
|
||||
require 'test/unit/util/observable'
|
||||
require 'test/unit/testresult'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module UI # :nodoc:
|
||||
|
||||
# Provides an interface to write any given UI against,
|
||||
# hopefully making it easy to write new UIs.
|
||||
class TestRunnerMediator
|
||||
RESET = name + "::RESET"
|
||||
STARTED = name + "::STARTED"
|
||||
FINISHED = name + "::FINISHED"
|
||||
|
||||
include Util::Observable
|
||||
|
||||
# Creates a new TestRunnerMediator initialized to run
|
||||
# the passed suite.
|
||||
def initialize(suite)
|
||||
@suite = suite
|
||||
end
|
||||
|
||||
# Runs the suite the TestRunnerMediator was created
|
||||
# with.
|
||||
def run_suite
|
||||
Unit.run = true
|
||||
begin_time = Time.now
|
||||
notify_listeners(RESET, @suite.size)
|
||||
result = create_result
|
||||
notify_listeners(STARTED, result)
|
||||
result_listener = result.add_listener(TestResult::CHANGED) do |updated_result|
|
||||
notify_listeners(TestResult::CHANGED, updated_result)
|
||||
end
|
||||
|
||||
fault_listener = result.add_listener(TestResult::FAULT) do |fault|
|
||||
notify_listeners(TestResult::FAULT, fault)
|
||||
end
|
||||
|
||||
@suite.run(result) do |channel, value|
|
||||
notify_listeners(channel, value)
|
||||
end
|
||||
|
||||
result.remove_listener(TestResult::FAULT, fault_listener)
|
||||
result.remove_listener(TestResult::CHANGED, result_listener)
|
||||
end_time = Time.now
|
||||
elapsed_time = end_time - begin_time
|
||||
notify_listeners(FINISHED, elapsed_time) #"Finished in #{elapsed_time} seconds.")
|
||||
return result
|
||||
end
|
||||
|
||||
private
|
||||
# A factory method to create the result the mediator
|
||||
# should run with. Can be overridden by subclasses if
|
||||
# one wants to use a different result.
|
||||
def create_result
|
||||
return TestResult.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
46
lib/test/unit/ui/testrunnerutilities.rb
Normal file
46
lib/test/unit/ui/testrunnerutilities.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module UI
|
||||
|
||||
SILENT = 0
|
||||
PROGRESS_ONLY = 1
|
||||
NORMAL = 2
|
||||
VERBOSE = 3
|
||||
|
||||
# Provides some utilities common to most, if not all,
|
||||
# TestRunners.
|
||||
#
|
||||
#--
|
||||
#
|
||||
# Perhaps there ought to be a TestRunner superclass? There
|
||||
# seems to be a decent amount of shared code between test
|
||||
# runners.
|
||||
|
||||
module TestRunnerUtilities
|
||||
|
||||
# Creates a new TestRunner and runs the suite.
|
||||
def run(suite, output_level=NORMAL)
|
||||
return new(suite, output_level).start
|
||||
end
|
||||
|
||||
# Takes care of the ARGV parsing and suite
|
||||
# determination necessary for running one of the
|
||||
# TestRunners from the command line.
|
||||
def start_command_line_test
|
||||
if ARGV.empty?
|
||||
puts "You should supply the name of a test suite file to the runner"
|
||||
exit
|
||||
end
|
||||
require ARGV[0].gsub(/.+::/, '')
|
||||
new(eval(ARGV[0])).start
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
260
lib/test/unit/ui/tk/testrunner.rb
Normal file
260
lib/test/unit/ui/tk/testrunner.rb
Normal file
|
@ -0,0 +1,260 @@
|
|||
#--
|
||||
#
|
||||
# Original Author:: Nathaniel Talbott.
|
||||
# Author:: Kazuhiro NISHIYAMA.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# Copyright:: Copyright (c) 2003 Kazuhiro NISHIYAMA. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'tk'
|
||||
require 'test/unit/ui/testrunnermediator'
|
||||
require 'test/unit/ui/testrunnerutilities'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module UI
|
||||
module Tk # :nodoc:
|
||||
|
||||
# Runs a Test::Unit::TestSuite in a Tk UI. Obviously,
|
||||
# this one requires you to have Tk
|
||||
# and the Ruby Tk extension installed.
|
||||
class TestRunner
|
||||
extend TestRunnerUtilities
|
||||
|
||||
# Creates a new TestRunner for running the passed
|
||||
# suite.
|
||||
def initialize(suite, output_level = NORMAL)
|
||||
if (suite.respond_to?(:suite))
|
||||
@suite = suite.suite
|
||||
else
|
||||
@suite = suite
|
||||
end
|
||||
@result = nil
|
||||
|
||||
@red = false
|
||||
@fault_detail_list = []
|
||||
@runner = Thread.current
|
||||
@restart_signal = Class.new(Exception)
|
||||
@viewer = Thread.start do
|
||||
@runner.join rescue @runner.run
|
||||
::Tk.mainloop
|
||||
end
|
||||
@viewer.join rescue nil # wait deadlock to handshake
|
||||
end
|
||||
|
||||
# Begins the test run.
|
||||
def start
|
||||
setup_ui
|
||||
setup_mediator
|
||||
attach_to_mediator
|
||||
start_ui
|
||||
@result
|
||||
end
|
||||
|
||||
private
|
||||
def setup_mediator # :nodoc:
|
||||
@mediator = TestRunnerMediator.new(@suite)
|
||||
suite_name = @suite.to_s
|
||||
if ( @suite.kind_of?(Module) )
|
||||
suite_name = @suite.name
|
||||
end
|
||||
@suite_name_entry.value = suite_name
|
||||
end
|
||||
|
||||
def attach_to_mediator # :nodoc:
|
||||
@run_button.command(method(:run_test))
|
||||
@fault_list.bind('ButtonPress-1', proc{|y|
|
||||
fault = @fault_detail_list[@fault_list.nearest(y)]
|
||||
if fault
|
||||
show_fault(fault)
|
||||
end
|
||||
}, '%y')
|
||||
@mediator.add_listener(TestRunnerMediator::RESET, &method(:reset_ui))
|
||||
@mediator.add_listener(TestResult::FAULT, &method(:add_fault))
|
||||
@mediator.add_listener(TestResult::CHANGED, &method(:result_changed))
|
||||
@mediator.add_listener(TestRunnerMediator::STARTED, &method(:started))
|
||||
@mediator.add_listener(TestCase::STARTED, &method(:test_started))
|
||||
@mediator.add_listener(TestRunnerMediator::FINISHED, &method(:finished))
|
||||
end
|
||||
|
||||
def run_test
|
||||
@runner.raise(@restart_signal)
|
||||
end
|
||||
|
||||
def start_ui # :nodoc:
|
||||
@viewer.run
|
||||
running = false
|
||||
begin
|
||||
loop do
|
||||
if (running ^= true)
|
||||
@run_button.configure('text'=>'Stop')
|
||||
@mediator.run_suite
|
||||
else
|
||||
@run_button.configure('text'=>'Run')
|
||||
@viewer.join
|
||||
break
|
||||
end
|
||||
end
|
||||
rescue @restart_signal
|
||||
retry
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
def stop # :nodoc:
|
||||
::Tk.exit
|
||||
end
|
||||
|
||||
def reset_ui(count) # :nodoc:
|
||||
@test_total_count = count.to_f
|
||||
@test_progress_bar.configure('background'=>'green')
|
||||
@test_progress_bar.place('relwidth'=>(count.zero? ? 0 : 0/count))
|
||||
@red = false
|
||||
|
||||
@test_count_label.value = 0
|
||||
@assertion_count_label.value = 0
|
||||
@failure_count_label.value = 0
|
||||
@error_count_label.value = 0
|
||||
|
||||
@fault_list.delete(0, 'end')
|
||||
@fault_detail_list = []
|
||||
clear_fault
|
||||
end
|
||||
|
||||
def add_fault(fault) # :nodoc:
|
||||
if ( ! @red )
|
||||
@test_progress_bar.configure('background'=>'red')
|
||||
@red = true
|
||||
end
|
||||
@fault_detail_list.push fault
|
||||
@fault_list.insert('end', fault.short_display)
|
||||
end
|
||||
|
||||
def show_fault(fault) # :nodoc:
|
||||
raw_show_fault(fault.long_display)
|
||||
end
|
||||
|
||||
def raw_show_fault(string) # :nodoc:
|
||||
@detail_text.value = string
|
||||
end
|
||||
|
||||
def clear_fault # :nodoc:
|
||||
raw_show_fault("")
|
||||
end
|
||||
|
||||
def result_changed(result) # :nodoc:
|
||||
@test_count_label.value = result.run_count
|
||||
@test_progress_bar.place('relwidth'=>result.run_count/@test_total_count)
|
||||
@assertion_count_label.value = result.assertion_count
|
||||
@failure_count_label.value = result.failure_count
|
||||
@error_count_label.value = result.error_count
|
||||
end
|
||||
|
||||
def started(result) # :nodoc:
|
||||
@result = result
|
||||
output_status("Started...")
|
||||
end
|
||||
|
||||
def test_started(test_name)
|
||||
output_status("Running #{test_name}...")
|
||||
end
|
||||
|
||||
def finished(elapsed_time)
|
||||
output_status("Finished in #{elapsed_time} seconds")
|
||||
end
|
||||
|
||||
def output_status(string) # :nodoc:
|
||||
@status_entry.value = string
|
||||
end
|
||||
|
||||
def setup_ui # :nodoc:
|
||||
@status_entry = TkVariable.new
|
||||
l = TkLabel.new(nil, 'textvariable'=>@status_entry, 'relief'=>'sunken')
|
||||
l.pack('side'=>'bottom', 'fill'=>'x')
|
||||
|
||||
suite_frame = TkFrame.new.pack('fill'=>'x')
|
||||
|
||||
@run_button = TkButton.new(suite_frame, 'text'=>'Run')
|
||||
@run_button.pack('side'=>'right')
|
||||
|
||||
TkLabel.new(suite_frame, 'text'=>'Suite:').pack('side'=>'left')
|
||||
@suite_name_entry = TkVariable.new
|
||||
l = TkLabel.new(suite_frame, 'textvariable'=>@suite_name_entry, 'relief'=>'sunken')
|
||||
l.pack('side'=>'left', 'fill'=>'x', 'expand'=>true)
|
||||
|
||||
f = TkFrame.new(nil, 'relief'=>'sunken', 'borderwidth'=>3, 'height'=>20).pack('fill'=>'x', 'padx'=>1)
|
||||
@test_progress_bar = TkFrame.new(f, 'background'=>'green').place('anchor'=>'nw', 'relwidth'=>0.0, 'relheight'=>1.0)
|
||||
|
||||
info_frame = TkFrame.new.pack('fill'=>'x')
|
||||
@test_count_label = create_count_label(info_frame, 'Tests:')
|
||||
@assertion_count_label = create_count_label(info_frame, 'Assertions:')
|
||||
@failure_count_label = create_count_label(info_frame, 'Failures:')
|
||||
@error_count_label = create_count_label(info_frame, 'Errors:')
|
||||
|
||||
if (::Tk.info('command', TkPanedWindow::TkCommandNames[0]) != "")
|
||||
# use panedwindow
|
||||
paned_frame = TkPanedWindow.new("orient"=>"vertical").pack('fill'=>'both', 'expand'=>true)
|
||||
|
||||
fault_list_frame = TkFrame.new(paned_frame)
|
||||
detail_frame = TkFrame.new(paned_frame)
|
||||
|
||||
paned_frame.add(fault_list_frame, detail_frame)
|
||||
else
|
||||
# no panedwindow
|
||||
paned_frame = nil
|
||||
fault_list_frame = TkFrame.new.pack('fill'=>'both', 'expand'=>true)
|
||||
detail_frame = TkFrame.new.pack('fill'=>'both', 'expand'=>true)
|
||||
end
|
||||
|
||||
TkGrid.rowconfigure(fault_list_frame, 0, 'weight'=>1, 'minsize'=>0)
|
||||
TkGrid.columnconfigure(fault_list_frame, 0, 'weight'=>1, 'minsize'=>0)
|
||||
|
||||
fault_scrollbar_y = TkScrollbar.new(fault_list_frame)
|
||||
fault_scrollbar_x = TkScrollbar.new(fault_list_frame)
|
||||
@fault_list = TkListbox.new(fault_list_frame)
|
||||
@fault_list.yscrollbar(fault_scrollbar_y)
|
||||
@fault_list.xscrollbar(fault_scrollbar_x)
|
||||
|
||||
TkGrid.rowconfigure(detail_frame, 0, 'weight'=>1, 'minsize'=>0)
|
||||
TkGrid.columnconfigure(detail_frame, 0, 'weight'=>1, 'minsize'=>0)
|
||||
|
||||
::Tk.grid(@fault_list, fault_scrollbar_y, 'sticky'=>'news')
|
||||
::Tk.grid(fault_scrollbar_x, 'sticky'=>'news')
|
||||
|
||||
detail_scrollbar_y = TkScrollbar.new(detail_frame)
|
||||
detail_scrollbar_x = TkScrollbar.new(detail_frame)
|
||||
@detail_text = TkText.new(detail_frame, 'height'=>10, 'wrap'=>'none') {
|
||||
bindtags(bindtags - [TkText])
|
||||
}
|
||||
@detail_text.yscrollbar(detail_scrollbar_y)
|
||||
@detail_text.xscrollbar(detail_scrollbar_x)
|
||||
|
||||
::Tk.grid(@detail_text, detail_scrollbar_y, 'sticky'=>'news')
|
||||
::Tk.grid(detail_scrollbar_x, 'sticky'=>'news')
|
||||
|
||||
# rubber-style pane
|
||||
if paned_frame
|
||||
::Tk.update
|
||||
@height = paned_frame.winfo_height
|
||||
paned_frame.bind('Configure', proc{|h|
|
||||
paned_frame.sash_place(0, 0, paned_frame.sash_coord(0)[1] * h / @height)
|
||||
@height = h
|
||||
}, '%h')
|
||||
end
|
||||
end
|
||||
|
||||
def create_count_label(parent, label) # :nodoc:
|
||||
TkLabel.new(parent, 'text'=>label).pack('side'=>'left', 'expand'=>true)
|
||||
v = TkVariable.new(0)
|
||||
TkLabel.new(parent, 'textvariable'=>v).pack('side'=>'left', 'expand'=>true)
|
||||
v
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $0
|
||||
Test::Unit::UI::Tk::TestRunner.start_command_line_test
|
||||
end
|
40
lib/test/unit/util/backtracefilter.rb
Normal file
40
lib/test/unit/util/backtracefilter.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
module Test
|
||||
module Unit
|
||||
module Util
|
||||
module BacktraceFilter
|
||||
TESTUNIT_FILE_SEPARATORS = %r{[\\/:]}
|
||||
TESTUNIT_PREFIX = __FILE__.split(TESTUNIT_FILE_SEPARATORS)[0..-3]
|
||||
TESTUNIT_RB_FILE = /\.rb\Z/
|
||||
|
||||
def filter_backtrace(backtrace, prefix=nil)
|
||||
return ["No backtrace"] unless(backtrace)
|
||||
split_p = if(prefix)
|
||||
prefix.split(TESTUNIT_FILE_SEPARATORS)
|
||||
else
|
||||
TESTUNIT_PREFIX
|
||||
end
|
||||
match = proc do |e|
|
||||
split_e = e.split(TESTUNIT_FILE_SEPARATORS)[0, split_p.size]
|
||||
next false unless(split_e[0..-2] == split_p[0..-2])
|
||||
split_e[-1].sub(TESTUNIT_RB_FILE, '') == split_p[-1]
|
||||
end
|
||||
return backtrace unless(backtrace.detect(&match))
|
||||
found_prefix = false
|
||||
new_backtrace = backtrace.reverse.reject do |e|
|
||||
if(match[e])
|
||||
found_prefix = true
|
||||
true
|
||||
elsif(found_prefix)
|
||||
false
|
||||
else
|
||||
true
|
||||
end
|
||||
end.reverse
|
||||
new_backtrace = (new_backtrace.empty? ? backtrace : new_backtrace)
|
||||
new_backtrace = new_backtrace.reject(&match)
|
||||
new_backtrace.empty? ? backtrace : new_backtrace
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
90
lib/test/unit/util/observable.rb
Normal file
90
lib/test/unit/util/observable.rb
Normal file
|
@ -0,0 +1,90 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
require 'test/unit/util/procwrapper'
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module Util # :nodoc:
|
||||
|
||||
# This is a utility class that allows anything mixing
|
||||
# it in to notify a set of listeners about interesting
|
||||
# events.
|
||||
module Observable
|
||||
# We use this for defaults since nil might mean something
|
||||
NOTHING = "NOTHING/#{__id__}"
|
||||
|
||||
# Adds the passed proc as a listener on the
|
||||
# channel indicated by channel_name. listener_key
|
||||
# is used to remove the listener later; if none is
|
||||
# specified, the proc itself is used.
|
||||
#
|
||||
# Whatever is used as the listener_key is
|
||||
# returned, making it very easy to use the proc
|
||||
# itself as the listener_key:
|
||||
#
|
||||
# listener = add_listener("Channel") { ... }
|
||||
# remove_listener("Channel", listener)
|
||||
def add_listener(channel_name, listener_key=NOTHING, &listener) # :yields: value
|
||||
unless(block_given?)
|
||||
raise ArgumentError.new("No callback was passed as a listener")
|
||||
end
|
||||
|
||||
key = listener_key
|
||||
if (listener_key == NOTHING)
|
||||
listener_key = listener
|
||||
key = ProcWrapper.new(listener)
|
||||
end
|
||||
|
||||
channels[channel_name] ||= {}
|
||||
channels[channel_name][key] = listener
|
||||
return listener_key
|
||||
end
|
||||
|
||||
# Removes the listener indicated by listener_key
|
||||
# from the channel indicated by
|
||||
# channel_name. Returns the registered proc, or
|
||||
# nil if none was found.
|
||||
def remove_listener(channel_name, listener_key)
|
||||
channel = channels[channel_name]
|
||||
return nil unless (channel)
|
||||
key = listener_key
|
||||
if (listener_key.instance_of?(Proc))
|
||||
key = ProcWrapper.new(listener_key)
|
||||
end
|
||||
if (channel.has_key?(key))
|
||||
return channel.delete(key)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
# Calls all the procs registered on the channel
|
||||
# indicated by channel_name. If value is
|
||||
# specified, it is passed in to the procs,
|
||||
# otherwise they are called with no arguments.
|
||||
#
|
||||
#--
|
||||
#
|
||||
# Perhaps this should be private? Would it ever
|
||||
# make sense for an external class to call this
|
||||
# method directly?
|
||||
def notify_listeners(channel_name, *arguments)
|
||||
channel = channels[channel_name]
|
||||
return 0 unless (channel)
|
||||
listeners = channel.values
|
||||
listeners.each { |listener| listener.call(*arguments) }
|
||||
return listeners.size
|
||||
end
|
||||
|
||||
private
|
||||
def channels # :nodoc:
|
||||
@channels ||= {}
|
||||
return @channels
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
48
lib/test/unit/util/procwrapper.rb
Normal file
48
lib/test/unit/util/procwrapper.rb
Normal file
|
@ -0,0 +1,48 @@
|
|||
#--
|
||||
#
|
||||
# Author:: Nathaniel Talbott.
|
||||
# Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
|
||||
# License:: Ruby license.
|
||||
|
||||
module Test
|
||||
module Unit
|
||||
module Util
|
||||
|
||||
# Allows the storage of a Proc passed through '&' in a
|
||||
# hash.
|
||||
#
|
||||
# Note: this may be inefficient, since the hash being
|
||||
# used is not necessarily very good. In Observable,
|
||||
# efficiency is not too important, since the hash is
|
||||
# only accessed when adding and removing listeners,
|
||||
# not when notifying.
|
||||
|
||||
class ProcWrapper
|
||||
|
||||
# Creates a new wrapper for a_proc.
|
||||
def initialize(a_proc)
|
||||
@a_proc = a_proc
|
||||
@hash = a_proc.inspect.sub(/^(#<#{a_proc.class}:)/, '').sub(/(>)$/, '').hex
|
||||
end
|
||||
|
||||
def hash # :nodoc:
|
||||
return @hash
|
||||
end
|
||||
|
||||
def ==(other) # :nodoc:
|
||||
case(other)
|
||||
when ProcWrapper
|
||||
return @a_proc == other.to_proc
|
||||
else
|
||||
return super
|
||||
end
|
||||
end
|
||||
alias :eql? :==
|
||||
|
||||
def to_proc # :nodoc:
|
||||
return @a_proc
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue