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.
- data/History.txt +46 -3
- data/Manifest.txt +13 -0
- data/README.txt +1 -0
- data/Rakefile +20 -3
- data/bin/autotest +23 -37
- data/bin/multiruby +13 -7
- data/bin/unit_diff +1 -1
- data/example_dot_autotest.rb +14 -0
- data/lib/autotest.rb +77 -30
- data/lib/autotest/autoupdate.rb +26 -0
- data/lib/autotest/emacs.rb +29 -0
- data/lib/autotest/fixtures.rb +12 -0
- data/lib/autotest/growl.rb +7 -17
- data/lib/autotest/heckle.rb +14 -0
- data/lib/autotest/migrate.rb +7 -0
- data/lib/autotest/notify.rb +38 -0
- data/lib/autotest/redgreen.rb +7 -4
- data/lib/autotest/screen.rb +77 -0
- data/lib/autotest/shame.rb +45 -0
- data/lib/autotest/timestamp.rb +3 -1
- data/lib/camping_autotest.rb +37 -0
- data/lib/functional_test_matrix.rb +85 -0
- data/lib/rails_autotest.rb +49 -41
- data/lib/rspec_rails_autotest.rb +119 -0
- data/lib/test/rails.rb +28 -1
- data/lib/test/rails/controller_test_case.rb +27 -6
- data/lib/test/rails/functional_test_case.rb +3 -0
- data/lib/test/rails/helper_test_case.rb +3 -0
- data/lib/test/rails/view_test_case.rb +13 -5
- data/lib/test/zentest_assertions.rb +42 -23
- data/lib/unit_diff.rb +86 -69
- data/lib/zentest.rb +58 -87
- data/lib/zentest_mapping.rb +97 -0
- data/test/test_autotest.rb +23 -3
- data/test/test_help.rb +10 -4
- data/test/test_rails_autotest.rb +6 -4
- data/test/test_rails_controller_test_case.rb +10 -2
- data/test/test_ruby_fork.rb +12 -12
- data/test/test_unit_diff.rb +37 -33
- data/test/test_zentest.rb +15 -141
- data/test/test_zentest_assertions.rb +38 -18
- data/test/test_zentest_mapping.rb +213 -0
- metadata +18 -4
data/lib/unit_diff.rb
CHANGED
@@ -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
|
77
|
-
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
|
54
|
+
def self.unit_diff
|
83
55
|
trap 'INT' do exit 1 end
|
84
|
-
|
85
|
-
ud.unit_diff(input)
|
56
|
+
puts UnitDiff.new.unit_diff
|
86
57
|
end
|
87
58
|
|
88
|
-
def
|
59
|
+
def parse_input(input, output)
|
89
60
|
current = []
|
90
61
|
data = []
|
91
62
|
data << current
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
103
|
-
|
101
|
+
output.sync = old_sync
|
102
|
+
data = data.reject { |o| o == ["\n"] or o.empty? }
|
104
103
|
footer = data.pop
|
105
|
-
|
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
|
-
|
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 = [
|
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
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
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
|
-
|
data/lib/zentest.rb
CHANGED
@@ -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
|
-
#
|
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.
|
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
|
-
|
318
|
-
|
319
|
-
|
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|
|