2003-02-11 22:12:14 -05:00
|
|
|
# :nodoc:
|
|
|
|
#
|
|
|
|
# Author:: Nathaniel Talbott.
|
2003-08-06 10:03:21 -04:00
|
|
|
# Copyright:: Copyright (c) 2000-2003 Nathaniel Talbott. All rights reserved.
|
2003-02-11 22:12:14 -05:00
|
|
|
# License:: Ruby license.
|
|
|
|
|
|
|
|
require 'test/unit/assertionfailederror'
|
2003-10-04 13:18:27 -04:00
|
|
|
require 'test/unit/util/backtracefilter'
|
2003-02-11 22:12:14 -05:00
|
|
|
|
|
|
|
module Test # :nodoc:
|
|
|
|
module Unit # :nodoc:
|
|
|
|
|
|
|
|
# Contains all of the standard Test::Unit assertions. Mixed in
|
|
|
|
# to Test::Unit::TestCase. To mix it in and use its
|
|
|
|
# functionality, you simply need to rescue
|
|
|
|
# Test::Unit::AssertionFailedError, and you can additionally
|
|
|
|
# override add_assertion to be notified whenever an assertion
|
|
|
|
# is made.
|
|
|
|
#
|
|
|
|
# Notes:
|
|
|
|
# * The message to each assertion, if given, will be
|
|
|
|
# propagated with the failure.
|
|
|
|
# * It's easy to add your own assertions based on assert_block().
|
|
|
|
module Assertions
|
|
|
|
|
|
|
|
# The assertion upon which all other assertions are
|
|
|
|
# based. Passes if the block yields true.
|
|
|
|
public
|
2003-10-04 17:40:17 -04:00
|
|
|
def assert_block(message="assert_block failed.") # :yields:
|
2003-02-11 22:12:14 -05:00
|
|
|
_wrap_assertion do
|
|
|
|
if (! yield)
|
|
|
|
raise AssertionFailedError.new(message.to_s)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if boolean is true.
|
|
|
|
public
|
2003-10-04 17:40:17 -04:00
|
|
|
def assert(boolean, message=nil)
|
2003-02-11 22:12:14 -05:00
|
|
|
_wrap_assertion do
|
|
|
|
assert_block("assert should not be called with a block.") { !block_given? }
|
2003-10-04 17:40:17 -04:00
|
|
|
assert_block(build_message(message, "<?> is not true.", boolean)) { boolean }
|
2003-02-11 22:12:14 -05:00
|
|
|
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.
|
|
|
|
public
|
|
|
|
def assert_equal(expected, actual, message=nil)
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, <<EOT, expected, actual)
|
|
|
|
<?> expected but was
|
2003-10-04 17:40:17 -04:00
|
|
|
<?>.
|
2003-10-04 15:18:52 -04:00
|
|
|
EOT
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { expected == actual }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if block raises exception.
|
|
|
|
public
|
|
|
|
def assert_raises(expected_exception_klass, message="")
|
|
|
|
_wrap_assertion do
|
|
|
|
assert_instance_of(Class, expected_exception_klass, "Should expect a class of exception")
|
|
|
|
actual_exception = nil
|
2003-10-04 17:40:17 -04:00
|
|
|
full_message = build_message(message, "<?> exception expected but none was thrown.", expected_exception_klass)
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) do
|
|
|
|
thrown = false
|
|
|
|
begin
|
|
|
|
yield
|
|
|
|
rescue Exception => thrown_exception
|
|
|
|
actual_exception = thrown_exception
|
|
|
|
thrown = true
|
|
|
|
end
|
|
|
|
thrown
|
|
|
|
end
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, "<?> exception expected but was\n?", expected_exception_klass, actual_exception)
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { expected_exception_klass == actual_exception.class }
|
|
|
|
actual_exception
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if object.class == klass.
|
|
|
|
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")
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, <<EOT, object, klass, object.class)
|
|
|
|
<?> expected to be an instance of
|
|
|
|
<?> but was
|
2003-10-04 17:40:17 -04:00
|
|
|
<?>.
|
2003-10-04 15:18:52 -04:00
|
|
|
EOT
|
2003-10-04 17:40:17 -04:00
|
|
|
assert_block(full_message){object.instance_of?(klass)}
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if object.nil?.
|
|
|
|
public
|
|
|
|
def assert_nil(object, message="")
|
|
|
|
assert_equal(nil, object, message)
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if object.kind_of?(klass).
|
|
|
|
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.")
|
2003-10-04 17:40:17 -04:00
|
|
|
full_message = build_message(message, "<?>\nexpected to be kind_of\\?<?>.", object, klass)
|
|
|
|
assert_block(full_message){object.kind_of?(klass)}
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if object.respond_to?(method) is true.
|
|
|
|
public
|
|
|
|
def assert_respond_to(object, method, message="")
|
|
|
|
_wrap_assertion do
|
2003-10-04 15:18:52 -04:00
|
|
|
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)
|
|
|
|
|
2003-10-04 17:40:17 -04:00
|
|
|
assert_block(full_message) do
|
|
|
|
method.kind_of?(Symbol) || method.respond_to?(:to_str)
|
|
|
|
end
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, <<EOT, object, object.class, method)
|
|
|
|
<?>
|
|
|
|
of type <?>
|
2003-10-04 17:40:17 -04:00
|
|
|
expected to respond_to\\?<?>.
|
2003-10-04 15:18:52 -04:00
|
|
|
EOT
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { object.respond_to?(method) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if string =~ pattern.
|
|
|
|
public
|
|
|
|
def assert_match(pattern, string, message="")
|
|
|
|
_wrap_assertion do
|
2003-08-06 10:03:21 -04:00
|
|
|
pattern = case(pattern)
|
|
|
|
when String
|
2003-10-04 13:18:27 -04:00
|
|
|
Regexp.new(Regexp.escape(pattern))
|
2003-08-06 10:03:21 -04:00
|
|
|
else
|
|
|
|
pattern
|
|
|
|
end
|
2003-10-04 17:40:17 -04:00
|
|
|
full_message = build_message(message, "<?> expected to be =~\n<?>.", string, pattern)
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { string =~ pattern }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if actual.equal?(expected) (i.e. they are the
|
|
|
|
# same instance).
|
|
|
|
public
|
|
|
|
def assert_same(expected, actual, message="")
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
|
|
|
|
<?>
|
|
|
|
with id <?> expected to be equal\\? to
|
|
|
|
<?>
|
2003-10-04 17:40:17 -04:00
|
|
|
with id <?>.
|
2003-10-04 15:18:52 -04:00
|
|
|
EOT
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { actual.equal?(expected) }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Compares the two objects based on the passed
|
|
|
|
# operator. Passes if object1.send(operator, object2) is
|
|
|
|
# true.
|
|
|
|
public
|
|
|
|
def assert_operator(object1, operator, object2, message="")
|
2003-10-04 13:18:27 -04:00
|
|
|
_wrap_assertion do
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(nil, "<?>\ngiven as the operator for #assert_operator must be a Symbol or #respond_to\\?(:to_str).", operator)
|
2003-10-04 17:40:17 -04:00
|
|
|
assert_block(full_message){operator.kind_of?(Symbol) || operator.respond_to?(:to_str)}
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, <<EOT, object1, AssertionMessage.literal(operator), object2)
|
|
|
|
<?> expected to be
|
|
|
|
?
|
2003-10-04 17:40:17 -04:00
|
|
|
<?>.
|
2003-10-04 15:18:52 -04:00
|
|
|
EOT
|
2003-10-04 13:18:27 -04:00
|
|
|
assert_block(full_message) { object1.send(operator, object2) }
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if block does not raise an exception.
|
|
|
|
public
|
|
|
|
def assert_nothing_raised(*args)
|
|
|
|
_wrap_assertion do
|
|
|
|
message = ""
|
|
|
|
if (!args[-1].instance_of?(Class))
|
|
|
|
message = args.pop
|
|
|
|
end
|
|
|
|
begin
|
|
|
|
yield
|
2003-10-01 22:20:42 -04:00
|
|
|
rescue Exception => e
|
|
|
|
if ((args.empty? && !e.instance_of?(AssertionFailedError)) || args.include?(e.class))
|
2003-10-04 17:40:17 -04:00
|
|
|
assert_block(build_message(message, "Exception raised:\n?", e)){false}
|
2003-02-11 22:12:14 -05:00
|
|
|
else
|
2003-10-01 22:20:42 -04:00
|
|
|
raise e.class, e.message, e.backtrace
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Always fails.
|
|
|
|
public
|
2003-10-04 17:40:17 -04:00
|
|
|
def flunk(message="Flunked")
|
|
|
|
assert_block(build_message(message)){false}
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if !actual.equal?(expected).
|
|
|
|
public
|
|
|
|
def assert_not_same(expected, actual, message="")
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, <<EOT, expected, expected.__id__, actual, actual.__id__)
|
|
|
|
<?>
|
|
|
|
with id <?> expected to not be equal\\? to
|
|
|
|
<?>
|
2003-10-04 17:40:17 -04:00
|
|
|
with id <?>.
|
2003-10-04 15:18:52 -04:00
|
|
|
EOT
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { !actual.equal?(expected) }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if expected != actual.
|
|
|
|
public
|
|
|
|
def assert_not_equal(expected, actual, message="")
|
2003-10-04 17:40:17 -04:00
|
|
|
full_message = build_message(message, "<?> expected to be != to\n<?>.", expected, actual)
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { expected != actual }
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if !object.nil?.
|
|
|
|
public
|
|
|
|
def assert_not_nil(object, message="")
|
2003-10-04 17:40:17 -04:00
|
|
|
warn("Assertions#assert_not_nil is deprecated and will be removed after 1.8.1. Use #assert.")
|
|
|
|
full_message = build_message(message, "<?> expected to not be nil.", object)
|
|
|
|
assert_block(full_message){!object.nil?}
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if string !~ regularExpression.
|
|
|
|
public
|
|
|
|
def assert_no_match(regexp, string, message="")
|
|
|
|
_wrap_assertion do
|
|
|
|
assert_instance_of(Regexp, regexp, "The first argument to assert_does_not_match should be a Regexp.")
|
2003-10-04 17:40:17 -04:00
|
|
|
full_message = build_message(message, "<?> expected to not match\n<?>.", regexp, string)
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { regexp !~ string }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if block throws symbol.
|
|
|
|
public
|
|
|
|
def assert_throws(expected_symbol, message="", &proc)
|
|
|
|
_wrap_assertion do
|
|
|
|
assert_instance_of(Symbol, expected_symbol, "assert_throws expects the symbol that should be thrown for its first argument")
|
2003-10-04 17:40:17 -04:00
|
|
|
assert_block("Should have passed a block to assert_throws."){block_given?}
|
2003-02-11 22:12:14 -05:00
|
|
|
caught = true
|
|
|
|
begin
|
|
|
|
catch(expected_symbol) do
|
|
|
|
proc.call
|
|
|
|
caught = false
|
|
|
|
end
|
2003-10-04 17:40:17 -04:00
|
|
|
full_message = build_message(message, "<?> should have been thrown.", expected_symbol)
|
|
|
|
assert_block(full_message){caught}
|
2003-02-11 22:12:14 -05:00
|
|
|
rescue NameError => name_error
|
|
|
|
if ( name_error.message !~ /^uncaught throw `(.+)'$/ ) #`
|
|
|
|
raise name_error
|
|
|
|
end
|
2003-10-04 17:40:17 -04:00
|
|
|
full_message = build_message(message, "<?> expected to be thrown but\n<?> was thrown.", expected_symbol, $1.intern)
|
2003-02-11 22:12:14 -05:00
|
|
|
flunk(full_message)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if block does not throw anything.
|
|
|
|
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 NameError => name_error
|
|
|
|
if (name_error.message !~ /^uncaught throw `(.+)'$/ ) #`
|
|
|
|
raise name_error
|
|
|
|
end
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, "<?> was thrown when nothing was expected", $1.intern)
|
2003-02-11 22:12:14 -05:00
|
|
|
flunk(full_message)
|
|
|
|
end
|
2003-10-04 15:18:52 -04:00
|
|
|
assert(true, "Expected nothing to be thrown")
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if expected_float and actual_float are equal
|
|
|
|
# within delta tolerance.
|
|
|
|
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")
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, <<EOT, expected_float, actual_float, delta)
|
|
|
|
<?> and
|
|
|
|
<?> expected to be within
|
2003-10-04 17:40:17 -04:00
|
|
|
<?> of each other.
|
2003-10-04 15:18:52 -04:00
|
|
|
EOT
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { (expected_float.to_f - actual_float.to_f).abs <= delta.to_f }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# Passes if the method sent returns a true value.
|
|
|
|
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")
|
2003-10-04 15:18:52 -04:00
|
|
|
full_message = build_message(message, <<EOT, send_array[0], AssertionMessage.literal(send_array[1].to_s), send_array[2..-1])
|
|
|
|
<?> expected to respond to
|
2003-10-04 17:40:17 -04:00
|
|
|
<?(?)> with a true value.
|
2003-10-04 15:18:52 -04:00
|
|
|
EOT
|
2003-02-11 22:12:14 -05:00
|
|
|
assert_block(full_message) { send_array[0].__send__(send_array[1], *send_array[2..-1]) }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
public
|
2003-10-04 17:40:17 -04:00
|
|
|
def build_message(head, template=nil, *arguments) # :nodoc:
|
|
|
|
template &&= template.chomp
|
|
|
|
return AssertionMessage.new(head, template, arguments)
|
2003-02-11 22:12:14 -05:00
|
|
|
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.
|
|
|
|
private
|
|
|
|
def add_assertion
|
|
|
|
end
|
2003-10-04 20:59:02 -04:00
|
|
|
|
|
|
|
# Select whether or not to use the prettyprinter. If this
|
|
|
|
# option is set to false before any assertions are made, the
|
|
|
|
# prettyprinter will not be required at all.
|
|
|
|
public
|
|
|
|
def self.use_pp=(value)
|
|
|
|
AssertionMessage.use_pp = value
|
|
|
|
end
|
2003-02-11 22:12:14 -05:00
|
|
|
|
|
|
|
class AssertionMessage # :nodoc: all
|
2003-10-04 20:59:02 -04:00
|
|
|
@use_pp = true
|
|
|
|
class << self
|
|
|
|
attr_accessor :use_pp
|
|
|
|
end
|
|
|
|
|
2003-10-04 13:18:27 -04:00
|
|
|
class Literal
|
|
|
|
def initialize(value)
|
|
|
|
@value = value
|
|
|
|
end
|
|
|
|
|
|
|
|
def inspect
|
2003-10-04 20:59:02 -04:00
|
|
|
@value.to_s
|
2003-10-04 13:18:27 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2003-10-04 15:18:52 -04:00
|
|
|
class Template
|
|
|
|
def self.create(string)
|
2003-10-04 17:40:17 -04:00
|
|
|
parts = (string ? string.scan(/(?=[^\\])\?|(?:\\\?|[^\?])+/m) : [])
|
|
|
|
self.new(parts)
|
2003-10-04 15:18:52 -04:00
|
|
|
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
|
|
|
|
|
2003-10-04 13:18:27 -04:00
|
|
|
def self.literal(value)
|
|
|
|
Literal.new(value)
|
|
|
|
end
|
|
|
|
|
|
|
|
include Util::BacktraceFilter
|
|
|
|
|
2003-10-04 15:18:52 -04:00
|
|
|
def initialize(head, template_string, parameters)
|
|
|
|
@head = head
|
|
|
|
@template_string = template_string
|
|
|
|
@parameters = parameters
|
|
|
|
end
|
|
|
|
|
2003-10-04 13:18:27 -04:00
|
|
|
def convert(object)
|
2003-02-11 22:12:14 -05:00
|
|
|
case object
|
|
|
|
when Exception
|
2003-10-04 20:59:02 -04:00
|
|
|
<<EOM.chop
|
|
|
|
Class: <#{convert(object.class)}>
|
|
|
|
Message: <#{convert(object.message)}>
|
2003-10-04 13:18:27 -04:00
|
|
|
---Backtrace---
|
|
|
|
#{filter_backtrace(object.backtrace).join("\n")}
|
|
|
|
---------------
|
|
|
|
EOM
|
2003-02-11 22:12:14 -05:00
|
|
|
else
|
2003-10-04 20:59:02 -04:00
|
|
|
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
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
end
|
2003-10-04 15:18:52 -04:00
|
|
|
|
|
|
|
def template
|
|
|
|
@template ||= Template.create(@template_string)
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
2003-10-04 15:18:52 -04:00
|
|
|
|
2003-10-04 17:40:17 -04:00
|
|
|
def add_period(string)
|
|
|
|
(string =~ /\.\Z/ ? string : string + '.')
|
|
|
|
end
|
|
|
|
|
2003-02-11 22:12:14 -05:00
|
|
|
def to_s
|
|
|
|
message_parts = []
|
2003-10-04 15:18:52 -04:00
|
|
|
if (@head)
|
|
|
|
head = @head.to_s
|
|
|
|
unless(head.empty?)
|
2003-10-04 17:40:17 -04:00
|
|
|
message_parts << add_period(head)
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
end
|
2003-10-04 17:40:17 -04:00
|
|
|
tail = template.result(@parameters.collect{|e| convert(e)})
|
|
|
|
message_parts << tail unless(tail.empty?)
|
|
|
|
message_parts.join("\n")
|
2003-02-11 22:12:14 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|