summaryrefslogtreecommitdiff
path: root/lib/test/unit/testcase.rb
blob: f53b460c5d6de17f577fae136d0f3f71483bcd14 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#--
#
# 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]

      # 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) and
               (method(test_method_name).arity == 0 ||
                method(test_method_name).arity == -1))
          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)
        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
        1
      end

      def add_assertion
        @_result.add_assertion
      end
      private :add_assertion

      def add_failure(message, all_locations=caller())
        @test_passed = false
        @_result.add_failure(Failure.new(name, filter_backtrace(all_locations), message))
      end
      private :add_failure

      def add_error(exception)
        @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