ZenTest 3.4.3 → 3.5.1

Sign up to get free protection for your applications and to get access to all the features.
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|