ZenTest 3.4.3 → 3.5.1

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 (43) hide show
  1. data/History.txt +46 -3
  2. data/Manifest.txt +13 -0
  3. data/README.txt +1 -0
  4. data/Rakefile +20 -3
  5. data/bin/autotest +23 -37
  6. data/bin/multiruby +13 -7
  7. data/bin/unit_diff +1 -1
  8. data/example_dot_autotest.rb +14 -0
  9. data/lib/autotest.rb +77 -30
  10. data/lib/autotest/autoupdate.rb +26 -0
  11. data/lib/autotest/emacs.rb +29 -0
  12. data/lib/autotest/fixtures.rb +12 -0
  13. data/lib/autotest/growl.rb +7 -17
  14. data/lib/autotest/heckle.rb +14 -0
  15. data/lib/autotest/migrate.rb +7 -0
  16. data/lib/autotest/notify.rb +38 -0
  17. data/lib/autotest/redgreen.rb +7 -4
  18. data/lib/autotest/screen.rb +77 -0
  19. data/lib/autotest/shame.rb +45 -0
  20. data/lib/autotest/timestamp.rb +3 -1
  21. data/lib/camping_autotest.rb +37 -0
  22. data/lib/functional_test_matrix.rb +85 -0
  23. data/lib/rails_autotest.rb +49 -41
  24. data/lib/rspec_rails_autotest.rb +119 -0
  25. data/lib/test/rails.rb +28 -1
  26. data/lib/test/rails/controller_test_case.rb +27 -6
  27. data/lib/test/rails/functional_test_case.rb +3 -0
  28. data/lib/test/rails/helper_test_case.rb +3 -0
  29. data/lib/test/rails/view_test_case.rb +13 -5
  30. data/lib/test/zentest_assertions.rb +42 -23
  31. data/lib/unit_diff.rb +86 -69
  32. data/lib/zentest.rb +58 -87
  33. data/lib/zentest_mapping.rb +97 -0
  34. data/test/test_autotest.rb +23 -3
  35. data/test/test_help.rb +10 -4
  36. data/test/test_rails_autotest.rb +6 -4
  37. data/test/test_rails_controller_test_case.rb +10 -2
  38. data/test/test_ruby_fork.rb +12 -12
  39. data/test/test_unit_diff.rb +37 -33
  40. data/test/test_zentest.rb +15 -141
  41. data/test/test_zentest_assertions.rb +38 -18
  42. data/test/test_zentest_mapping.rb +213 -0
  43. metadata +18 -4
@@ -1,41 +1,5 @@
1
1
  require 'tempfile'
2
2
 
3
- class Tempfile
4
- # blatently stolen. Design was poor in Tempfile.
5
- def self.make_tempname(basename, n=10)
6
- sprintf('%s%d.%d', basename, $$, n)
7
- end
8
-
9
- def self.make_temppath(basename)
10
- tempname = ""
11
- n = 1
12
- begin
13
- tmpname = File.join('/tmp', make_tempname(basename, n))
14
- n += 1
15
- end while File.exist?(tmpname) and n < 100
16
- tmpname
17
- end
18
- end
19
-
20
- def temp_file(data)
21
- temp =
22
- if $k then
23
- File.new(Tempfile.make_temppath("diff"), "w")
24
- else
25
- Tempfile.new("diff")
26
- end
27
- count = 0
28
- data = data.map { |l| '%3d) %s' % [count+=1, l] } if $l
29
- data = data.join('')
30
- # unescape newlines, strip <> from entire string
31
- data = data.gsub(/\\n/, "\n").gsub(/0x[a-f0-9]+/m, '0xXXXXXX') + "\n"
32
- temp.print data
33
- temp.puts unless data =~ /\n\Z/m
34
- temp.flush
35
- temp.rewind
36
- temp
37
- end
38
-
39
3
  ##
40
4
  # UnitDiff makes reading Test::Unit output easy and fun. Instead of a
41
5
  # confusing jumble of text with nearly unnoticable changes like this:
@@ -73,36 +37,72 @@ end
73
37
 
74
38
  class UnitDiff
75
39
 
76
- WINDOZE = /win32/ =~ RUBY_PLATFORM unless defined? WINDOZE
77
- DIFF = (WINDOZE ? 'diff.exe' : 'diff') unless defined? DIFF
40
+ WINDOZE = /win32/ =~ RUBY_PLATFORM unless defined? WINDOZE
41
+ DIFF = if WINDOZE
42
+ 'diff.exe'
43
+ else
44
+ if system("gdiff", __FILE__, __FILE__)
45
+ 'gdiff' # solaris and kin suck
46
+ else
47
+ 'diff'
48
+ end
49
+ end unless defined? DIFF
78
50
 
79
51
  ##
80
52
  # Handy wrapper for UnitDiff#unit_diff.
81
53
 
82
- def self.unit_diff(input)
54
+ def self.unit_diff
83
55
  trap 'INT' do exit 1 end
84
- ud = UnitDiff.new
85
- ud.unit_diff(input)
56
+ puts UnitDiff.new.unit_diff
86
57
  end
87
58
 
88
- def input(input)
59
+ def parse_input(input, output)
89
60
  current = []
90
61
  data = []
91
62
  data << current
92
-
93
- # Collect
94
- input.each_line do |line|
95
- if line =~ /^\s*$/ or line =~ /^\(?\s*\d+\) (Failure|Error):/ then
63
+ print_lines = true
64
+
65
+ term = "\nFinished".split(//).map { |c| c[0] }
66
+ term_length = term.size
67
+
68
+ old_sync = output.sync
69
+ output.sync = true
70
+ while line = input.gets
71
+ case line
72
+ when /^(Loaded suite|Started)/ then
73
+ print_lines = true
74
+ output.puts line
75
+ chars = []
76
+ while c = input.getc do
77
+ output.putc c
78
+ chars << c
79
+ tail = chars[-term_length..-1]
80
+ break if chars.size >= term_length and tail == term
81
+ end
82
+ output.puts input.gets # the rest of "Finished in..."
83
+ output.puts
84
+ next
85
+ when /^\s*$/ then
86
+ print_lines = false
87
+ type = nil
88
+ current = []
89
+ data << current
90
+ when /^\(?\s*\d+\) (Failure|Error):/ then
91
+ print_lines = false # if line =~ /Failure|Error/
96
92
  type = $1
97
93
  current = []
98
94
  data << current
95
+ when /^Finished in \d/ then
96
+ print_lines = false
99
97
  end
98
+ output.puts line if print_lines
100
99
  current << line
101
100
  end
102
- data = data.reject { |o| o == ["\n"] }
103
- header = data.shift
101
+ output.sync = old_sync
102
+ data = data.reject { |o| o == ["\n"] or o.empty? }
104
103
  footer = data.pop
105
- return header, data, footer
104
+
105
+ return data, footer
106
106
  end
107
107
 
108
108
  def parse_diff(result)
@@ -115,7 +115,7 @@ class UnitDiff
115
115
  until result.empty? do
116
116
  case state
117
117
  when :header then
118
- header << result.shift
118
+ header << result.shift
119
119
  state = :expect if result.first =~ /^</
120
120
  when :expect then
121
121
  state = :butwas if result.first.sub!(/ expected but was/, '')
@@ -146,19 +146,16 @@ class UnitDiff
146
146
  # Scans Test::Unit output +input+ looking for comparison failures and makes
147
147
  # them easily readable by passing them through diff.
148
148
 
149
- def unit_diff(input)
149
+ def unit_diff(input=ARGF, output=$stdout)
150
150
  $b = false unless defined? $b
151
151
  $c = false unless defined? $c
152
152
  $k = false unless defined? $k
153
153
  $l = false unless defined? $l
154
154
  $u = false unless defined? $u
155
155
 
156
- header, data, footer = self.input(input)
157
-
158
- header = header.map { |l| l.chomp }
159
- header << nil unless header.empty?
156
+ data, footer = self.parse_input(input, output)
160
157
 
161
- output = [header]
158
+ output = []
162
159
 
163
160
  # Output
164
161
  data.each do |result|
@@ -175,17 +172,30 @@ class UnitDiff
175
172
  output.push prefix.compact.map {|line| line.strip}.join("\n")
176
173
 
177
174
  if butwas then
178
- a = temp_file(expect)
179
- b = temp_file(butwas)
180
-
181
- diff_flags = $u ? "-u" : $c ? "-c" : ""
182
- diff_flags += " -b" if $b
183
-
184
- result = `#{DIFF} #{diff_flags} #{a.path} #{b.path}`
185
- if result.empty? then
186
- output.push "[no difference--suspect ==]"
187
- else
188
- output.push result.map { |line| line.chomp }
175
+ Tempfile.open("expect") do |a|
176
+ a.write(massage(expect))
177
+ a.rewind
178
+ Tempfile.open("butwas") do |b|
179
+ b.write(massage(butwas))
180
+ b.rewind
181
+
182
+ diff_flags = $u ? "-u" : $c ? "-c" : ""
183
+ diff_flags += " -b" if $b
184
+
185
+ result = `#{DIFF} #{diff_flags} #{a.path} #{b.path}`
186
+ if result.empty? then
187
+ output.push "[no difference--suspect ==]"
188
+ else
189
+ output.push result.map { |line| line.chomp }
190
+ end
191
+
192
+ if $k then
193
+ warn "moving #{a.path} to #{a.path}.keep"
194
+ File.rename a.path, a.path + ".keep"
195
+ warn "moving #{b.path} to #{b.path}.keep"
196
+ File.rename b.path, b.path + ".keep"
197
+ end
198
+ end
189
199
  end
190
200
 
191
201
  output.push ''
@@ -195,12 +205,19 @@ class UnitDiff
195
205
  end
196
206
 
197
207
  if footer then
198
- footer.shift if footer.first.strip.empty?
208
+ footer.shift if footer.first.strip.empty?# unless footer.first.nil?
199
209
  output.push footer.compact.map {|line| line.strip}.join("\n")
200
210
  end
201
211
 
202
212
  return output.flatten.join("\n")
203
213
  end
204
214
 
215
+ def massage(data)
216
+ data = data.map { |l| '%3d) %s' % [count+=1, l] } if $l
217
+ # unescape newlines, strip <> from entire string
218
+ data = data.join
219
+ data = data.gsub(/\\n/, "\n").gsub(/0x[a-f0-9]+/m, '0xXXXXXX') + "\n"
220
+ data += "\n" unless data[-1] == ?\n
221
+ data
222
+ end
205
223
  end
206
-
@@ -1,3 +1,6 @@
1
+
2
+ require 'zentest_mapping'
3
+
1
4
  $stdlib = {}
2
5
  ObjectSpace.each_object(Module) { |m| $stdlib[m.name] = true }
3
6
 
@@ -49,33 +52,13 @@ end
49
52
  # * TestA::TestB#test_blah_missing_file
50
53
  # * All naming conventions are bidirectional with the exception of test extensions.
51
54
  #
52
- # == METHOD MAPPING
53
- #
54
- # Method names are mapped bidirectionally in the following way:
55
- #
56
- # method test_method
57
- # method? test_method_eh (too much exposure to Canadians :)
58
- # method! test_method_bang
59
- # method= test_method_equals
60
- # [] test_index
61
- # * test_times
62
- # == test_equals2
63
- # === test_equals3
64
- #
65
- # Further, any of the test methods should be able to have arbitrary
66
- # extensions put on the name to distinguish edge cases:
67
- #
68
- # method test_method
69
- # method test_method_simple
70
- # method test_method_no_network
71
- #
72
- # To allow for unmapped test methods (ie, non-unit tests), name them:
73
- #
74
- # test_integration_.*
55
+ # See ZenTestMapping for documentation on method naming.
75
56
 
76
57
  class ZenTest
77
58
 
78
- VERSION = '3.4.3'
59
+ VERSION = '3.5.1'
60
+
61
+ include ZenTestMapping
79
62
 
80
63
  if $TESTING then
81
64
  attr_reader :missing_methods
@@ -96,6 +79,7 @@ class ZenTest
96
79
  @missing_methods = Hash.new { |h,k| h[k] = {} }
97
80
  end
98
81
 
82
+ # load_file wraps require, skipping the loading of $0.
99
83
  def load_file(file)
100
84
  puts "# loading #{file} // #{$0}" if $DEBUG
101
85
 
@@ -110,6 +94,8 @@ class ZenTest
110
94
  end
111
95
  end
112
96
 
97
+ # obtain the class klassname, either from Module or
98
+ # using ObjectSpace to search for it.
113
99
  def get_class(klassname)
114
100
  begin
115
101
  klass = Module.const_get(klassname.intern)
@@ -133,6 +119,11 @@ class ZenTest
133
119
  return klass
134
120
  end
135
121
 
122
+ # Get the public instance, class and singleton methods for
123
+ # class klass. If full is true, include the methods from
124
+ # Kernel and other modules that get included. The methods
125
+ # suite, new, pretty_print, pretty_print_cycle will not
126
+ # be included in the resuting array.
136
127
  def get_methods_for(klass, full=false)
137
128
  klass = self.get_class(klass) if klass.kind_of? String
138
129
 
@@ -154,6 +145,10 @@ class ZenTest
154
145
  return klassmethods
155
146
  end
156
147
 
148
+ # Return the methods for class klass, as a hash with the
149
+ # method nemas as keys, and true as the value for all keys.
150
+ # Unless full is true, leave out the methods for Object which
151
+ # all classes get.
157
152
  def get_inherited_methods_for(klass, full)
158
153
  klass = self.get_class(klass) if klass.kind_of? String
159
154
 
@@ -177,6 +172,8 @@ class ZenTest
177
172
  return klassmethods
178
173
  end
179
174
 
175
+ # Check the class klass is a testing class
176
+ # (by inspecting its name).
180
177
  def is_test_class(klass)
181
178
  klass = klass.to_s
182
179
  klasspath = klass.split(/::/)
@@ -184,6 +181,9 @@ class ZenTest
184
181
  return a_bad_classpath.nil?
185
182
  end
186
183
 
184
+ # Generate the name of a testclass from non-test class
185
+ # so that Foo::Blah => TestFoo::TestBlah, etc. It the
186
+ # name is already a test class, convert it the other way.
187
187
  def convert_class_name(name)
188
188
  name = name.to_s
189
189
 
@@ -204,6 +204,11 @@ class ZenTest
204
204
  return name
205
205
  end
206
206
 
207
+ # Does all the work of finding a class by name,
208
+ # obtaining its methods and those of its superclass.
209
+ # The full parameter determines if all the methods
210
+ # including those of Object and mixed in modules
211
+ # are obtained (true if they are, false by default).
207
212
  def process_class(klassname, full=false)
208
213
  klass = self.get_class(klassname)
209
214
  raise "Couldn't get class for #{klassname}" if klass.nil?
@@ -216,10 +221,16 @@ class ZenTest
216
221
  target[klassname] = self.get_methods_for(klass, full)
217
222
 
218
223
  # record ALL instance methods including superclasses (minus Object)
224
+ # Only minus Object if full is true.
219
225
  @inherited_methods[klassname] = self.get_inherited_methods_for(klass, full)
220
226
  return klassname
221
227
  end
222
228
 
229
+ # Work through files, collecting class names, method names
230
+ # and assertions. Detects ZenTest (SKIP|FULL) comments
231
+ # in the bodies of classes.
232
+ # For each class a count of methods and test methods is
233
+ # kept, and the ratio noted.
223
234
  def scan_files(*files)
224
235
  assert_count = Hash.new(0)
225
236
  method_count = Hash.new(0)
@@ -308,75 +319,16 @@ class ZenTest
308
319
  end
309
320
  end
310
321
 
322
+ # Adds a missing method to the collected results.
311
323
  def add_missing_method(klassname, methodname)
312
324
  @result.push "# ERROR method #{klassname}\##{methodname} does not exist (1)" if $DEBUG and not $TESTING
313
325
  @error_count += 1
314
326
  @missing_methods[klassname][methodname] = true
315
327
  end
316
328
 
317
- @@orig_method_map = {
318
- '!' => 'bang',
319
- '%' => 'percent',
320
- '&' => 'and',
321
- '*' => 'times',
322
- '**' => 'times2',
323
- '+' => 'plus',
324
- '-' => 'minus',
325
- '/' => 'div',
326
- '<' => 'lt',
327
- '<=' => 'lte',
328
- '<=>' => 'spaceship',
329
- "<\<" => 'lt2',
330
- '==' => 'equals2',
331
- '===' => 'equals3',
332
- '=~' => 'equalstilde',
333
- '>' => 'gt',
334
- '>=' => 'ge',
335
- '>>' => 'gt2',
336
- '+@' => 'unary_plus',
337
- '-@' => 'unary_minus',
338
- '[]' => 'index',
339
- '[]=' => 'index_equals',
340
- '^' => 'carat',
341
- '|' => 'or',
342
- '~' => 'tilde',
343
- }
344
-
345
- @@method_map = @@orig_method_map.merge(@@orig_method_map.invert)
346
-
347
- def normal_to_test(name)
348
- name = name.dup # wtf?
349
- is_cls_method = name.sub!(/^self\./, '')
350
- name = @@method_map[name] if @@method_map.has_key? name
351
- name = name.sub(/=$/, '_equals')
352
- name = name.sub(/\?$/, '_eh')
353
- name = name.sub(/\!$/, '_bang')
354
- name = "class_" + name if is_cls_method
355
- "test_#{name}"
356
- end
357
-
358
- def test_to_normal(name, klassname=nil)
359
- known_methods = (@inherited_methods[klassname] || {}).keys.sort.reverse
360
-
361
- mapped_re = @@orig_method_map.values.sort_by { |k| k.length }.map {|s| Regexp.escape(s)}.reverse.join("|")
362
- known_methods_re = known_methods.map {|s| Regexp.escape(s)}.join("|")
363
-
364
- name = name.sub(/^test_/, '')
365
- name = name.sub(/_equals/, '=') unless name =~ /index/
366
- name = name.sub(/_bang.*$/, '!') # FIX: deal w/ extensions separately
367
- name = name.sub(/_eh/, '?')
368
- is_cls_method = name.sub!(/^class_/, '')
369
- name = name.sub(/^(#{mapped_re})(.*)$/) {$1}
370
- name = name.sub(/^(#{known_methods_re})(.*)$/) {$1} unless known_methods_re.empty?
371
-
372
- # look up in method map
373
- name = @@method_map[name] if @@method_map.has_key? name
374
-
375
- name = 'self.' + name if is_cls_method
376
-
377
- name
378
- end
379
-
329
+ # Checks, for the given class klassname, that each method
330
+ # has a corrsponding test method. If it doesn't this is
331
+ # added to the information for that class
380
332
  def analyze_impl(klassname)
381
333
  testklassname = self.convert_class_name(klassname)
382
334
  if @test_klasses[testklassname] then
@@ -406,6 +358,9 @@ class ZenTest
406
358
  end # @test_klasses[testklassname]
407
359
  end
408
360
 
361
+ # For the given test class testklassname, ensure that all
362
+ # the test methods have corresponding (normal) methods.
363
+ # If not, add them to the information about that class.
409
364
  def analyze_test(testklassname)
410
365
  klassname = self.convert_class_name(testklassname)
411
366
 
@@ -459,6 +414,10 @@ class ZenTest
459
414
  end # @klasses[klassname]
460
415
  end
461
416
 
417
+
418
+ # Walk each known class and test that each method has
419
+ # a test method
420
+ # Then do it in the other direction...
462
421
  def analyze
463
422
  # walk each known class and test that each method has a test method
464
423
  @klasses.each_key do |klassname|
@@ -471,6 +430,10 @@ class ZenTest
471
430
  end
472
431
  end
473
432
 
433
+ # Using the results gathered during analysis
434
+ # generate skeletal code with methods raising
435
+ # NotImplementedError, so that they can be filled
436
+ # in later, and so the tests will fail to start with.
474
437
  def generate_code
475
438
 
476
439
  # @result.unshift "# run against: #{files.join(', ')}" if $DEBUG
@@ -551,10 +514,14 @@ class ZenTest
551
514
  @result.push ''
552
515
  end
553
516
 
517
+ # presents results in a readable manner.
554
518
  def result
555
519
  return @result.join("\n")
556
520
  end
557
521
 
522
+ # Runs ZenTest over all the supplied files so that
523
+ # they are analysed and the missing methods have
524
+ # skeleton code written.
558
525
  def self.fix(*files)
559
526
  zentest = ZenTest.new
560
527
  zentest.scan_files(*files)
@@ -563,6 +530,10 @@ class ZenTest
563
530
  return zentest.result
564
531
  end
565
532
 
533
+ # Process all the supplied classes for methods etc,
534
+ # and analyse the results. Generate the skeletal code
535
+ # and eval it to put the methods into the runtime
536
+ # environment.
566
537
  def self.autotest(*klasses)
567
538
  zentest = ZenTest.new
568
539
  klasses.each do |klass|