test-unit 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/History.txt +5 -0
  2. data/Manifest.txt +48 -0
  3. data/README.txt +27 -0
  4. data/Rakefile +15 -0
  5. data/bin/testrb +5 -0
  6. data/lib/test/unit.rb +280 -0
  7. data/lib/test/unit/assertionfailederror.rb +14 -0
  8. data/lib/test/unit/assertions.rb +622 -0
  9. data/lib/test/unit/autorunner.rb +220 -0
  10. data/lib/test/unit/collector.rb +43 -0
  11. data/lib/test/unit/collector/dir.rb +108 -0
  12. data/lib/test/unit/collector/objectspace.rb +34 -0
  13. data/lib/test/unit/error.rb +56 -0
  14. data/lib/test/unit/failure.rb +51 -0
  15. data/lib/test/unit/testcase.rb +160 -0
  16. data/lib/test/unit/testresult.rb +80 -0
  17. data/lib/test/unit/testsuite.rb +76 -0
  18. data/lib/test/unit/ui/console/testrunner.rb +127 -0
  19. data/lib/test/unit/ui/fox/testrunner.rb +268 -0
  20. data/lib/test/unit/ui/gtk/testrunner.rb +416 -0
  21. data/lib/test/unit/ui/gtk2/testrunner.rb +465 -0
  22. data/lib/test/unit/ui/testrunnermediator.rb +68 -0
  23. data/lib/test/unit/ui/testrunnerutilities.rb +46 -0
  24. data/lib/test/unit/ui/tk/testrunner.rb +260 -0
  25. data/lib/test/unit/util/backtracefilter.rb +40 -0
  26. data/lib/test/unit/util/observable.rb +90 -0
  27. data/lib/test/unit/util/procwrapper.rb +48 -0
  28. data/lib/test/unit/version.rb +7 -0
  29. data/sample/adder.rb +13 -0
  30. data/sample/subtracter.rb +12 -0
  31. data/sample/tc_adder.rb +18 -0
  32. data/sample/tc_subtracter.rb +18 -0
  33. data/sample/ts_examples.rb +7 -0
  34. data/test/collector/test_dir.rb +406 -0
  35. data/test/collector/test_objectspace.rb +98 -0
  36. data/test/runit/test_assert.rb +402 -0
  37. data/test/runit/test_testcase.rb +91 -0
  38. data/test/runit/test_testresult.rb +144 -0
  39. data/test/runit/test_testsuite.rb +49 -0
  40. data/test/test_assertions.rb +528 -0
  41. data/test/test_error.rb +26 -0
  42. data/test/test_failure.rb +33 -0
  43. data/test/test_testcase.rb +275 -0
  44. data/test/test_testresult.rb +104 -0
  45. data/test/test_testsuite.rb +129 -0
  46. data/test/util/test_backtracefilter.rb +41 -0
  47. data/test/util/test_observable.rb +102 -0
  48. data/test/util/test_procwrapper.rb +36 -0
  49. metadata +128 -0
@@ -0,0 +1,68 @@
1
+ #--
2
+ #
3
+ # Author:: Nathaniel Talbott.
4
+ # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
5
+ # License:: Ruby license.
6
+
7
+ require 'test/unit'
8
+ require 'test/unit/util/observable'
9
+ require 'test/unit/testresult'
10
+
11
+ module Test
12
+ module Unit
13
+ module UI
14
+
15
+ # Provides an interface to write any given UI against,
16
+ # hopefully making it easy to write new UIs.
17
+ class TestRunnerMediator
18
+ RESET = name + "::RESET"
19
+ STARTED = name + "::STARTED"
20
+ FINISHED = name + "::FINISHED"
21
+
22
+ include Util::Observable
23
+
24
+ # Creates a new TestRunnerMediator initialized to run
25
+ # the passed suite.
26
+ def initialize(suite)
27
+ @suite = suite
28
+ end
29
+
30
+ # Runs the suite the TestRunnerMediator was created
31
+ # with.
32
+ def run_suite
33
+ Unit.run = true
34
+ begin_time = Time.now
35
+ notify_listeners(RESET, @suite.size)
36
+ result = create_result
37
+ notify_listeners(STARTED, result)
38
+ result_listener = result.add_listener(TestResult::CHANGED) do |updated_result|
39
+ notify_listeners(TestResult::CHANGED, updated_result)
40
+ end
41
+
42
+ fault_listener = result.add_listener(TestResult::FAULT) do |fault|
43
+ notify_listeners(TestResult::FAULT, fault)
44
+ end
45
+
46
+ @suite.run(result) do |channel, value|
47
+ notify_listeners(channel, value)
48
+ end
49
+
50
+ result.remove_listener(TestResult::FAULT, fault_listener)
51
+ result.remove_listener(TestResult::CHANGED, result_listener)
52
+ end_time = Time.now
53
+ elapsed_time = end_time - begin_time
54
+ notify_listeners(FINISHED, elapsed_time) #"Finished in #{elapsed_time} seconds.")
55
+ return result
56
+ end
57
+
58
+ private
59
+ # A factory method to create the result the mediator
60
+ # should run with. Can be overridden by subclasses if
61
+ # one wants to use a different result.
62
+ def create_result
63
+ return TestResult.new
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,46 @@
1
+ #--
2
+ #
3
+ # Author:: Nathaniel Talbott.
4
+ # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
5
+ # License:: Ruby license.
6
+
7
+ module Test
8
+ module Unit
9
+ module UI
10
+
11
+ SILENT = 0
12
+ PROGRESS_ONLY = 1
13
+ NORMAL = 2
14
+ VERBOSE = 3
15
+
16
+ # Provides some utilities common to most, if not all,
17
+ # TestRunners.
18
+ #
19
+ #--
20
+ #
21
+ # Perhaps there ought to be a TestRunner superclass? There
22
+ # seems to be a decent amount of shared code between test
23
+ # runners.
24
+
25
+ module TestRunnerUtilities
26
+
27
+ # Creates a new TestRunner and runs the suite.
28
+ def run(suite, output_level=NORMAL)
29
+ return new(suite, output_level).start
30
+ end
31
+
32
+ # Takes care of the ARGV parsing and suite
33
+ # determination necessary for running one of the
34
+ # TestRunners from the command line.
35
+ def start_command_line_test
36
+ if ARGV.empty?
37
+ puts "You should supply the name of a test suite file to the runner"
38
+ exit
39
+ end
40
+ require ARGV[0].gsub(/.+::/, '')
41
+ new(eval(ARGV[0])).start
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,260 @@
1
+ #--
2
+ #
3
+ # Original Author:: Nathaniel Talbott.
4
+ # Author:: Kazuhiro NISHIYAMA.
5
+ # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
6
+ # Copyright:: Copyright (c) 2003 Kazuhiro NISHIYAMA. All rights reserved.
7
+ # License:: Ruby license.
8
+
9
+ require 'tk'
10
+ require 'test/unit/ui/testrunnermediator'
11
+ require 'test/unit/ui/testrunnerutilities'
12
+
13
+ module Test
14
+ module Unit
15
+ module UI
16
+ module Tk
17
+
18
+ # Runs a Test::Unit::TestSuite in a Tk UI. Obviously,
19
+ # this one requires you to have Tk
20
+ # and the Ruby Tk extension installed.
21
+ class TestRunner
22
+ extend TestRunnerUtilities
23
+
24
+ # Creates a new TestRunner for running the passed
25
+ # suite.
26
+ def initialize(suite, output_level = NORMAL)
27
+ if (suite.respond_to?(:suite))
28
+ @suite = suite.suite
29
+ else
30
+ @suite = suite
31
+ end
32
+ @result = nil
33
+
34
+ @red = false
35
+ @fault_detail_list = []
36
+ @runner = Thread.current
37
+ @restart_signal = Class.new(Exception)
38
+ @viewer = Thread.start do
39
+ @runner.join rescue @runner.run
40
+ ::Tk.mainloop
41
+ end
42
+ @viewer.join rescue nil # wait deadlock to handshake
43
+ end
44
+
45
+ # Begins the test run.
46
+ def start
47
+ setup_ui
48
+ setup_mediator
49
+ attach_to_mediator
50
+ start_ui
51
+ @result
52
+ end
53
+
54
+ private
55
+ def setup_mediator
56
+ @mediator = TestRunnerMediator.new(@suite)
57
+ suite_name = @suite.to_s
58
+ if ( @suite.kind_of?(Module) )
59
+ suite_name = @suite.name
60
+ end
61
+ @suite_name_entry.value = suite_name
62
+ end
63
+
64
+ def attach_to_mediator
65
+ @run_button.command(method(:run_test))
66
+ @fault_list.bind('ButtonPress-1', proc{|y|
67
+ fault = @fault_detail_list[@fault_list.nearest(y)]
68
+ if fault
69
+ show_fault(fault)
70
+ end
71
+ }, '%y')
72
+ @mediator.add_listener(TestRunnerMediator::RESET, &method(:reset_ui))
73
+ @mediator.add_listener(TestResult::FAULT, &method(:add_fault))
74
+ @mediator.add_listener(TestResult::CHANGED, &method(:result_changed))
75
+ @mediator.add_listener(TestRunnerMediator::STARTED, &method(:started))
76
+ @mediator.add_listener(TestCase::STARTED, &method(:test_started))
77
+ @mediator.add_listener(TestRunnerMediator::FINISHED, &method(:finished))
78
+ end
79
+
80
+ def run_test
81
+ @runner.raise(@restart_signal)
82
+ end
83
+
84
+ def start_ui
85
+ @viewer.run
86
+ running = false
87
+ begin
88
+ loop do
89
+ if (running ^= true)
90
+ @run_button.configure('text'=>'Stop')
91
+ @mediator.run_suite
92
+ else
93
+ @run_button.configure('text'=>'Run')
94
+ @viewer.join
95
+ break
96
+ end
97
+ end
98
+ rescue @restart_signal
99
+ retry
100
+ rescue
101
+ end
102
+ end
103
+
104
+ def stop
105
+ ::Tk.exit
106
+ end
107
+
108
+ def reset_ui(count)
109
+ @test_total_count = count.to_f
110
+ @test_progress_bar.configure('background'=>'green')
111
+ @test_progress_bar.place('relwidth'=>(count.zero? ? 0 : 0/count))
112
+ @red = false
113
+
114
+ @test_count_label.value = 0
115
+ @assertion_count_label.value = 0
116
+ @failure_count_label.value = 0
117
+ @error_count_label.value = 0
118
+
119
+ @fault_list.delete(0, 'end')
120
+ @fault_detail_list = []
121
+ clear_fault
122
+ end
123
+
124
+ def add_fault(fault)
125
+ if ( ! @red )
126
+ @test_progress_bar.configure('background'=>'red')
127
+ @red = true
128
+ end
129
+ @fault_detail_list.push fault
130
+ @fault_list.insert('end', fault.short_display)
131
+ end
132
+
133
+ def show_fault(fault)
134
+ raw_show_fault(fault.long_display)
135
+ end
136
+
137
+ def raw_show_fault(string)
138
+ @detail_text.value = string
139
+ end
140
+
141
+ def clear_fault
142
+ raw_show_fault("")
143
+ end
144
+
145
+ def result_changed(result)
146
+ @test_count_label.value = result.run_count
147
+ @test_progress_bar.place('relwidth'=>result.run_count/@test_total_count)
148
+ @assertion_count_label.value = result.assertion_count
149
+ @failure_count_label.value = result.failure_count
150
+ @error_count_label.value = result.error_count
151
+ end
152
+
153
+ def started(result)
154
+ @result = result
155
+ output_status("Started...")
156
+ end
157
+
158
+ def test_started(test_name)
159
+ output_status("Running #{test_name}...")
160
+ end
161
+
162
+ def finished(elapsed_time)
163
+ output_status("Finished in #{elapsed_time} seconds")
164
+ end
165
+
166
+ def output_status(string)
167
+ @status_entry.value = string
168
+ end
169
+
170
+ def setup_ui
171
+ @status_entry = TkVariable.new
172
+ l = TkLabel.new(nil, 'textvariable'=>@status_entry, 'relief'=>'sunken')
173
+ l.pack('side'=>'bottom', 'fill'=>'x')
174
+
175
+ suite_frame = TkFrame.new.pack('fill'=>'x')
176
+
177
+ @run_button = TkButton.new(suite_frame, 'text'=>'Run')
178
+ @run_button.pack('side'=>'right')
179
+
180
+ TkLabel.new(suite_frame, 'text'=>'Suite:').pack('side'=>'left')
181
+ @suite_name_entry = TkVariable.new
182
+ l = TkLabel.new(suite_frame, 'textvariable'=>@suite_name_entry, 'relief'=>'sunken')
183
+ l.pack('side'=>'left', 'fill'=>'x', 'expand'=>true)
184
+
185
+ f = TkFrame.new(nil, 'relief'=>'sunken', 'borderwidth'=>3, 'height'=>20).pack('fill'=>'x', 'padx'=>1)
186
+ @test_progress_bar = TkFrame.new(f, 'background'=>'green').place('anchor'=>'nw', 'relwidth'=>0.0, 'relheight'=>1.0)
187
+
188
+ info_frame = TkFrame.new.pack('fill'=>'x')
189
+ @test_count_label = create_count_label(info_frame, 'Tests:')
190
+ @assertion_count_label = create_count_label(info_frame, 'Assertions:')
191
+ @failure_count_label = create_count_label(info_frame, 'Failures:')
192
+ @error_count_label = create_count_label(info_frame, 'Errors:')
193
+
194
+ if (::Tk.info('command', TkPanedWindow::TkCommandNames[0]) != "")
195
+ # use panedwindow
196
+ paned_frame = TkPanedWindow.new("orient"=>"vertical").pack('fill'=>'both', 'expand'=>true)
197
+
198
+ fault_list_frame = TkFrame.new(paned_frame)
199
+ detail_frame = TkFrame.new(paned_frame)
200
+
201
+ paned_frame.add(fault_list_frame, detail_frame)
202
+ else
203
+ # no panedwindow
204
+ paned_frame = nil
205
+ fault_list_frame = TkFrame.new.pack('fill'=>'both', 'expand'=>true)
206
+ detail_frame = TkFrame.new.pack('fill'=>'both', 'expand'=>true)
207
+ end
208
+
209
+ TkGrid.rowconfigure(fault_list_frame, 0, 'weight'=>1, 'minsize'=>0)
210
+ TkGrid.columnconfigure(fault_list_frame, 0, 'weight'=>1, 'minsize'=>0)
211
+
212
+ fault_scrollbar_y = TkScrollbar.new(fault_list_frame)
213
+ fault_scrollbar_x = TkScrollbar.new(fault_list_frame)
214
+ @fault_list = TkListbox.new(fault_list_frame)
215
+ @fault_list.yscrollbar(fault_scrollbar_y)
216
+ @fault_list.xscrollbar(fault_scrollbar_x)
217
+
218
+ TkGrid.rowconfigure(detail_frame, 0, 'weight'=>1, 'minsize'=>0)
219
+ TkGrid.columnconfigure(detail_frame, 0, 'weight'=>1, 'minsize'=>0)
220
+
221
+ ::Tk.grid(@fault_list, fault_scrollbar_y, 'sticky'=>'news')
222
+ ::Tk.grid(fault_scrollbar_x, 'sticky'=>'news')
223
+
224
+ detail_scrollbar_y = TkScrollbar.new(detail_frame)
225
+ detail_scrollbar_x = TkScrollbar.new(detail_frame)
226
+ @detail_text = TkText.new(detail_frame, 'height'=>10, 'wrap'=>'none') {
227
+ bindtags(bindtags - [TkText])
228
+ }
229
+ @detail_text.yscrollbar(detail_scrollbar_y)
230
+ @detail_text.xscrollbar(detail_scrollbar_x)
231
+
232
+ ::Tk.grid(@detail_text, detail_scrollbar_y, 'sticky'=>'news')
233
+ ::Tk.grid(detail_scrollbar_x, 'sticky'=>'news')
234
+
235
+ # rubber-style pane
236
+ if paned_frame
237
+ ::Tk.update
238
+ @height = paned_frame.winfo_height
239
+ paned_frame.bind('Configure', proc{|h|
240
+ paned_frame.sash_place(0, 0, paned_frame.sash_coord(0)[1] * h / @height)
241
+ @height = h
242
+ }, '%h')
243
+ end
244
+ end
245
+
246
+ def create_count_label(parent, label)
247
+ TkLabel.new(parent, 'text'=>label).pack('side'=>'left', 'expand'=>true)
248
+ v = TkVariable.new(0)
249
+ TkLabel.new(parent, 'textvariable'=>v).pack('side'=>'left', 'expand'=>true)
250
+ v
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ if __FILE__ == $0
259
+ Test::Unit::UI::Tk::TestRunner.start_command_line_test
260
+ end
@@ -0,0 +1,40 @@
1
+ module Test
2
+ module Unit
3
+ module Util
4
+ module BacktraceFilter
5
+ TESTUNIT_FILE_SEPARATORS = %r{[\\/:]}
6
+ TESTUNIT_PREFIX = __FILE__.split(TESTUNIT_FILE_SEPARATORS)[0..-3]
7
+ TESTUNIT_RB_FILE = /\.rb\Z/
8
+
9
+ def filter_backtrace(backtrace, prefix=nil)
10
+ return ["No backtrace"] unless(backtrace)
11
+ split_p = if(prefix)
12
+ prefix.split(TESTUNIT_FILE_SEPARATORS)
13
+ else
14
+ TESTUNIT_PREFIX
15
+ end
16
+ match = proc do |e|
17
+ split_e = e.split(TESTUNIT_FILE_SEPARATORS)[0, split_p.size]
18
+ next false unless(split_e[0..-2] == split_p[0..-2])
19
+ split_e[-1].sub(TESTUNIT_RB_FILE, '') == split_p[-1]
20
+ end
21
+ return backtrace unless(backtrace.detect(&match))
22
+ found_prefix = false
23
+ new_backtrace = backtrace.reverse.reject do |e|
24
+ if(match[e])
25
+ found_prefix = true
26
+ true
27
+ elsif(found_prefix)
28
+ false
29
+ else
30
+ true
31
+ end
32
+ end.reverse
33
+ new_backtrace = (new_backtrace.empty? ? backtrace : new_backtrace)
34
+ new_backtrace = new_backtrace.reject(&match)
35
+ new_backtrace.empty? ? backtrace : new_backtrace
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,90 @@
1
+ #--
2
+ #
3
+ # Author:: Nathaniel Talbott.
4
+ # Copyright:: Copyright (c) 2000-2002 Nathaniel Talbott. All rights reserved.
5
+ # License:: Ruby license.
6
+
7
+ require 'test/unit/util/procwrapper'
8
+
9
+ module Test
10
+ module Unit
11
+ module Util
12
+
13
+ # This is a utility class that allows anything mixing
14
+ # it in to notify a set of listeners about interesting
15
+ # events.
16
+ module Observable
17
+ # We use this for defaults since nil might mean something
18
+ NOTHING = "NOTHING/#{__id__}"
19
+
20
+ # Adds the passed proc as a listener on the
21
+ # channel indicated by channel_name. listener_key
22
+ # is used to remove the listener later; if none is
23
+ # specified, the proc itself is used.
24
+ #
25
+ # Whatever is used as the listener_key is
26
+ # returned, making it very easy to use the proc
27
+ # itself as the listener_key:
28
+ #
29
+ # listener = add_listener("Channel") { ... }
30
+ # remove_listener("Channel", listener)
31
+ def add_listener(channel_name, listener_key=NOTHING, &listener) # :yields: value
32
+ unless(block_given?)
33
+ raise ArgumentError.new("No callback was passed as a listener")
34
+ end
35
+
36
+ key = listener_key
37
+ if (listener_key == NOTHING)
38
+ listener_key = listener
39
+ key = ProcWrapper.new(listener)
40
+ end
41
+
42
+ channels[channel_name] ||= {}
43
+ channels[channel_name][key] = listener
44
+ return listener_key
45
+ end
46
+
47
+ # Removes the listener indicated by listener_key
48
+ # from the channel indicated by
49
+ # channel_name. Returns the registered proc, or
50
+ # nil if none was found.
51
+ def remove_listener(channel_name, listener_key)
52
+ channel = channels[channel_name]
53
+ return nil unless (channel)
54
+ key = listener_key
55
+ if (listener_key.instance_of?(Proc))
56
+ key = ProcWrapper.new(listener_key)
57
+ end
58
+ if (channel.has_key?(key))
59
+ return channel.delete(key)
60
+ end
61
+ return nil
62
+ end
63
+
64
+ # Calls all the procs registered on the channel
65
+ # indicated by channel_name. If value is
66
+ # specified, it is passed in to the procs,
67
+ # otherwise they are called with no arguments.
68
+ #
69
+ #--
70
+ #
71
+ # Perhaps this should be private? Would it ever
72
+ # make sense for an external class to call this
73
+ # method directly?
74
+ def notify_listeners(channel_name, *arguments)
75
+ channel = channels[channel_name]
76
+ return 0 unless (channel)
77
+ listeners = channel.values
78
+ listeners.each { |listener| listener.call(*arguments) }
79
+ return listeners.size
80
+ end
81
+
82
+ private
83
+ def channels
84
+ @channels ||= {}
85
+ return @channels
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end