turn 0.8.2 → 0.9.7

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 (52) hide show
  1. data/CONTRIBUTING.md +40 -0
  2. data/History.txt +81 -1
  3. data/Index.rb +34 -0
  4. data/LICENSE.txt +21 -0
  5. data/NOTICE.md +12 -0
  6. data/README.md +176 -0
  7. data/Release.txt +21 -1
  8. data/Version.txt +1 -1
  9. data/bin/turn +8 -2
  10. data/lib/turn/autoload.rb +6 -0
  11. data/lib/turn/autorun.rb +4 -0
  12. data/lib/turn/bin.rb +8 -2
  13. data/lib/turn/colorize.rb +50 -19
  14. data/lib/turn/command.rb +102 -39
  15. data/lib/turn/components/case.rb +7 -2
  16. data/lib/turn/components/method.rb +13 -2
  17. data/lib/turn/components/suite.rb +15 -6
  18. data/lib/turn/components.rb +4 -0
  19. data/lib/turn/configuration.rb +271 -0
  20. data/lib/turn/controller.rb +18 -176
  21. data/lib/turn/decorators/topten_decorator.rb +67 -0
  22. data/lib/turn/minitest.rb +25 -0
  23. data/lib/turn/reporter.rb +75 -13
  24. data/lib/turn/reporters/cue_reporter.rb +27 -11
  25. data/lib/turn/reporters/dot_reporter.rb +24 -28
  26. data/lib/turn/reporters/outline_reporter.rb +76 -39
  27. data/lib/turn/reporters/pretty_reporter.rb +110 -129
  28. data/lib/turn/reporters/progress_reporter.rb +70 -33
  29. data/lib/turn/runners/crossrunner.rb +2 -2
  30. data/lib/turn/runners/isorunner.rb +12 -10
  31. data/lib/turn/runners/minirunner.rb +51 -81
  32. data/lib/turn/runners/solorunner.rb +0 -1
  33. data/lib/turn/runners/testrunner.rb +12 -15
  34. data/lib/turn/testunit.rb +24 -0
  35. data/lib/turn/version.rb +1 -1
  36. data/lib/turn.rb +10 -15
  37. data/test/helper.rb +81 -5
  38. data/test/reporter_test.rb +35 -0
  39. data/test/test_framework.rb +81 -38
  40. data/test/test_reporters.rb +14 -14
  41. data/test/test_runners.rb +57 -19
  42. data/{demo → try}/test_autorun_minitest.rb +5 -4
  43. data/{demo → try}/test_autorun_testunit.rb +1 -2
  44. metadata +135 -112
  45. data/README.txt +0 -116
  46. data/Rakefile +0 -39
  47. data/lib/turn/autorun/minitest.rb +0 -173
  48. data/lib/turn/autorun/testunit.rb +0 -116
  49. data/turn.gemspec +0 -49
  50. /data/{demo → try}/test_counts.rb +0 -0
  51. /data/{demo → try}/test_sample.rb +0 -0
  52. /data/{demo → try}/test_sample2.rb +0 -0
@@ -1,198 +1,45 @@
1
- require 'fileutils'
2
-
3
1
  module Turn
4
- require 'turn/version'
5
- require 'turn/components/suite.rb'
6
- require 'turn/components/case.rb'
7
- require 'turn/components/method.rb'
8
2
 
9
- # = Controller
3
+ # Controls execution of test run.
10
4
  #
11
- #--
12
- # TODO: Add support to test run loggging.
13
- #++
14
5
  class Controller
15
6
 
16
- # List of if file names or glob pattern of tests to run.
17
- attr_accessor :tests
18
-
19
- # List of file names or globs to exclude from +tests+ list.
20
- attr_accessor :exclude
21
-
22
- # Regexp pattern that all test name's must
23
- # match to be eligible to run.
24
- attr_accessor :pattern
25
-
26
- # Regexp pattern that all test cases must
27
- # match to be eligible to run.
28
- attr_accessor :matchcase
29
-
30
- # Add these folders to the $LOAD_PATH.
31
- attr_accessor :loadpath
32
-
33
- # Libs to require when running tests.
34
- attr_accessor :requires
35
-
36
- # Reporter type.
37
- attr_accessor :format
38
-
39
- # Run mode.
40
- attr_accessor :runmode
41
-
42
- # Test against live install (i.e. Don't use loadpath option)
43
- attr_accessor :live
44
-
45
- # Log results? May be true/false or log file name. (TODO)
46
- attr_accessor :log
47
-
48
- # Verbose output?
49
- attr_accessor :verbose
50
-
51
- # Test framework, either :minitest or :testunit
52
- attr_accessor :framework
53
-
54
- def verbose? ; @verbose ; end
55
- def live? ; @live ; end
56
-
57
- private
58
-
59
- def initialize
60
- yield(self) if block_given?
61
- initialize_defaults
62
- end
63
-
64
7
  #
65
- def initialize_defaults
66
- @loadpath ||= ['lib']
67
- @tests ||= ["test/**/{test,}*{,test}.rb"]
68
- @exclude ||= []
69
- @requires ||= []
70
- @live ||= false
71
- @log ||= true
72
- #@format ||= nil
73
- #@runner ||= RUBY_VERSION >= "1.9" ? MiniRunner : TestRunner
74
- @pattern ||= /.*/
8
+ def initialize(config=Turn.config)
9
+ @config = config
75
10
  end
76
11
 
77
- # Collect test configuation.
78
- #def test_configuration(options={})
79
- # #options = configure_options(options, 'test')
80
- # #options['loadpath'] ||= metadata.loadpath
81
- # options['tests'] ||= self.tests
82
- # options['loadpath'] ||= self.loadpath
83
- # options['requires'] ||= self.requires
84
- # options['live'] ||= self.live
85
- # options['exclude'] ||= self.exclude
86
- # #options['tests'] = list_option(options['tests'])
87
- # options['loadpath'] = list_option(options['loadpath'])
88
- # options['exclude'] = list_option(options['exclude'])
89
- # options['require'] = list_option(options['require'])
90
- # return options
91
- #end
92
-
93
12
  #
94
- def list_option(list)
95
- case list
96
- when nil
97
- []
98
- when Array
99
- list
100
- else
101
- list.split(/[:;]/)
102
- end
103
- end
104
-
105
- public
106
-
107
- def tests=(paths)
108
- @tests = list_option(paths)
109
- end
110
-
111
- def loadpath=(paths)
112
- @loadpath = list_option(paths)
113
- end
114
-
115
- def exclude=(paths)
116
- @exclude = list_option(paths)
117
- end
118
-
119
- def requires=(paths)
120
- @requires = list_option(paths)
121
- end
122
-
123
- # Test files.
124
- def files
125
- @files ||= (
126
- fs = tests.map do |t|
127
- File.directory?(t) ? Dir[File.join(t, '**', '*')] : Dir[t]
128
- end
129
- fs = fs.flatten.reject{ |f| File.directory?(f) }
130
-
131
- ex = exclude.map do |x|
132
- File.directory?(x) ? Dir[File.join(x, '**', '*')] : Dir[x]
133
- end
134
- ex = ex.flatten.reject{ |f| File.directory?(f) }
135
-
136
- (fs - ex).uniq.map{ |f| File.expand_path(f) }
137
- )
138
- end
13
+ attr :config
139
14
 
15
+ #
140
16
  def start
141
- @files = nil # reset files just in case
142
-
143
- if files.empty?
17
+ if config.files.empty?
144
18
  $stderr.puts "No tests."
145
19
  return
146
20
  end
147
21
 
148
- testrun = runner.new(self)
22
+ setup
149
23
 
24
+ testrun = runner.new
150
25
  testrun.start
151
26
  end
152
27
 
153
- # Select reporter based on output mode.
154
- def reporter
155
- @reporter ||= (
156
- case format
157
- when :marshal
158
- require 'turn/reporters/marshal_reporter'
159
- Turn::MarshalReporter.new($stdout)
160
- when :progress
161
- require 'turn/reporters/progress_reporter'
162
- Turn::ProgressReporter.new($stdout)
163
- when :dotted
164
- require 'turn/reporters/dot_reporter'
165
- Turn::DotReporter.new($stdout)
166
- when :pretty
167
- require 'turn/reporters/pretty_reporter'
168
- Turn::PrettyReporter.new($stdout)
169
- when :cue
170
- require 'turn/reporters/cue_reporter'
171
- Turn::CueReporter.new($stdout)
172
- else
173
- require 'turn/reporters/outline_reporter'
174
- Turn::OutlineReporter.new($stdout)
175
- end
176
- )
28
+ #
29
+ def setup
30
+ config.loadpath.each{ |path| $: << path } unless config.live?
31
+ config.requires.each{ |path| require(path) }
32
+ config.files.each{ |path| require(path) }
177
33
  end
178
34
 
179
- # # Insatance of Runner, selected based on format and runmode.
35
+ # Insatance of Runner, selected based on format and runmode.
180
36
  def runner
181
37
  @runner ||= (
182
- case framework
183
- when :minitest
184
- require 'turn/runners/minirunner'
185
- else
186
- require 'turn/runners/testrunner'
187
- end
38
+ require 'turn/runners/minirunner'
188
39
 
189
- case runmode
40
+ case config.runmode
190
41
  when :marshal
191
- if framework == :minitest
192
- Turn::MiniRunner
193
- else
194
- Turn::TestRunner
195
- end
42
+ Turn::MiniRunner
196
43
  when :solo
197
44
  require 'turn/runners/solorunner'
198
45
  Turn::SoloRunner
@@ -200,11 +47,7 @@ module Turn
200
47
  require 'turn/runners/crossrunner'
201
48
  Turn::CrossRunner
202
49
  else
203
- if framework == :minitest
204
- Turn::MiniRunner
205
- else
206
- Turn::TestRunner
207
- end
50
+ Turn::MiniRunner
208
51
  end
209
52
  )
210
53
  end
@@ -212,4 +55,3 @@ module Turn
212
55
  end
213
56
 
214
57
  end
215
-
@@ -0,0 +1,67 @@
1
+ module Turn
2
+
3
+ class ToptenDecorator
4
+
5
+ def initialize(reporter)
6
+ @reporter = reporter
7
+ end
8
+
9
+ def method_missing(m,*args,&block)
10
+ @reporter.send(m,*args,&block)
11
+ end
12
+
13
+ def start_case(kase)
14
+ @reporter.start_case(kase)
15
+ @top_ten_current_case = kase
16
+ end
17
+
18
+ def start_test(test)
19
+ @reporter.start_test(test)
20
+ test_time_data[test_key(test)] = {:start => Time.now}
21
+ end
22
+
23
+ def finish_test(test)
24
+ @reporter.finish_test(test)
25
+ test_time_data[test_key(test)][:end] = Time.now
26
+ end
27
+
28
+ def finish_suite(suite)
29
+ @reporter.finish_suite(suite)
30
+ io.puts
31
+ io.puts Colorize.bold("Top 10 Longest Running Tests")
32
+ top_ten_times.each do |(test_name, time)|
33
+ io.print format_time(time)
34
+ io.puts format_test_name(test_name, time)
35
+ end
36
+ io.puts
37
+ end
38
+
39
+ private
40
+
41
+ def test_key(test)
42
+ "#{@top_ten_current_case.name} #{test}"
43
+ end
44
+
45
+ def top_ten_times
46
+ test_times.sort_by {|(_, time)| -time}.take(10)
47
+ end
48
+
49
+ def test_times
50
+ test_time_data.map {|(test, times)| [test, times[:end] - times[:start]]}
51
+ end
52
+
53
+ def test_time_data
54
+ @test_time_data ||= {}
55
+ end
56
+
57
+ def format_time(time)
58
+ Colorize.blue(time)
59
+ end
60
+
61
+ def format_test_name(test_name, time)
62
+ test_name.tabto(11-time.to_s.length)
63
+ end
64
+
65
+ end
66
+
67
+ end
@@ -0,0 +1,25 @@
1
+ # make sure latest verison is used, rather than ruby's built-in
2
+ begin
3
+ gem 'minitest', '< 5.0.0'
4
+ rescue Gem::LoadError
5
+ warn "gem install minitest"
6
+ end
7
+
8
+ # we save the developer the trouble of having to load these (TODO: should we?)
9
+ require 'minitest/unit'
10
+ require 'minitest/spec'
11
+
12
+ # compatability with old Test::Unit
13
+ #Test = MiniTest unless defined?(Test)
14
+
15
+ # load Turn's minitest runner
16
+ require 'turn/runners/minirunner'
17
+
18
+ # set MiniTest's runner to Turn::MiniRunner instance
19
+ if MiniTest::Unit.respond_to?(:runner=)
20
+ MiniTest::Unit.runner = Turn::MiniRunner.new
21
+ else
22
+ raise "MiniTest v#{MiniTest::Unit::VERSION} is out of date.\n" \
23
+ "Please update to a newer version, but version 5.0.0 or above will not work. ."
24
+ #MiniTest::Unit = Turn::MiniRunner
25
+ end
data/lib/turn/reporter.rb CHANGED
@@ -2,8 +2,6 @@ module Turn
2
2
  require 'turn/colorize'
3
3
  require 'turn/core_ext'
4
4
 
5
- # = Reporter
6
- #
7
5
  # There are two distinct way in which a report may be utilized
8
6
  # by a Runner: per-call or per-file. The method #pass, #fail
9
7
  # and #error are generic, and will be used in either case.
@@ -16,54 +14,118 @@ module Turn
16
14
 
17
15
  include Colorize
18
16
 
17
+ # Where to send report, defaults to `$stdout`.
19
18
  attr :io
20
19
 
21
- def initialize(io)
22
- @io = io || $stdout
20
+ def initialize(io, opts={})
21
+ @io = io || $stdout
22
+ @trace = opts[:trace]
23
+ @natural = opts[:natural]
24
+ @verbose = opts[:verbose]
25
+ @mark = opts[:mark].to_i
23
26
  end
24
27
 
25
28
  # These methods are called in the process of running the tests.
26
29
 
30
+ # At the very start, before any testcases are run, this is called.
27
31
  def start_suite(test_suite)
28
32
  end
29
33
 
34
+ # Invoked before a testcase is run.
30
35
  def start_case(test_case)
31
36
  end
32
37
 
38
+ # Invoked before a test is run.
33
39
  def start_test(test)
34
40
  end
35
41
 
42
+ # Invoked when a test passes.
36
43
  def pass(message=nil)
37
44
  end
38
45
 
46
+ # Invoked when a test raises an assertion.
39
47
  def fail(assertion, message=nil)
40
48
  end
41
49
 
50
+ # Invoked when a test raises an exception.
42
51
  def error(exception, message=nil)
43
52
  end
44
53
 
54
+ # Invoked when a test is skipped.
55
+ def skip(exception, message=nil)
56
+ end
57
+
58
+ # Invoked after a test has been run.
45
59
  def finish_test(test)
46
60
  end
47
61
 
62
+ # Invoked after all tests in a testcase have ben run.
48
63
  def finish_case(test_case)
49
64
  end
50
65
 
66
+ # After all tests are run, this is the last observable action.
51
67
  def finish_suite(test_suite)
52
68
  end
53
69
 
54
70
  private
55
71
 
56
- # TODO: backtrace filter probably could use some refinement.
57
- def filter_backtrace(bt)
58
- return [] unless bt
59
- bt.reject!{ |line| line.rindex('minitest') }
60
- bt.reject!{ |line| line.rindex('test/unit') }
61
- bt.reject!{ |line| line.rindex('lib/turn') }
62
- bt.reject!{ |line| line.rindex('bin/turn') }
63
- bt.map{ |line| line.sub(Dir.pwd+'/', '') }
72
+ # Apply filter_backtrace and limit_backtrace in one go.
73
+ def clean_backtrace(backtrace)
74
+ limit_backtrace(filter_backtrace(backtrace))
75
+ end
76
+
77
+ # TODO: Is the text/unit line needed any more now that Dir.pwd is excluded
78
+ # from filtering?
79
+
80
+ $RUBY_IGNORE_CALLERS ||= []
81
+ $RUBY_IGNORE_CALLERS.concat([
82
+ /\/lib\/turn.*\.rb/,
83
+ /\/bin\/turn/,
84
+ /\/lib\/minitest.*\.rb/,
85
+ /\/test\/unit(?!(\/.*\_test.rb)|.*\/test_.*).*\.rb.*/
86
+ ])
87
+
88
+ # Filter backtrace of unimportant entries, and applies count limit if set in
89
+ # configuration. Setting $DEBUG to true will deactivate filter, or if the filter
90
+ # happens to remove all backtrace entries it will revert to the full backtrace,
91
+ # as that probably means there was an issue with the test harness itself.
92
+ def filter_backtrace(backtrace)
93
+ return [] unless backtrace
94
+ bt, pwd = backtrace.dup, Dir.pwd
95
+ unless $DEBUG
96
+ bt = bt.reject do |line|
97
+ $RUBY_IGNORE_CALLERS.any?{|re| re =~ line} unless line.start_with?(pwd)
98
+ end
99
+ end
100
+ bt = backtrace if bt.empty? # if empty just dump the whole thing
101
+ bt.map{ |line| line.sub(pwd+'/', '') }
102
+ end
103
+
104
+ # Limit backtrace to number of lines if `trace` configuration option is set.
105
+ def limit_backtrace(backtrace)
106
+ return [] unless backtrace
107
+ @trace ? backtrace[0, @trace.to_i] : backtrace
108
+ end
109
+
110
+ # Returns a more readable test name with spaces instead of underscores
111
+ def naturalized_name(test)
112
+ if @natural
113
+ test.name.gsub("test_", "").gsub(/_/, " ")
114
+ else
115
+ test.name
116
+ end
64
117
  end
65
118
 
119
+ #
120
+ def ticktock
121
+ t = Time.now - @time
122
+ h, t = t.divmod(3600)
123
+ m, t = t.divmod(60)
124
+ s = t.truncate
125
+ f = ((t - s) * 1000).to_i
126
+
127
+ "%01d:%02d:%02d.%03d" % [h,m,s,f]
128
+ end
66
129
  end
67
130
 
68
131
  end
69
-
@@ -27,7 +27,8 @@ module Turn
27
27
  # @file = test.file
28
28
  # io.puts(test.file)
29
29
  #end
30
- io.print Colorize.blue(" %-69s" % test.name)
30
+ name = naturalized_name(test)
31
+ io.print Colorize.blue(" %-69s" % name)
31
32
  $stdout = @stdout
32
33
  $stderr = @stderr
33
34
  $stdout.rewind
@@ -67,6 +68,15 @@ module Turn
67
68
  prompt
68
69
  end
69
70
 
71
+ def skip(exception, message=nil)
72
+ message = message || exception.to_s
73
+ io.puts("#{SKIP}")
74
+ io.puts(message) #if message
75
+
76
+ #prompt
77
+ end
78
+
79
+
70
80
  def finish_test(test)
71
81
  $stdout = STDOUT
72
82
  $stderr = STDERR
@@ -99,22 +109,26 @@ module Turn
99
109
  #end
100
110
 
101
111
  def finish_suite(suite)
102
- total = suite.count_tests
103
- failure = suite.count_failures
104
- error = suite.count_errors
105
- pass = total - failure - error
112
+ total = suite.count_tests
113
+ passes = suite.count_passes
114
+ assertions = suite.count_assertions
115
+ failures = suite.count_failures
116
+ errors = suite.count_errors
117
+ skips = suite.count_skips
106
118
 
107
119
  bar = '=' * 78
108
- if COLORIZE
120
+ # @FIXME: Remove this, since Colorize already take care of colorize?
121
+ if colorize?
109
122
  bar = if pass == total then Colorize.green(bar)
110
123
  else Colorize.red(bar) end
111
124
  end
112
125
 
113
- tally = [total, suite.count_assertions]
126
+ # @FIXME: Should we add suite.runtime, instead if this lame time calculations?
127
+ tally = [total, assertions, (Time.new - @time)]
114
128
 
115
129
  io.puts bar
116
- io.puts " pass: %d, fail: %d, error: %d" % [pass, failure, error]
117
- io.puts " total: %d tests with %d assertions in #{Time.new - @time} seconds" % tally
130
+ io.puts " pass: %d, fail: %d, error: %d, skip: %d" % [passes, failures, errors, skips]
131
+ io.puts " total: %d tests with %d assertions in %f seconds" % tally
118
132
  io.puts bar
119
133
  end
120
134
 
@@ -130,9 +144,11 @@ module Turn
130
144
  when 'c', ''
131
145
  when 'r'
132
146
  # how to reload and start over?
147
+ io.puts "restart has not been implemented yet"
133
148
  when 'i'
134
149
  # how to drop into an interactive console?
135
- when 't'
150
+ io.puts "irb support has not been implemented yet"
151
+ when 'b', 't'
136
152
  io.puts $@
137
153
  raise ArgumentError
138
154
  when /^\d+$/
@@ -155,7 +171,7 @@ module Turn
155
171
  c continue
156
172
  r restart
157
173
  i irb
158
- t backtrace
174
+ b backtrace
159
175
  # backtrace lines
160
176
  q quit
161
177
  ? help
@@ -22,14 +22,18 @@ module Turn
22
22
  io.print Colorize.pass('.'); io.flush
23
23
  end
24
24
 
25
- def fail(message=nil)
25
+ def fail(assertion, message=nil)
26
26
  io.print Colorize.fail('F'); io.flush
27
27
  end
28
28
 
29
- def error(message=nil)
29
+ def error(exception, message=nil)
30
30
  io.print Colorize.error('E'); io.flush
31
31
  end
32
32
 
33
+ def skip(exception, message=nil)
34
+ io.print Colorize.skip('S'); io.flush
35
+ end
36
+
33
37
  def finish_test(test)
34
38
  end
35
39
 
@@ -53,41 +57,33 @@ module Turn
53
57
  unless list.empty? # or verbose?
54
58
  #report << "\n\n-- Failures and Errors --\n\n"
55
59
  list.uniq.each do |testunit|
56
- message = testunit.fail? ? ' ' + FAIL : ERROR
57
- message = message + ' ' + testunit.message.tabto(0)
58
- message << "\n" + (filter_backtrace(testunit.backtrace).first || '')
59
- report << "\n" << message << "\n"
60
+ message = []
61
+ message << (testunit.fail? ? FAIL : ERROR)
62
+ message << testunit.message.tabto(2)
63
+ message << clean_backtrace(testunit.backtrace).join("\n").tabto(2)
64
+ report << "\n" << message.join("\n") << "\n"
60
65
  end
61
66
  report << "\n"
62
67
  end
63
68
 
64
69
  io.puts report
65
70
 
66
- count = test_tally(suite)
67
-
68
- tally = "%s tests, %s assertions, %s failures, %s errors" % count
69
-
70
- if count[-1] > 0 or count[-2] > 0
71
- tally = Colorize.red(tally)
72
- else
73
- tally = Colorize.green(tally)
74
- end
75
-
76
- io.puts tally
77
- end
71
+ # @TODO: Add something like suite.count(:tests, :passes) or
72
+ # suite.count(tests: "%d tests", passes: "%d passes")
73
+ # to cleanup, which will return something handy
74
+ # (suite.count(:test, :passes).test proxy maybe?)
75
+ total = "%d tests" % suite.count_tests
76
+ passes = "%d passed" % suite.count_passes
77
+ assertions = "%d assertions" % suite.count_assertions
78
+ failures = "%s failures" % suite.count_failures
79
+ errors = "%s errors" % suite.count_errors
80
+ skips = "%s skips" % suite.count_skips
78
81
 
79
- private
82
+ tally = [total, passes, assertions, failures, errors, skips].join(", ")
80
83
 
81
- def test_tally(suite)
82
- counts = suite.collect{ |tr| tr.counts }
83
- tally = [0,0,0,0]
84
- counts.each do |count|
85
- 4.times{ |i| tally[i] += count[i] }
86
- end
87
- return tally
84
+ io.puts suite.passed? ? Colorize.green(tally) : Colorize.red(tally)
88
85
  end
89
86
 
90
87
  end
91
88
 
92
- end
93
-
89
+ end