Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

462 lines (379 sloc) 12.305 kb
class FancySpec {
"""
The FancySpec class is used for defining FancySpec testsuites.
Have a look at the tests/ directory to see some examples.
"""
def initialize: @description test_obj: @test_obj (@description) {
"""
@description Description @String@ for testcase.
@test_obj Object to be tested, defaults to @description.
"""
@spec_tests = []
@before_blocks = []
@after_blocks = []
}
def FancySpec describe: test_obj with: block {
"""
Factory method for creating FancySpec instances.
Calls @block with the new FancySpec instance as the receiver, then runs it.
Example:
FancySpec describe: MyTestClass with: {
# test cases using it:for:when: here.
}
"""
spec = FancySpec new: test_obj
block call_with_receiver: spec
spec run
}
def FancySpec describe: description for: test_obj with: block {
"""
Similar to @FancySpec~describe:with:@ but also taking an explicit @test_obj.
Example:
FancySpec describe: \"My cool class\" for: MyCoolClass with: {
# test cases using it:for:when: here.
}
"""
spec = FancySpec new: description test_obj: test_obj
block call_with_receiver: spec
spec run
}
def it: spec_info_string when: spec_block {
"""
@spec_info_string Info @String@ related to the test case defined in @spec_block.
@spec_block @Block@ that holds the testcase's code (including assertions).
Example:
it: \"should be an empty Array\" when: {
arr = [1,2,3]
3 times: { arr pop }
arr empty? is: true
}
"""
test = SpecTest new: spec_info_string block: spec_block
@spec_tests << test
}
def it: spec_info_string with: method_name when: spec_block {
"""
@spec_info_string Info @String@ related to the test case defined in @spec_block.
@method_name Name of Method that this testcase is related to.
@spec_block @Block@ that holds the testcase's code (including assertions).
Example:
it: \"should be an empty Array\" with: 'empty? when: {
arr = [1,2,3]
3 times: { arr pop }
arr empty? is: true
}
"""
test = SpecTest new: spec_info_string block: spec_block
@spec_tests << test
match @test_obj {
case Class ->
has_method? = @test_obj has_method?: method_name
{ has_method? = @test_obj metaclass has_method?: method_name } unless: has_method?
unless: has_method? do: {
SpecTest method_not_found: method_name for: @test_obj
}
}
}
alias_method: 'it:for:when: for: 'it:with:when:
def before_each: block {
"""
@block @Block@ to be run before each test case.
"""
@before_blocks << block
}
def after_each: block {
"""
@block @Block@ to be run after each test case.
"""
@after_blocks << block
}
def run {
"""
Runs a FancySpec's test cases.
"""
# " " ++ @description ++ ": " print
@spec_tests each: |test| {
@before_blocks each: |b| {
b call_with_receiver: test
}
test run: @test_obj
@after_blocks each: |b| {
b call_with_receiver: test
}
}
# untested_methods = @test_obj methods select: |m| {
# m tests size == 0
# }
# untested_methods empty? if_false: {
# ["WARNING: These methods need tests:",
# untested_methods map: 'name . select: |m| { m whitespace? not } . join: ", "] println
# }
}
class SpecTest {
"""
FancySpec test case class.
"""
read_slot: 'info_str
@@failed_positive = <[]>
@@failed_negative = <[]>
@@failed_count = 0
@@total_tests = 0
@@total_expectations = 0
@@methods_not_found = <[]>
def SpecTest add_expectation {
@@total_expectations = @@total_expectations + 1
}
def SpecTest failed_test: test {
"""
@actual_and_expected Pair of actual and expected values for a failed test case.
Gets called when a SpecTest failed.
"""
@@failed_positive[@@current_test_obj]: $ @@failed_positive[@@current_test_obj] || { Set new }
@@failed_positive[@@current_test_obj] << test
@@failed_count = @@failed_count + 1
}
def SpecTest failed_negative_test: test {
"""
@value Value that should not have occured.
Gets called when a negative SpecTest (using @NegativeMatcher@) failed.
"""
@@failed_negative[@@current_test_obj]: $ @@failed_negative[@@current_test_obj] || { Set new }
@@failed_negative[@@current_test_obj] << test
@@failed_count = @@failed_count + 1
}
def SpecTest method_not_found: method_name for: type {
{ @@methods_not_found[type]: []} unless: $ @@methods_not_found[type]
@@methods_not_found[type] << method_name
}
def SpecTest current {
@@current
}
def SpecTest print_failures: start_time no_failures: ok_block else: error_block {
let: '*stdout* be: *stderr* in: {
@@failed_positive each: |test_obj failed_tests| {
failed_tests each: |t| {
"\n> FAILED: " ++ test_obj ++ " " ++ (t info_str) print
t print_failed_positive
}
}
@@failed_negative each: |test_obj failed_tests| {
failed_tests each: |t| {
"\n> FAILED: " ++ test_obj ++ " " ++ (t info_str) print
t print_failed_negative
}
}
unless: (@@methods_not_found empty? ) do: {
"The following methods were referenced in tests but could not be found:" println
max_size = @@methods_not_found keys map: @{ to_s size } . max
@@methods_not_found each: |type, methods| {
*stdout* printf("%-#{max_size}s : ", type)
methods map: @{ to_fancy_message } . join: ", " . println
}
}
"\nRan #{@@total_tests} tests (#{@@total_expectations} expectations) with #{@@failed_count} failures in #{Time now - start_time} seconds." println
}
if: (@@failed_count == 0) then: ok_block else: error_block
}
def initialize: @info_str block: @block {
{ @@failed_positive = <[]> } unless: @@failed_positive
{ @@failed_negative = <[]> } unless: @@failed_negative
@failed_positive = []
@failed_negative = []
}
def run: test_obj {
@@current_test_obj = test_obj
@@current = self
@@total_tests = @@total_tests + 1
try {
@block call_with_receiver: self
} catch StandardError => e {
failed: (e, "No Exception") location: (e backtrace)
}
if: failed? then: {
"f" print
} else: {
"." print
}
}
def failed: actual_and_expected location: location (nil) {
unless: location do: {
location = caller() find: |l| { l =~ /__script__/ }
}
@failed_positive << (actual_and_expected, location)
SpecTest failed_test: self
}
def failed_negative: value {
{ value = [value, 'negative_failure] } unless: $ value responds_to?: 'at:
location = caller() find: |l| { l =~ /__script__/ }
@failed_negative << (value, location)
SpecTest failed_negative_test: self
}
def failed? {
@failed_positive empty? not || { @failed_negative empty? not }
}
def print_failed_positive {
" [" ++ (@failed_positive size) ++ " unexpected values]" println
print_failed_common: @failed_positive
}
def print_failed_negative {
" [" ++ (@failed_negative size) ++ " unexpected values]" println
"Should not have gotten the following values: " println
print_failed_common: @failed_negative
}
def print_failed_common: failures {
failures each: |f| {
actual, expected = f first
locations = f second
locations to_a map: |loc| {
loc = loc gsub(/:(\d+):in `[^']+'/, " +\1")
file, line = loc split: " +"
loc = "#{file} +#{line to_i - 1}" # somehow line is off by +1
loc = loc split: (Directory pwd + "/") . second
} . compact println
unless: (expected == 'negative_failure) do: {
" Expected: #{expected inspect}" println
" Received: #{actual inspect}" println
} else: {
" " ++ (actual inspect) println
}
}
}
}
class PositiveMatcher {
"""
PositiveMatcher expects its actual value to be equal to an
expected value.
If the values are not equal, a SpecTest failure is generated.
"""
def initialize: @actual_value {
SpecTest add_expectation
}
def == expected_value {
unless: (@actual_value == expected_value) do: {
SpecTest current failed: (@actual_value, expected_value)
}
}
def != expected_value {
unless: (@actual_value != expected_value) do: {
SpecTest current failed_negative: (@actual_value, expected_value)
}
}
def raise: exception_class {
try {
@actual_value call
# make sure we raise an exception.
# if no exepction raised at this point, we have an error.
SpecTest current failed: (nil, exception_class)
} catch exception_class {
# ok
} catch StandardError => e {
SpecTest current failed: (e class, exception_class)
}
}
def raise: exception_class with: block {
try {
@actual_value call
# same here
SpecTest current failed: (nil, exception_class)
} catch exception_class => e {
block call: [e]
# ok
} catch StandardError => e {
SpecTest current failed: (e class, exception_class)
}
}
def unknown_message: msg with_params: params {
"""
Forwards any other message and parameters to the object itself
and checks the return value.
"""
unless: (@actual_value receive_message: msg with_params: params) do: {
SpecTest current failed: (@actual_value, params first)
}
}
def be: block {
unless: (block call: [@actual_value]) do: {
SpecTest current failed: (@actual_value, nil)
}
}
}
class NegativeMatcher {
"""
NegativeMatcher expects its actual value to be unequal to an
expected value.
If the values are equal, a SpecTest failure is generated.
"""
def initialize: @actual_value {
SpecTest add_expectation
}
def == expected_value {
if: (@actual_value == expected_value) then: {
SpecTest current failed_negative: @actual_value
}
}
def != expected_value {
if: (@actual_value != expected_value) then: {
SpecTest current failed: (@actual_value, expected_value)
}
}
def raise: exception_class {
try {
@actual_value call
} catch exception_class {
SpecTest current failed_negative: (exception_class, nil)
} catch StandardError {
true
# ok
}
}
def unknown_message: msg with_params: params {
"""
Forwards any other message and parameters to the object itself
and checks the return value.
"""
if: (@actual_value receive_message: msg with_params: params) then: {
SpecTest current failed_negative: @actual_value
}
}
def be: block {
if: (block call: [@actual_value]) then: {
SpecTest current failed_negative: @actual_value
}
}
}
}
class Object {
def should {
"""
Returns a @PositiveMatcher@ for self.
"""
FancySpec PositiveMatcher new: self
}
alias_method: 'is for: 'should
alias_method: 'does for: 'should
def is: expected {
is == expected
}
def is_a: class {
is_a?: class . is: true
}
def is_not: expected {
does_not == expected
}
def should_not {
"""
Returns a @NegativeMatcher@ for self.
"""
FancySpec NegativeMatcher new: self
}
alias_method: 'is_not for: 'should_not
alias_method: 'does_not for: 'should_not
}
class Block {
def raises: exception_class {
FancySpec PositiveMatcher new: self . raise: exception_class
}
def raises: exception_class with: block {
FancySpec PositiveMatcher new: self . raise: exception_class with: block
}
}
Jump to Line
Something went wrong with that request. Please try again.