rubytest 0.3.0

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.
@@ -0,0 +1,30 @@
1
+ class String
2
+
3
+ # Preserves relative tabbing.
4
+ # The first non-empty line ends up with n spaces before nonspace.
5
+ #
6
+ # This is a Ruby Facet (http://rubyworks.github.com/facets).
7
+
8
+ def tabto(n)
9
+ if self =~ /^( *)\S/
10
+ indent(n - $1.length)
11
+ else
12
+ self
13
+ end
14
+ end unless method_defined?(:tabto)
15
+
16
+ # Indent left or right by n spaces.
17
+ # (This used to be called #tab and aliased as #indent.)
18
+ #
19
+ # This is a Ruby Facet (http://rubyworks.github.com/facets).
20
+
21
+ def indent(n, c=' ')
22
+ if n >= 0
23
+ gsub(/^/, c * n)
24
+ else
25
+ gsub(/^#{Regexp.escape(c)}{0,#{-n}}/, "")
26
+ end
27
+ end unless method_defined?(:indent)
28
+
29
+ end
30
+
@@ -0,0 +1,9 @@
1
+ if RUBY_VERSION < '1.9'
2
+ require 'ruth/core_ext/assertion'
3
+ require 'ruth/core_ext/exception'
4
+ require 'ruth/core_ext/string'
5
+ else
6
+ require_relative 'core_ext/assertion'
7
+ require_relative 'core_ext/exception'
8
+ require_relative 'core_ext/string'
9
+ end
@@ -0,0 +1,124 @@
1
+ require 'rake/tasklib'
2
+
3
+ module Ruth
4
+
5
+ #
6
+ module Rake
7
+
8
+ # TODO: The test task uses #fork. Maybe it should shell out instead?
9
+ # Or provide the option for either?
10
+
11
+ # Define a test rake task.
12
+ #
13
+ # The `TEST` environment variable can be used to select tests
14
+ # when using the task.
15
+ #
16
+ class TestTask < ::Rake::TaskLib
17
+
18
+ # Glob patterns are used by default to select test scripts.
19
+ DEFAULT_TESTS = [
20
+ 'test/**/case_*.rb',
21
+ 'test/**/*_case.rb',
22
+ 'test/**/test_*.rb',
23
+ 'test/**/*_test.rb'
24
+ ]
25
+
26
+ # Test scripts to load. Can be a file glob.
27
+ attr_accessor :tests
28
+
29
+ # Paths to add to $LOAD_PATH.
30
+ attr_accessor :loadpath
31
+
32
+ # Scripts to load prior to loading tests.
33
+ attr_accessor :requires
34
+
35
+ # Report format to use.
36
+ attr_accessor :format
37
+
38
+ # Filter tests based by tags.
39
+ attr_accessor :tags
40
+
41
+ # Filter tests by matching description.
42
+ attr_accessor :match
43
+
44
+ # From Rake's own TestTask.
45
+ alias_method :libs, :loadpath
46
+ alias_method :test_files, :tests
47
+
48
+ #
49
+ def initialize(name='test', desc="run tests", &block)
50
+ @name = name
51
+ @desc = desc
52
+
53
+ @loadpath = ['lib']
54
+ @requires = []
55
+ @tests = [ENV['TEST'] || DEFAULT_TESTS].flatten
56
+ @format = nil
57
+ @match = nil
58
+ @tags = []
59
+
60
+ block.call(self)
61
+
62
+ define_task
63
+ end
64
+
65
+ #
66
+ def define_task
67
+ desc @desc
68
+ task @name do
69
+ @tests ||= default_tests
70
+ run
71
+ end
72
+ end
73
+
74
+ #
75
+ def run
76
+ fork {
77
+ #require 'test'
78
+ require 'test/runner'
79
+
80
+ loadpath.each { |d| $LOAD_PATH.unshift(d) }
81
+ requires.each { |f| require f }
82
+ test_files.each { |f| require f }
83
+
84
+ suite = $TEST_SUITE || []
85
+ runner = new(suite, :format=>format, :tags=>tags, :match=>match)
86
+ success = runner.run
87
+
88
+ exit -1 unless success
89
+ }
90
+ Process.wait
91
+ end
92
+
93
+ # Resolve test globs.
94
+ #--
95
+ # TODO: simplify?
96
+ #++
97
+ def test_files
98
+ files = tests
99
+ files = files.map{ |f| Dir[f] }.flatten
100
+ files = files.map{ |f| File.directory?(f) ? Dir[File.join(f, '**/*.rb')] : f }
101
+ files = files.flatten.uniq
102
+ files = files.map{ |f| File.expand_path(f) }
103
+ files
104
+ end
105
+
106
+ #
107
+ def default_tests
108
+ if ENV['tests']
109
+ ENV['tests'].split(/[:;]/)
110
+ else
111
+ DEFAULT_TESTS
112
+ end
113
+ end
114
+
115
+ #
116
+ #def ruby_command
117
+ # File.join(RbConfig::CONFIG['bindir'], Config::CONFIG['ruby_install_name'])
118
+ #end
119
+
120
+ end
121
+
122
+ end
123
+
124
+ end
@@ -0,0 +1,53 @@
1
+ module Test
2
+
3
+ # Recorder class is an observer that tracks all tests
4
+ # that are run and categorizes them according to their
5
+ # test status.
6
+ class Recorder
7
+
8
+ def initialize
9
+ @table = Hash.new{ |h,k| h[k] = [] }
10
+ end
11
+
12
+ def [](key)
13
+ @table[key.to_sym]
14
+ end
15
+
16
+ #
17
+ def skip(test)
18
+ self[:skip] << test
19
+ end
20
+
21
+ # Add `test` to pass set.
22
+ def pass(test)
23
+ self[:pass] << test
24
+ end
25
+
26
+ def fail(test, exception)
27
+ self[:fail] << [test, exception]
28
+ end
29
+
30
+ def error(test, exception)
31
+ self[:error] << [test, exception]
32
+ end
33
+
34
+ def todo(test, exception)
35
+ self[:todo] << [test, exception]
36
+ end
37
+
38
+ def omit(test, exception)
39
+ self[:omit] << [test, exception]
40
+ end
41
+
42
+ # Returns true if their are no test errors or failures.
43
+ def success?
44
+ self[:error].size + self[:fail].size > 0 ? false : true
45
+ end
46
+
47
+ # Ignore any other signals.
48
+ def method_missing(*a)
49
+ end
50
+
51
+ end
52
+
53
+ end
@@ -0,0 +1,261 @@
1
+ # encoding: UTF-8
2
+
3
+ module Test
4
+
5
+ #
6
+ module Reporters
7
+
8
+ # Test Reporter Base Class
9
+ class Abstract
10
+
11
+ #
12
+ def self.inherited(base)
13
+ registry << base
14
+ end
15
+
16
+ #
17
+ def self.registry
18
+ @registry ||= []
19
+ end
20
+
21
+ #
22
+ def initialize(runner)
23
+ @runner = runner
24
+ #@source = {}
25
+
26
+ # in case start_suite is overridden
27
+ @start_time = Time.now
28
+ end
29
+
30
+ #
31
+ attr :runner
32
+
33
+ #
34
+ def begin_suite(test_suite)
35
+ @start_time = Time.now
36
+ end
37
+
38
+ #
39
+ def begin_case(test_case)
40
+ end
41
+
42
+ #
43
+ def begin_test(test)
44
+ end
45
+
46
+ #
47
+ def skip_case(test_case)
48
+ end
49
+
50
+ #
51
+ def skip_test(test)
52
+ end
53
+
54
+ #
55
+ #def test(test)
56
+ #end
57
+
58
+ #
59
+ def pass(test)
60
+ end
61
+
62
+ #
63
+ def fail(test, exception)
64
+ end
65
+
66
+ #
67
+ def error(test, exception)
68
+ end
69
+
70
+ # Report a pending test.
71
+ def todo(test, exception)
72
+ end
73
+
74
+ # Report an omitted test.
75
+ def omit(test, exception)
76
+ end
77
+
78
+ #
79
+ def end_test(test)
80
+ end
81
+
82
+ #
83
+ def end_case(test_case)
84
+ end
85
+
86
+ #
87
+ def end_suite(test_suite)
88
+ end
89
+
90
+ protected
91
+
92
+ def record
93
+ runner.recorder
94
+ end
95
+
96
+ # Is coverage information requested?
97
+ #def cover?
98
+ # runner.cover?
99
+ #end
100
+
101
+ # Count up the total number of tests.
102
+ def total_count(suite)
103
+ c = 0
104
+ suite.each do |tc|
105
+ if tc.respond_to?(:each)
106
+ c += total_count(tc)
107
+ else
108
+ c += 1
109
+ end
110
+ end
111
+ return c
112
+ end
113
+
114
+ # Common timestamp any reporter can use.
115
+ def timestamp
116
+ seconds = Time.now - @start_time
117
+
118
+ "Finished in %.5fs, %.2f tests/s." % [seconds, total/seconds]
119
+ end
120
+
121
+ #
122
+ def total
123
+ @total ||= subtotal
124
+ end
125
+
126
+ #
127
+ def subtotal
128
+ [:todo, :pass, :fail, :error, :omit, :skip].inject(0) do |s,r|
129
+ s += record[r.to_sym].size; s
130
+ end
131
+ end
132
+
133
+ # TODO: lump skipped and omitted into one group ?
134
+
135
+ TITLES = {
136
+ :pass => 'passing',
137
+ :fail => 'failures',
138
+ :error => 'errors',
139
+ :todo => 'pending',
140
+ :omit => 'omissions',
141
+ :skip => 'skipped'
142
+ }
143
+
144
+ # TODO: Add assertion counts (if reasonably possible).
145
+
146
+ # Common tally stamp any reporter can use.
147
+ #
148
+ # @return [String] tally stamp
149
+ def tally
150
+ sizes = {}
151
+ names = %w{pass error fail todo omit skip}.map{ |n| n.to_sym }
152
+ names.each do |r|
153
+ sizes[r] = record[r].size
154
+ end
155
+
156
+ #names.unshift(:tests)
157
+ #sizes[:tests] = total
158
+
159
+ s = []
160
+ names.each do |n|
161
+ next unless sizes[n] > 0
162
+ s << tally_item(n, sizes)
163
+ end
164
+
165
+ 'Executed ' + "#{total}".ansi(:bold) + ' tests with ' + s.join(', ') + '.'
166
+ end
167
+
168
+ #
169
+ def tally_item(name, sizes)
170
+ x = []
171
+ x << "%s" % sizes[name].to_s.ansi(:bold)
172
+ x << " %s" % TITLES[name].downcase
173
+ x << " (%.1f%%)" % ((sizes[name].to_f/total*100)) if runner.verbose?
174
+ x.join('')
175
+ end
176
+
177
+ #--
178
+ # TODO: Matching `bin/ruby-test` is not robust.
179
+ #++
180
+
181
+ # Remove reference to lemon library from backtrace.
182
+ #
183
+ # @param [Exception] exception
184
+ # The error that was rasied.
185
+ #
186
+ # @return [Array] filtered backtrace
187
+ def clean_backtrace(exception)
188
+ trace = (Exception === exception ? exception.backtrace : exception)
189
+ return trace if $DEBUG
190
+ trace = trace.reject{ |t| RUBY_IGNORE_CALLERS.any?{ |r| r =~ t }}
191
+ trace = trace.map do |t|
192
+ i = t.index(':in')
193
+ i ? t[0...i] : t
194
+ end
195
+ #if trace.empty?
196
+ # exception
197
+ #else
198
+ # exception.set_backtrace(trace) if Exception === exception
199
+ # exception
200
+ #end
201
+ trace.uniq
202
+ end
203
+
204
+ # That an exception, backtrace or source code text and line
205
+ # number and return a CodeSnippet object.
206
+ #
207
+ # @return [CodeSnippet] code snippet
208
+ def code(source, line=nil)
209
+ case source
210
+ when Exception
211
+ CodeSnippet.from_backtrace(clean_backtrace(source.backtrace))
212
+ when Array
213
+ CodeSnippet.from_backtrace(clean_backtrace(source))
214
+ else
215
+ CodeSnippet.new(source, line)
216
+ end
217
+ end
218
+
219
+ #--
220
+ # TODO: Show more of the file name than just the basename.
221
+ #++
222
+
223
+ #
224
+ def file_and_line(exception)
225
+ line = clean_backtrace(exception)[0]
226
+ return "" unless line
227
+ i = line.rindex(':in')
228
+ line = i ? line[0...i] : line
229
+ File.basename(line)
230
+ end
231
+
232
+ #
233
+ def file_and_line_array(exception)
234
+ case exception
235
+ when Exception
236
+ line = exception.backtrace[0]
237
+ else
238
+ line = exception[0] # backtrace
239
+ end
240
+ return ["", 0] unless line
241
+ i = line.rindex(':in')
242
+ line = i ? line[0...i] : line
243
+ f, l = File.basename(line).split(':')
244
+ return [f, l.to_i]
245
+ end
246
+
247
+ #
248
+ def file(exception)
249
+ file_and_line_array(exception).first
250
+ end
251
+
252
+ #
253
+ def line(exception)
254
+ file_and_line_array(exception).last
255
+ end
256
+
257
+ end
258
+
259
+ end
260
+
261
+ end
@@ -0,0 +1,224 @@
1
+ # encoding: UTF-8
2
+
3
+ module Test::Reporters
4
+
5
+ # Hash Abstract is a base class for the TAP-Y
6
+ # and TAP-J reporters.
7
+ #
8
+ class AbstractHash < Abstract
9
+
10
+ #
11
+ def begin_suite(suite)
12
+ require 'yaml'
13
+
14
+ @start_time = Time.now
15
+ @case_level = 0
16
+ @test_index = 0
17
+
18
+ now = Time.now.strftime('%Y-%m-%d %H:%M:%S')
19
+
20
+ h = {
21
+ 'type' => 'suite',
22
+ 'start' => now,
23
+ 'count' => total_count(suite)
24
+ }
25
+
26
+ return h
27
+ end
28
+
29
+ #
30
+ def begin_case(test_case)
31
+ h = {}
32
+ h['type' ] = 'case'
33
+ h['level'] = @case_level
34
+
35
+ merge_subtype h, test_case
36
+ merge_setup h, test_case
37
+ merge_label h, test_case
38
+
39
+ @case_level += 1
40
+
41
+ return h
42
+ end
43
+
44
+ #
45
+ def begin_test(test)
46
+ @test_index += 1
47
+ end
48
+
49
+ #
50
+ def pass(test) #, backtrace=nil)
51
+ h = {}
52
+ h['type' ] = 'test'
53
+ h['status'] = 'pass'
54
+
55
+ merge_subtype h, test
56
+ merge_setup h, test
57
+ merge_label h, test
58
+ #merge_comparison h, test, exception
59
+ #merge_coverage h, test
60
+ merge_source h, test
61
+ merge_time h
62
+
63
+ return h
64
+ end
65
+
66
+ #
67
+ def fail(test, exception)
68
+ h = {}
69
+ h['type' ] = 'test'
70
+ h['status'] = 'fail'
71
+
72
+ merge_subtype h, test
73
+ merge_setup h, test
74
+ merge_label h, test
75
+ #merge_comparison h, test, exception
76
+ #merge_coverage h, test
77
+ merge_source h, test
78
+ merge_exception h, test, exception
79
+ merge_time h
80
+
81
+ return h
82
+ end
83
+
84
+ #
85
+ def error(test, exception)
86
+ h = {}
87
+ h['type' ] = 'test'
88
+ h['status'] = 'error'
89
+
90
+ merge_subtype h, test
91
+ merge_setup h, test
92
+ merge_label h, test
93
+ #merge_comparison h, test, exception
94
+ #merge_coverage h, test
95
+ merge_source h, test
96
+ merge_exception h, test, exception, true
97
+ merge_time h
98
+
99
+ return h
100
+ end
101
+
102
+ #
103
+ def todo(test, exception)
104
+ h = {}
105
+ h['type' ] = 'test'
106
+ h['status'] = 'todo'
107
+
108
+ merge_subtype h, test
109
+ merge_setup h, test
110
+ merge_label h, test
111
+ #merge_comparison h, test, exception
112
+ #merge_coverage h, test
113
+ merge_source h, test
114
+ merge_exception h, test, exception
115
+ merge_time h
116
+
117
+ return h
118
+ end
119
+
120
+ #
121
+ def omit(test, exception)
122
+ h = {}
123
+ h['type' ] = 'test'
124
+ h['status'] = 'omit'
125
+
126
+ merge_subtype h, test
127
+ merge_setup h, test
128
+ merge_label h, test
129
+ #merge_comparison h, test, exception
130
+ #merge_coverage h, test
131
+ merge_source h, test
132
+ merge_exception h, test, exception
133
+ merge_time h
134
+
135
+ return h
136
+ end
137
+
138
+ #
139
+ def end_case(test_case)
140
+ @case_level -= 1
141
+ end
142
+
143
+ #
144
+ def end_suite(suite)
145
+ h = {
146
+ 'type' => 'tally',
147
+ 'time' => Time.now - @start_time,
148
+ 'counts' => {
149
+ 'total' => total,
150
+ 'pass' => record[:pass].size,
151
+ 'fail' => record[:fail].size,
152
+ 'error' => record[:error].size,
153
+ 'omit' => record[:omit].size,
154
+ 'todo' => record[:todo].size
155
+ }
156
+ }
157
+ return h
158
+ end
159
+
160
+ private
161
+
162
+ #
163
+ def merge_subtype(hash, test)
164
+ hash['subtype'] = test.type.to_s if test.respond_to?(:type)
165
+ end
166
+
167
+ # TODO: topic or setup ?
168
+ def merge_setup(hash, test)
169
+ #hash['setup'] = test.setup.to_s if test.respond_to?(:setup)
170
+ hash['setup'] = test.topic.to_s if test.respond_to?(:topic)
171
+ end
172
+
173
+ # Add test description to hash.
174
+ def merge_label(hash, test)
175
+ hash['label'] = test.to_s.strip
176
+ end
177
+
178
+ # NOTE: Not presently used.
179
+ def merge_comparison(hash, test, exception)
180
+ hash['returned'] = exception.returned
181
+ hash['expected'] = exception.expected
182
+ end
183
+
184
+ # Add source location information to hash.
185
+ def merge_source(hash, test)
186
+ if test.respond_to?('source_location')
187
+ file, line = source_location
188
+ hash['file' ] = file
189
+ hash['line' ] = line
190
+ hash['source' ] = code(file, line).to_str
191
+ hash['snippet'] = code(file, line).to_omap
192
+ end
193
+ end
194
+
195
+ # Add exception subsection of hash.
196
+ def merge_exception(hash, test, exception, bt=false)
197
+ hash['exception'] = {}
198
+ hash['exception']['file' ] = code(exception).file
199
+ hash['exception']['line' ] = code(exception).line
200
+ hash['exception']['source' ] = code(exception).to_str
201
+ hash['exception']['snippet' ] = code(exception).to_omap
202
+ hash['exception']['message' ] = exception.message
203
+ hash['exception']['backtrace'] = clean_backtrace(exception) if bt
204
+ end
205
+
206
+ # TODO: Really?
207
+ def merge_coverage(hash, test)
208
+ if test.respond_to?(:file_coverage) or test.respond_to?(:code_coverage)
209
+ fc = test.file_coverage
210
+ cc = test.code_coverage
211
+ hash['coverage'] = {}
212
+ hash['coverage']['file'] = fc if fc
213
+ hash['coverage']['code'] = cc if cc
214
+ end
215
+ end
216
+
217
+ #
218
+ def merge_time(hash)
219
+ hash['time'] = Time.now - @start_time
220
+ end
221
+
222
+ end
223
+
224
+ end