ZenTest 3.7.2 → 3.8.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.
- data/History.txt +19 -0
- data/Manifest.txt +3 -1
- data/Rakefile +12 -0
- data/articles/Article.css +721 -0
- data/articles/getting_started_with_autotest.html +532 -0
- data/{LinuxJournalArticle.txt → articles/how_to_use_zentest.txt} +0 -0
- data/bin/multiruby +7 -0
- data/example_dot_autotest.rb +2 -0
- data/lib/autotest.rb +203 -87
- data/lib/autotest/rails.rb +51 -43
- data/lib/zentest.rb +1 -1
- data/test/test_autotest.rb +93 -13
- metadata +6 -4
File without changes
|
data/bin/multiruby
CHANGED
@@ -80,6 +80,11 @@ Dir.chdir root_dir do
|
|
80
80
|
end
|
81
81
|
end
|
82
82
|
end
|
83
|
+
|
84
|
+
# pick up rubinius - allows for simple symlinks to your build dir
|
85
|
+
Dir.chdir('install') do
|
86
|
+
versions.push(*Dir["rubinius*"])
|
87
|
+
end
|
83
88
|
end
|
84
89
|
|
85
90
|
versions = ENV['VERSIONS'].split(/:/) if ENV.has_key? 'VERSIONS'
|
@@ -87,6 +92,8 @@ versions = ENV['VERSIONS'].split(/:/) if ENV.has_key? 'VERSIONS'
|
|
87
92
|
results = {}
|
88
93
|
versions.each do |version|
|
89
94
|
ruby = "#{root_dir}/install/#{version}/bin/ruby"
|
95
|
+
ruby.sub!(/bin.ruby/, 'shotgun/rubinius') if version =~ /rubinius/
|
96
|
+
|
90
97
|
puts
|
91
98
|
puts "VERSION = #{version}"
|
92
99
|
puts
|
data/example_dot_autotest.rb
CHANGED
data/lib/autotest.rb
CHANGED
@@ -119,12 +119,14 @@ class Autotest
|
|
119
119
|
@@discoveries.map { |proc| proc.call }.flatten.compact.sort.uniq
|
120
120
|
end
|
121
121
|
|
122
|
+
##
|
123
|
+
# Initialize and run the system.
|
124
|
+
|
122
125
|
def self.run
|
123
126
|
new.run
|
124
127
|
end
|
125
128
|
|
126
|
-
attr_accessor(:
|
127
|
-
:extra_class_map,
|
129
|
+
attr_accessor(:extra_class_map,
|
128
130
|
:extra_files,
|
129
131
|
:files,
|
130
132
|
:files_to_test,
|
@@ -134,12 +136,14 @@ class Autotest
|
|
134
136
|
:output,
|
135
137
|
:results,
|
136
138
|
:tainted,
|
137
|
-
:test_mappings,
|
138
139
|
:unit_diff,
|
139
140
|
:wants_to_quit)
|
140
141
|
|
142
|
+
##
|
143
|
+
# Initialize the instance and then load the user's .autotest file, if any.
|
144
|
+
|
141
145
|
def initialize
|
142
|
-
@
|
146
|
+
@exception_list = []
|
143
147
|
@extra_class_map = {}
|
144
148
|
@extra_files = []
|
145
149
|
@files = Hash.new Time.at(0)
|
@@ -148,43 +152,43 @@ class Autotest
|
|
148
152
|
@output = $stderr
|
149
153
|
@sleep = 1
|
150
154
|
@unit_diff = "unit_diff -u"
|
155
|
+
@test_mappings = {}
|
151
156
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
/^test.*\/test_.*rb$/ => proc { |filename, _|
|
157
|
-
filename
|
158
|
-
}
|
159
|
-
}
|
157
|
+
self.add_mapping(/^lib\/.*\.rb$/) do |filename, _|
|
158
|
+
possible = File.basename(filename).gsub '_', '_?'
|
159
|
+
files_matching %r%^test/.*#{possible}$%
|
160
|
+
end
|
160
161
|
|
161
|
-
|
162
|
+
self.add_mapping(/^test.*\/test_.*rb$/) do |filename, _|
|
163
|
+
filename
|
164
|
+
end
|
162
165
|
|
163
|
-
|
164
|
-
|
165
|
-
else
|
166
|
-
@exceptions = Regexp.union(*@exceptions)
|
166
|
+
[File.expand_path('~/.autotest'), './.autotest'].each do |f|
|
167
|
+
load f if File.exist? f
|
167
168
|
end
|
168
169
|
end
|
169
170
|
|
170
|
-
|
171
|
-
#
|
171
|
+
##
|
172
|
+
# Repeatedly run failed tests, then all tests, then wait for changes
|
173
|
+
# and carry on until killed.
|
174
|
+
|
172
175
|
def run
|
173
|
-
hook :
|
176
|
+
hook :initialize
|
177
|
+
hook :run # TODO: phase out
|
174
178
|
reset
|
175
179
|
add_sigint_handler
|
176
180
|
|
177
181
|
loop do # ^c handler
|
178
182
|
begin
|
179
183
|
get_to_green
|
180
|
-
if
|
184
|
+
if self.tainted then
|
181
185
|
rerun_all_tests
|
182
186
|
else
|
183
187
|
hook :all_good
|
184
188
|
end
|
185
189
|
wait_for_changes
|
186
190
|
rescue Interrupt
|
187
|
-
if
|
191
|
+
if self.wants_to_quit then
|
188
192
|
break
|
189
193
|
else
|
190
194
|
reset
|
@@ -194,7 +198,9 @@ class Autotest
|
|
194
198
|
hook :quit
|
195
199
|
end
|
196
200
|
|
201
|
+
##
|
197
202
|
# Keep running the tests after a change, until all pass.
|
203
|
+
|
198
204
|
def get_to_green
|
199
205
|
until all_good do
|
200
206
|
run_tests
|
@@ -202,19 +208,20 @@ class Autotest
|
|
202
208
|
end
|
203
209
|
end
|
204
210
|
|
205
|
-
|
206
|
-
# handle the results.
|
211
|
+
##
|
212
|
+
# Look for files to test then run the tests and handle the results.
|
213
|
+
|
207
214
|
def run_tests
|
208
215
|
hook :run_command
|
209
216
|
|
210
|
-
find_files_to_test # failed + changed/affected
|
211
|
-
cmd = make_test_cmd
|
217
|
+
self.find_files_to_test # failed + changed/affected
|
218
|
+
cmd = self.make_test_cmd self.files_to_test
|
212
219
|
|
213
220
|
puts cmd
|
214
221
|
|
215
222
|
old_sync = $stdout.sync
|
216
223
|
$stdout.sync = true
|
217
|
-
|
224
|
+
self.results = []
|
218
225
|
line = []
|
219
226
|
begin
|
220
227
|
open("| #{cmd}", "r") do |f|
|
@@ -223,11 +230,11 @@ class Autotest
|
|
223
230
|
putc c
|
224
231
|
line << c
|
225
232
|
if c == ?\n then
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
233
|
+
self.results << if RUBY_VERSION >= "1.9" then
|
234
|
+
line.join
|
235
|
+
else
|
236
|
+
line.pack "c*"
|
237
|
+
end
|
231
238
|
line.clear
|
232
239
|
end
|
233
240
|
end
|
@@ -236,26 +243,25 @@ class Autotest
|
|
236
243
|
$stdout.sync = old_sync
|
237
244
|
end
|
238
245
|
hook :ran_command
|
239
|
-
|
246
|
+
self.results = self.results.join
|
240
247
|
|
241
|
-
handle_results(
|
248
|
+
handle_results(self.results)
|
242
249
|
end
|
243
250
|
|
244
251
|
############################################################
|
245
252
|
# Utility Methods, not essential to reading of logic
|
246
253
|
|
247
|
-
|
248
|
-
|
249
|
-
end
|
254
|
+
##
|
255
|
+
# Installs a sigint handler.
|
250
256
|
|
251
257
|
def add_sigint_handler
|
252
258
|
trap 'INT' do
|
253
|
-
if
|
254
|
-
|
259
|
+
if self.interrupted then
|
260
|
+
self.wants_to_quit = true
|
255
261
|
else
|
256
262
|
unless hook :interrupt then
|
257
263
|
puts "Interrupt a second time to quit"
|
258
|
-
|
264
|
+
self.interrupted = true
|
259
265
|
sleep 1.5
|
260
266
|
end
|
261
267
|
raise Interrupt, nil # let the run loop catch it
|
@@ -263,16 +269,18 @@ class Autotest
|
|
263
269
|
end
|
264
270
|
end
|
265
271
|
|
266
|
-
|
267
|
-
# (because they've all passed),
|
272
|
+
##
|
273
|
+
# If there are no files left to test (because they've all passed),
|
268
274
|
# then all is good.
|
275
|
+
|
269
276
|
def all_good
|
270
|
-
|
277
|
+
files_to_test.empty?
|
271
278
|
end
|
272
279
|
|
273
|
-
|
274
|
-
#
|
275
|
-
# etc
|
280
|
+
##
|
281
|
+
# Convert a path in a string, s, into a class name, changing
|
282
|
+
# underscores to CamelCase, etc.
|
283
|
+
|
276
284
|
def path_to_classname(s)
|
277
285
|
sep = File::SEPARATOR
|
278
286
|
f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split(sep)
|
@@ -281,35 +289,43 @@ class Autotest
|
|
281
289
|
f.join('::')
|
282
290
|
end
|
283
291
|
|
292
|
+
##
|
293
|
+
# Returns a hash mapping a file name to the known failures for that
|
294
|
+
# file.
|
295
|
+
|
284
296
|
def consolidate_failures(failed)
|
285
297
|
filters = Hash.new { |h,k| h[k] = [] }
|
286
298
|
|
287
|
-
class_map = Hash[
|
299
|
+
class_map = Hash[*self.files.keys.grep(/^test/).map { |f|
|
300
|
+
[path_to_classname(f), f]
|
301
|
+
}.flatten]
|
288
302
|
class_map.merge!(self.extra_class_map)
|
289
303
|
|
290
304
|
failed.each do |method, klass|
|
291
305
|
if class_map.has_key? klass then
|
292
306
|
filters[class_map[klass]] << method
|
293
307
|
else
|
294
|
-
|
308
|
+
output.puts "Unable to map class #{klass} to a file"
|
295
309
|
end
|
296
310
|
end
|
297
311
|
|
298
312
|
return filters
|
299
313
|
end
|
300
314
|
|
301
|
-
|
302
|
-
#
|
303
|
-
#
|
315
|
+
##
|
316
|
+
# Find the files to process, ignoring temporary files, source
|
317
|
+
# configuration management files, etc., and return a Hash mapping
|
318
|
+
# filename to modification time.
|
319
|
+
|
304
320
|
def find_files
|
305
321
|
result = {}
|
306
322
|
targets = ['.'] + self.extra_files
|
307
323
|
|
308
324
|
Find.find(*targets) do |f|
|
309
|
-
Find.prune if
|
325
|
+
Find.prune if f =~ self.exceptions
|
310
326
|
|
311
327
|
next if test ?d, f
|
312
|
-
next if f =~ /(swp|~|rej|orig)$/
|
328
|
+
next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
|
313
329
|
next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
|
314
330
|
|
315
331
|
filename = f.sub(/^\.\//, '')
|
@@ -320,73 +336,83 @@ class Autotest
|
|
320
336
|
return result
|
321
337
|
end
|
322
338
|
|
323
|
-
|
324
|
-
# the
|
325
|
-
# the files to test. Returns true
|
326
|
-
# newer than the previously recorded most recent
|
339
|
+
##
|
340
|
+
# Find the files which have been modified, update the recorded
|
341
|
+
# timestamps, and use this to update the files to test. Returns true
|
342
|
+
# if any file is newer than the previously recorded most recent
|
327
343
|
# file.
|
344
|
+
|
328
345
|
def find_files_to_test(files=find_files)
|
329
346
|
updated = files.select { |filename, mtime|
|
330
|
-
|
347
|
+
self.files[filename] < mtime
|
331
348
|
}
|
332
349
|
|
333
|
-
p updated if $v unless updated.empty? or
|
350
|
+
p updated if $v unless updated.empty? or self.last_mtime.to_i == 0
|
334
351
|
|
335
352
|
# TODO: keep an mtime at app level and drop the files hash
|
336
353
|
updated.each do |filename, mtime|
|
337
|
-
|
354
|
+
self.files[filename] = mtime
|
338
355
|
end
|
339
356
|
|
340
357
|
updated.each do |filename, mtime|
|
341
358
|
tests_for_file(filename).each do |f|
|
342
|
-
|
359
|
+
self.files_to_test[f] # creates key with default value
|
343
360
|
end
|
344
361
|
end
|
345
362
|
|
346
|
-
previous =
|
347
|
-
|
348
|
-
|
363
|
+
previous = self.last_mtime
|
364
|
+
self.last_mtime = self.files.values.max
|
365
|
+
self.last_mtime > previous
|
349
366
|
end
|
350
367
|
|
351
|
-
|
352
|
-
#
|
368
|
+
##
|
369
|
+
# Check results for failures, set the "bar" to red or green, and if
|
370
|
+
# there are failures record this.
|
371
|
+
|
353
372
|
def handle_results(results)
|
354
373
|
failed = results.scan(/^\s+\d+\) (?:Failure|Error):\n(.*?)\((.*?)\)/)
|
355
374
|
completed = results =~ /\d+ tests, \d+ assertions, \d+ failures, \d+ errors/
|
356
375
|
|
357
|
-
|
376
|
+
self.files_to_test = consolidate_failures failed if completed
|
358
377
|
|
359
|
-
hook completed &&
|
378
|
+
hook completed && self.files_to_test.empty? ? :green : :red unless $TESTING
|
360
379
|
|
361
|
-
|
380
|
+
self.tainted = true unless self.files_to_test.empty?
|
362
381
|
end
|
363
382
|
|
383
|
+
##
|
364
384
|
# Generate the commands to test the supplied files
|
385
|
+
|
365
386
|
def make_test_cmd files_to_test
|
366
387
|
cmds = []
|
367
388
|
full, partial = files_to_test.partition { |k,v| v.empty? }
|
368
389
|
|
369
390
|
unless full.empty? then
|
370
391
|
classes = full.map {|k,v| k}.flatten.uniq.sort.join(' ')
|
371
|
-
cmds << "#{ruby} -I#{
|
392
|
+
cmds << "#{ruby} -I#{libs} -rtest/unit -e \"%w[#{classes}].each { |f| require f }\" | #{unit_diff}"
|
372
393
|
end
|
373
394
|
|
374
395
|
partial.each do |klass, methods|
|
375
|
-
|
396
|
+
regexp = Regexp.union(*methods).source
|
397
|
+
cmds << "#{ruby} -I#{libs} #{klass} -n \"/^(#{regexp})$/\" | #{unit_diff}"
|
376
398
|
end
|
377
399
|
|
378
400
|
return cmds.join("#{SEP} ")
|
379
401
|
end
|
380
402
|
|
403
|
+
##
|
381
404
|
# Rerun the tests from cold (reset state)
|
405
|
+
|
382
406
|
def rerun_all_tests
|
383
407
|
reset
|
384
408
|
run_tests
|
385
409
|
hook :all_good if all_good
|
386
410
|
end
|
387
411
|
|
388
|
-
|
389
|
-
#
|
412
|
+
##
|
413
|
+
# Clear all state information about test failures and whether
|
414
|
+
# interrupts will kill autotest.
|
415
|
+
|
390
416
|
def reset
|
391
417
|
@interrupted = @wants_to_quit = false
|
392
418
|
@files.clear
|
@@ -397,7 +423,9 @@ class Autotest
|
|
397
423
|
hook :reset
|
398
424
|
end
|
399
425
|
|
400
|
-
|
426
|
+
##
|
427
|
+
# Determine and return the path of the ruby executable.
|
428
|
+
|
401
429
|
def ruby
|
402
430
|
ruby = File.join(Config::CONFIG['bindir'],
|
403
431
|
Config::CONFIG['ruby_install_name'])
|
@@ -409,20 +437,23 @@ class Autotest
|
|
409
437
|
return ruby
|
410
438
|
end
|
411
439
|
|
440
|
+
##
|
441
|
+
# Return the name of the file with the tests for filename by finding
|
442
|
+
# a +test_mapping+ that matches the file and executing the mapping's
|
443
|
+
# proc.
|
412
444
|
|
413
|
-
# Return the name of the file with the tests for
|
414
|
-
# filename.
|
415
445
|
def tests_for_file(filename)
|
416
446
|
result = @test_mappings.find { |file_re, ignored| filename =~ file_re }
|
417
447
|
result = result.nil? ? [] : Array(result.last.call(filename, $~))
|
418
448
|
|
419
|
-
|
449
|
+
output.puts "Dunno! #{filename}" if ($v or $TESTING) and result.empty?
|
420
450
|
|
421
451
|
result.sort.uniq
|
422
452
|
end
|
423
453
|
|
424
|
-
|
425
|
-
# are some.
|
454
|
+
##
|
455
|
+
# Sleep then look for files to test, until there are some.
|
456
|
+
|
426
457
|
def wait_for_changes
|
427
458
|
hook :waiting
|
428
459
|
begin
|
@@ -430,30 +461,115 @@ class Autotest
|
|
430
461
|
end until find_files_to_test
|
431
462
|
end
|
432
463
|
|
464
|
+
############################################################
|
465
|
+
# File Mappings:
|
466
|
+
|
467
|
+
##
|
468
|
+
# Returns all known files in the codebase matching +regexp+.
|
469
|
+
|
433
470
|
def files_matching regexp
|
434
471
|
@files.keys.select { |k|
|
435
472
|
k =~ regexp
|
436
473
|
}
|
437
474
|
end
|
438
475
|
|
476
|
+
##
|
477
|
+
# Adds a file mapping. +regexp+ should match a file path in the
|
478
|
+
# codebase. +proc+ is passed a matched filename and
|
479
|
+
# Regexp.last_match. +proc+ should return an array of tests to run.
|
480
|
+
#
|
481
|
+
# For example, if test_helper.rb is modified, rerun all tests:
|
482
|
+
#
|
483
|
+
# at.add_mapping(/test_helper.rb/) do |f, _|
|
484
|
+
# at.files_matching(/^test.*rb$/)
|
485
|
+
# end
|
486
|
+
|
487
|
+
def add_mapping(regexp, &proc)
|
488
|
+
@test_mappings[regexp] = proc
|
489
|
+
end
|
490
|
+
|
491
|
+
##
|
492
|
+
# Removed a file mapping matching +regexp+.
|
493
|
+
|
494
|
+
def remove_mapping regexp
|
495
|
+
@test_mappings.delete regexp
|
496
|
+
end
|
497
|
+
|
498
|
+
##
|
499
|
+
# Clears all file mappings. This is DANGEROUS as it entirely
|
500
|
+
# disables autotest. You must add at least one file mapping that
|
501
|
+
# does a good job of rerunning appropriate tests.
|
502
|
+
|
503
|
+
def clear_mappings
|
504
|
+
@test_mappings.clear
|
505
|
+
end
|
506
|
+
|
507
|
+
############################################################
|
508
|
+
# Exceptions:
|
509
|
+
|
510
|
+
##
|
511
|
+
# Adds +regexp+ to the list of exceptions for find_file. This must
|
512
|
+
# be called _before_ the exceptions are compiled.
|
513
|
+
|
514
|
+
def add_exception regexp
|
515
|
+
raise "exceptions already compiled" if defined? @exceptions
|
516
|
+
@exception_list << regexp
|
517
|
+
end
|
518
|
+
|
519
|
+
##
|
520
|
+
# Removes +regexp+ to the list of exceptions for find_file. This
|
521
|
+
# must be called _before_ the exceptions are compiled.
|
522
|
+
|
523
|
+
def remove_exception regexp
|
524
|
+
raise "exceptions already compiled" if defined? @exceptions
|
525
|
+
@exception_list.delete regexp
|
526
|
+
end
|
527
|
+
|
528
|
+
##
|
529
|
+
# Clears the list of exceptions for find_file. This must be called
|
530
|
+
# _before_ the exceptions are compiled.
|
531
|
+
|
532
|
+
def clear_exceptions
|
533
|
+
raise "exceptions already compiled" if defined? @exceptions
|
534
|
+
@exception_list.clear
|
535
|
+
end
|
536
|
+
|
537
|
+
##
|
538
|
+
# Return a compiled regexp of exceptions for find_files or nil if no
|
539
|
+
# filtering should take place. This regexp is generated from
|
540
|
+
# @exception_list.
|
541
|
+
|
542
|
+
def exceptions
|
543
|
+
unless defined? @exceptions then
|
544
|
+
if @exception_list.empty? then
|
545
|
+
@exceptions = nil
|
546
|
+
else
|
547
|
+
@exceptions = Regexp.union(*@exception_list)
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
@exceptions
|
552
|
+
end
|
553
|
+
|
439
554
|
############################################################
|
440
555
|
# Hooks:
|
441
556
|
|
557
|
+
##
|
558
|
+
# Call the event hook named +name+, executing all registered hooks
|
559
|
+
# until one returns true. Returns false if no hook handled the
|
560
|
+
# event.
|
561
|
+
|
442
562
|
def hook(name)
|
443
563
|
HOOKS[name].inject(false) do |handled,plugin|
|
444
564
|
plugin[self] || handled
|
445
565
|
end
|
446
566
|
end
|
447
567
|
|
448
|
-
|
449
|
-
# the given
|
568
|
+
##
|
569
|
+
# Add the supplied block to the available hooks, with the given
|
570
|
+
# name.
|
571
|
+
|
450
572
|
def self.add_hook(name, &block)
|
451
573
|
HOOKS[name] << block
|
452
574
|
end
|
453
575
|
end
|
454
|
-
|
455
|
-
if test ?f, './.autotest' then
|
456
|
-
load './.autotest'
|
457
|
-
elsif test ?f, File.expand_path('~/.autotest') then
|
458
|
-
load File.expand_path('~/.autotest')
|
459
|
-
end
|