test-unit 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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