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