ZenTest 4.11.0 → 4.12.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.autotest +0 -1
- data/History.txt +31 -0
- data/Manifest.txt +0 -15
- data/README.txt +4 -0
- data/Rakefile +1 -1
- data/bin/multigem +2 -1
- data/bin/multiruby +43 -18
- data/lib/zentest.rb +1 -1
- data/test/test_zentest.rb +11 -4
- data.tar.gz.sig +0 -0
- metadata +39 -69
- metadata.gz.sig +0 -0
- data/.gemtest +0 -0
- data/articles/getting_started_with_autotest.html +0 -533
- data/bin/autotest +0 -6
- data/bin/multiruby_setup +0 -74
- data/example_dot_autotest.rb +0 -16
- data/lib/autotest/autoupdate.rb +0 -26
- data/lib/autotest/bundler.rb +0 -10
- data/lib/autotest/isolate.rb +0 -19
- data/lib/autotest/once.rb +0 -9
- data/lib/autotest/preload.rb +0 -56
- data/lib/autotest/rcov.rb +0 -27
- data/lib/autotest/restart.rb +0 -16
- data/lib/autotest/timestamp.rb +0 -9
- data/lib/autotest.rb +0 -901
- data/lib/multiruby.rb +0 -413
- data/test/test_autotest.rb +0 -560
data/lib/autotest.rb
DELETED
@@ -1,901 +0,0 @@
|
|
1
|
-
require "find"
|
2
|
-
require "rbconfig"
|
3
|
-
|
4
|
-
##
|
5
|
-
# Autotest continuously scans the files in your project for changes
|
6
|
-
# and runs the appropriate tests. Test failures are run until they
|
7
|
-
# have all passed. Then the full test suite is run to ensure that
|
8
|
-
# nothing else was inadvertantly broken.
|
9
|
-
#
|
10
|
-
# If you want Autotest to start over from the top, hit ^C once. If
|
11
|
-
# you want Autotest to quit, hit ^C twice.
|
12
|
-
#
|
13
|
-
# Rails:
|
14
|
-
#
|
15
|
-
# The autotest command will automatically discover a Rails directory
|
16
|
-
# by looking for config/environment.rb. When Rails is discovered,
|
17
|
-
# autotest uses RailsAutotest to perform file mappings and other work.
|
18
|
-
# See RailsAutotest for details.
|
19
|
-
#
|
20
|
-
# Plugins:
|
21
|
-
#
|
22
|
-
# Plugins are available by creating a .autotest file either in your
|
23
|
-
# project root or in your home directory. You can then write event
|
24
|
-
# handlers in the form of:
|
25
|
-
#
|
26
|
-
# Autotest.add_hook hook_name { |autotest| ... }
|
27
|
-
#
|
28
|
-
# The available hooks are listed in +ALL_HOOKS+.
|
29
|
-
#
|
30
|
-
# See example_dot_autotest.rb for more details.
|
31
|
-
#
|
32
|
-
# If a hook returns a true value, it signals to autotest that the hook
|
33
|
-
# was handled and should not continue executing hooks.
|
34
|
-
#
|
35
|
-
# Naming:
|
36
|
-
#
|
37
|
-
# Autotest uses a simple naming scheme to figure out how to map
|
38
|
-
# implementation files to test files following the Test::Unit naming
|
39
|
-
# scheme.
|
40
|
-
#
|
41
|
-
# * Test files must be stored in test/
|
42
|
-
# * Test files names must start with test_
|
43
|
-
# * Test class names must start with Test
|
44
|
-
# * Implementation files must be stored in lib/
|
45
|
-
# * Implementation files must match up with a test file named
|
46
|
-
# test_.*<impl-name>.rb
|
47
|
-
#
|
48
|
-
# Strategy:
|
49
|
-
#
|
50
|
-
# 1. Find all files and associate them from impl <-> test.
|
51
|
-
# 2. Run all tests.
|
52
|
-
# 3. Scan for failures.
|
53
|
-
# 4. Detect changes in ANY (ruby?. file, rerun all failures + changed files.
|
54
|
-
# 5. Until 0 defects, goto 3.
|
55
|
-
# 6. When 0 defects, goto 2.
|
56
|
-
|
57
|
-
class Autotest
|
58
|
-
|
59
|
-
RUBY19 = defined? Encoding
|
60
|
-
|
61
|
-
T0 = Time.at 0
|
62
|
-
|
63
|
-
ALL_HOOKS = [ :all_good, :died, :green, :initialize,
|
64
|
-
:post_initialize, :interrupt, :quit, :ran_command,
|
65
|
-
:red, :reset, :run_command, :updated, :waiting ]
|
66
|
-
|
67
|
-
def self.options
|
68
|
-
@@options ||= {}
|
69
|
-
end
|
70
|
-
|
71
|
-
def options
|
72
|
-
self.class.options
|
73
|
-
end
|
74
|
-
|
75
|
-
HOOKS = Hash.new { |h,k| h[k] = [] }
|
76
|
-
|
77
|
-
unless defined? WINDOZE then
|
78
|
-
WINDOZE = /mswin|mingw/ =~ RbConfig::CONFIG['host_os']
|
79
|
-
SEP = WINDOZE ? '&' : ';'
|
80
|
-
end
|
81
|
-
|
82
|
-
@@discoveries = []
|
83
|
-
|
84
|
-
def self.parse_options args = ARGV
|
85
|
-
require 'optparse'
|
86
|
-
options = {
|
87
|
-
:args => args.dup
|
88
|
-
}
|
89
|
-
|
90
|
-
OptionParser.new do |opts|
|
91
|
-
opts.banner = <<-BANNER.gsub(/^ /, '')
|
92
|
-
Continuous testing for your ruby app.
|
93
|
-
|
94
|
-
Autotest automatically tests code that has changed. It assumes
|
95
|
-
the code is in lib, and tests are in test/test_*.rb. Autotest
|
96
|
-
uses plugins to control what happens. You configure plugins
|
97
|
-
with require statements in the .autotest file in your
|
98
|
-
project base directory, and a default configuration for all
|
99
|
-
your projects in the .autotest file in your home directory.
|
100
|
-
|
101
|
-
Usage:
|
102
|
-
autotest [options]
|
103
|
-
BANNER
|
104
|
-
|
105
|
-
opts.on "-f", "--fast-start", "Do not run full tests at start" do
|
106
|
-
options[:no_full_after_start] = true
|
107
|
-
end
|
108
|
-
|
109
|
-
opts.on("-c", "--no-full-after-failed",
|
110
|
-
"Do not run all tests on red->green") do
|
111
|
-
options[:no_full_after_failed] = true
|
112
|
-
end
|
113
|
-
|
114
|
-
opts.on "-d", "--debug", "Debug mode, for reporting bugs." do
|
115
|
-
require "pp"
|
116
|
-
options[:debug] = true
|
117
|
-
end
|
118
|
-
|
119
|
-
opts.on "-v", "--verbose", "Be annoyingly verbose (debugs .autotest)." do
|
120
|
-
options[:verbose] = true
|
121
|
-
end
|
122
|
-
|
123
|
-
opts.on "-q", "--quiet", "Be quiet." do
|
124
|
-
options[:quiet] = true
|
125
|
-
end
|
126
|
-
|
127
|
-
opts.on("-r", "--rc CONF", String, "Override path to config file") do |o|
|
128
|
-
options[:rc] = Array(o)
|
129
|
-
end
|
130
|
-
|
131
|
-
opts.on("-s", "--style STYLE", String,
|
132
|
-
"Manually specify test style. (default: autodiscover)") do |style|
|
133
|
-
options[:style] = Array(style)
|
134
|
-
end
|
135
|
-
|
136
|
-
opts.on("-w", "--warnings", "Turn on ruby warnings") do
|
137
|
-
$-w = true
|
138
|
-
end
|
139
|
-
|
140
|
-
opts.on "-h", "--help", "Show this." do
|
141
|
-
puts opts
|
142
|
-
exit 1
|
143
|
-
end
|
144
|
-
end.parse! args
|
145
|
-
|
146
|
-
Autotest.options.merge! options
|
147
|
-
|
148
|
-
options
|
149
|
-
end
|
150
|
-
|
151
|
-
##
|
152
|
-
# Calculates the autotest runner to use to run the tests.
|
153
|
-
#
|
154
|
-
# Can be overridden with --style, otherwise uses ::autodiscover.
|
155
|
-
|
156
|
-
def self.runner
|
157
|
-
style = options[:style] || Autotest.autodiscover
|
158
|
-
target = Autotest
|
159
|
-
|
160
|
-
unless style.empty? then
|
161
|
-
mod = "autotest/#{style.join "_"}"
|
162
|
-
puts "loading #{mod}"
|
163
|
-
begin
|
164
|
-
require mod
|
165
|
-
rescue LoadError
|
166
|
-
abort "Autotest style #{mod} doesn't seem to exist. Aborting."
|
167
|
-
end
|
168
|
-
target = Autotest.const_get(style.map {|s| s.capitalize}.join)
|
169
|
-
end
|
170
|
-
|
171
|
-
target
|
172
|
-
end
|
173
|
-
|
174
|
-
##
|
175
|
-
# Add a proc to the collection of discovery procs. See
|
176
|
-
# +autodiscover+.
|
177
|
-
|
178
|
-
def self.add_discovery &proc
|
179
|
-
@@discoveries << proc
|
180
|
-
end
|
181
|
-
|
182
|
-
##
|
183
|
-
# Automatically find all potential autotest runner styles by
|
184
|
-
# searching your loadpath, vendor/plugins, and rubygems for
|
185
|
-
# "autotest/discover.rb". If found, that file is loaded and it
|
186
|
-
# should register discovery procs with autotest using
|
187
|
-
# +add_discovery+. That proc should return one or more strings
|
188
|
-
# describing the user's current environment. Those styles are then
|
189
|
-
# combined to dynamically invoke an autotest plugin to suite your
|
190
|
-
# environment. That plugin should define a subclass of Autotest with
|
191
|
-
# a corresponding name.
|
192
|
-
#
|
193
|
-
# === Process:
|
194
|
-
#
|
195
|
-
# 1. All autotest/discover.rb files loaded.
|
196
|
-
# 2. Those procs determine your styles (eg ["rails", "rspec"]).
|
197
|
-
# 3. Require file by sorting styles and joining (eg 'autotest/rails_rspec').
|
198
|
-
# 4. Invoke run method on appropriate class (eg Autotest::RailsRspec.run).
|
199
|
-
#
|
200
|
-
# === Example autotest/discover.rb:
|
201
|
-
#
|
202
|
-
# Autotest.add_discovery do
|
203
|
-
# "rails" if File.exist? 'config/environment.rb'
|
204
|
-
# end
|
205
|
-
#
|
206
|
-
|
207
|
-
def self.autodiscover
|
208
|
-
require 'rubygems'
|
209
|
-
|
210
|
-
# *sigh*
|
211
|
-
#
|
212
|
-
# This is needed for rspec's hacky discovery mechanism. For some
|
213
|
-
# reason rspec2 added generators that create
|
214
|
-
# "autotest/discover.rb" right in the project directory instead of
|
215
|
-
# keeping it in the rspec gem and properly deciding that the
|
216
|
-
# project is an rspec based project or not. See the url for more
|
217
|
-
# details:
|
218
|
-
#
|
219
|
-
# http://rubyforge.org/tracker/?func=detail&atid=1678&aid=28775&group_id=419
|
220
|
-
#
|
221
|
-
# For the record, the sane way to do it is the bacon way:
|
222
|
-
#
|
223
|
-
# "Since version 1.0, there is autotest support. You need to tag
|
224
|
-
# your test directories (test/ or spec/) by creating an .bacon
|
225
|
-
# file there. Autotest then will find it."
|
226
|
-
#
|
227
|
-
# I'm submitting a counter-patch to rspec to fix stuff properly,
|
228
|
-
# but for now I'm stuck with this because their brokenness is
|
229
|
-
# documented in multiple books.
|
230
|
-
#
|
231
|
-
# I'm removing this code once a sane rspec goes out.
|
232
|
-
|
233
|
-
hacky_discovery = Gem::Specification.any? { |s| s.name =~ /^rspec/ }
|
234
|
-
$: << '.' if hacky_discovery
|
235
|
-
|
236
|
-
Gem.find_files("autotest/discover").each do |f|
|
237
|
-
load f
|
238
|
-
end
|
239
|
-
|
240
|
-
# call all discovery procs and determine the style to use
|
241
|
-
@@discoveries.map{ |proc| proc.call }.flatten.compact.sort.uniq
|
242
|
-
end
|
243
|
-
|
244
|
-
##
|
245
|
-
# Initialize and run the system.
|
246
|
-
|
247
|
-
def self.run
|
248
|
-
new.run
|
249
|
-
end
|
250
|
-
|
251
|
-
attr_writer :known_files
|
252
|
-
attr_accessor :completed_re
|
253
|
-
attr_accessor :extra_class_map
|
254
|
-
attr_accessor :extra_files
|
255
|
-
attr_accessor :failed_results_re
|
256
|
-
attr_accessor :files_to_test
|
257
|
-
attr_accessor :find_directories
|
258
|
-
attr_accessor :find_order
|
259
|
-
attr_accessor :interrupted
|
260
|
-
attr_accessor :last_mtime
|
261
|
-
attr_accessor :latest_results
|
262
|
-
attr_accessor :libs
|
263
|
-
attr_accessor :order # TODO: deprecate and remove
|
264
|
-
attr_accessor :output
|
265
|
-
attr_accessor :prefix
|
266
|
-
attr_accessor :results
|
267
|
-
attr_accessor :sleep
|
268
|
-
attr_accessor :tainted
|
269
|
-
attr_accessor :test_mappings
|
270
|
-
attr_accessor :testlib
|
271
|
-
attr_accessor :testprefix
|
272
|
-
attr_accessor :unit_diff
|
273
|
-
attr_accessor :wants_to_quit
|
274
|
-
|
275
|
-
alias tainted? tainted
|
276
|
-
|
277
|
-
##
|
278
|
-
# Initialize the instance and then load the user's .autotest file, if any.
|
279
|
-
|
280
|
-
def initialize
|
281
|
-
# these two are set directly because they're wrapped with
|
282
|
-
# add/remove/clear accessor methods
|
283
|
-
@exception_list = []
|
284
|
-
@child = nil
|
285
|
-
self.test_mappings = []
|
286
|
-
|
287
|
-
self.completed_re =
|
288
|
-
/\d+ (tests|runs), \d+ assertions, \d+ failures, \d+ errors(, \d+ skips)?/
|
289
|
-
self.extra_class_map = {}
|
290
|
-
self.extra_files = []
|
291
|
-
self.failed_results_re = /^\s*\d+\) (?:Failure|Error):\n(.*?)(?: [\(\[](.*?)[\)\]])?:$/
|
292
|
-
self.files_to_test = new_hash_of_arrays
|
293
|
-
self.find_order = []
|
294
|
-
self.known_files = nil
|
295
|
-
self.libs = %w[. lib test].join(File::PATH_SEPARATOR)
|
296
|
-
self.order = :random
|
297
|
-
self.output = $stderr
|
298
|
-
self.prefix = nil
|
299
|
-
self.sleep = 1
|
300
|
-
self.testlib = "minitest/autorun" # TODO: rename
|
301
|
-
self.testprefix = "gem 'minitest'" # TODO: rename
|
302
|
-
|
303
|
-
specified_directories = ARGV.reject { |arg| arg.start_with?("-") } # options are not directories
|
304
|
-
self.find_directories = specified_directories.empty? ? ['.'] : specified_directories
|
305
|
-
self.unit_diff = nil
|
306
|
-
self.latest_results = nil
|
307
|
-
|
308
|
-
# file in /lib -> run test in /test
|
309
|
-
self.add_mapping(/^lib\/.*\.rb$/) do |filename, _|
|
310
|
-
possible = File.basename(filename).gsub '_', '_?'
|
311
|
-
files_matching %r%^test/.*#{possible}$%
|
312
|
-
end
|
313
|
-
|
314
|
-
# file in /test -> run it
|
315
|
-
self.add_mapping(/^test.*\/test_.*rb$/) do |filename, _|
|
316
|
-
filename
|
317
|
-
end
|
318
|
-
|
319
|
-
default_configs = [File.expand_path('~/.autotest'), './.autotest']
|
320
|
-
configs = options[:rc] || default_configs
|
321
|
-
|
322
|
-
configs.each do |f|
|
323
|
-
load f if File.exist? f
|
324
|
-
end
|
325
|
-
end
|
326
|
-
|
327
|
-
def debug
|
328
|
-
find_files_to_test
|
329
|
-
|
330
|
-
puts "Known test files:"
|
331
|
-
puts
|
332
|
-
pp files_to_test.keys.sort
|
333
|
-
|
334
|
-
class_map = self.class_map
|
335
|
-
|
336
|
-
puts
|
337
|
-
puts "Known class map:"
|
338
|
-
puts
|
339
|
-
pp class_map
|
340
|
-
end
|
341
|
-
|
342
|
-
def class_map
|
343
|
-
class_map = Hash[*self.find_order.grep(/^test/).map { |f| # TODO: ugly
|
344
|
-
[path_to_classname(f), f]
|
345
|
-
}.flatten]
|
346
|
-
class_map.merge! self.extra_class_map
|
347
|
-
class_map
|
348
|
-
end
|
349
|
-
|
350
|
-
##
|
351
|
-
# Repeatedly run failed tests, then all tests, then wait for changes
|
352
|
-
# and carry on until killed.
|
353
|
-
|
354
|
-
def run
|
355
|
-
hook :initialize
|
356
|
-
hook :post_initialize
|
357
|
-
|
358
|
-
reset
|
359
|
-
add_sigint_handler
|
360
|
-
|
361
|
-
self.last_mtime = Time.now if options[:no_full_after_start]
|
362
|
-
|
363
|
-
self.debug if options[:debug]
|
364
|
-
|
365
|
-
loop do
|
366
|
-
begin # ^c handler
|
367
|
-
get_to_green
|
368
|
-
if tainted? and not options[:no_full_after_failed] then
|
369
|
-
rerun_all_tests
|
370
|
-
else
|
371
|
-
hook :all_good
|
372
|
-
end
|
373
|
-
wait_for_changes
|
374
|
-
rescue Interrupt
|
375
|
-
break if wants_to_quit
|
376
|
-
reset
|
377
|
-
end
|
378
|
-
end
|
379
|
-
hook :quit
|
380
|
-
puts
|
381
|
-
rescue Exception => err
|
382
|
-
hook(:died, err) or (
|
383
|
-
warn "Unhandled exception: #{err}"
|
384
|
-
warn err.backtrace.join("\n ")
|
385
|
-
warn "Quitting"
|
386
|
-
)
|
387
|
-
end
|
388
|
-
|
389
|
-
##
|
390
|
-
# Keep running the tests after a change, until all pass.
|
391
|
-
|
392
|
-
def get_to_green
|
393
|
-
begin
|
394
|
-
run_tests
|
395
|
-
wait_for_changes unless all_good
|
396
|
-
end until all_good
|
397
|
-
end
|
398
|
-
|
399
|
-
##
|
400
|
-
# Look for files to test then run the tests and handle the results.
|
401
|
-
|
402
|
-
def run_tests
|
403
|
-
new_mtime = self.find_files_to_test
|
404
|
-
return unless new_mtime
|
405
|
-
self.last_mtime = new_mtime
|
406
|
-
|
407
|
-
cmd = self.make_test_cmd self.files_to_test
|
408
|
-
return if cmd.empty?
|
409
|
-
|
410
|
-
hook :run_command, cmd
|
411
|
-
|
412
|
-
puts cmd unless options[:quiet]
|
413
|
-
|
414
|
-
old_sync = $stdout.sync
|
415
|
-
$stdout.sync = true
|
416
|
-
self.results = []
|
417
|
-
line = []
|
418
|
-
begin
|
419
|
-
open "| #{cmd}", "r" do |f|
|
420
|
-
until f.eof? do
|
421
|
-
c = f.getc or break
|
422
|
-
if RUBY19 then
|
423
|
-
print c
|
424
|
-
else
|
425
|
-
putc c
|
426
|
-
end
|
427
|
-
line << c
|
428
|
-
if c == ?\n then
|
429
|
-
self.results << if RUBY19 then
|
430
|
-
line.join
|
431
|
-
else
|
432
|
-
line.pack "c*"
|
433
|
-
end
|
434
|
-
line.clear
|
435
|
-
end
|
436
|
-
end
|
437
|
-
end
|
438
|
-
ensure
|
439
|
-
$stdout.sync = old_sync
|
440
|
-
end
|
441
|
-
hook :ran_command
|
442
|
-
self.results = self.results.join
|
443
|
-
|
444
|
-
handle_results self.results
|
445
|
-
end
|
446
|
-
|
447
|
-
############################################################
|
448
|
-
# Utility Methods, not essential to reading of logic
|
449
|
-
|
450
|
-
##
|
451
|
-
# Installs a sigint handler.
|
452
|
-
|
453
|
-
def add_sigint_handler
|
454
|
-
trap 'INT' do
|
455
|
-
Process.kill "KILL", @child if @child
|
456
|
-
|
457
|
-
if self.interrupted then
|
458
|
-
self.wants_to_quit = true
|
459
|
-
else
|
460
|
-
unless hook :interrupt then
|
461
|
-
puts "Interrupt a second time to quit"
|
462
|
-
self.interrupted = true
|
463
|
-
Kernel.sleep 1.5
|
464
|
-
end
|
465
|
-
raise Interrupt, nil # let the run loop catch it
|
466
|
-
end
|
467
|
-
end
|
468
|
-
end
|
469
|
-
|
470
|
-
##
|
471
|
-
# Installs a sigquit handler
|
472
|
-
|
473
|
-
def add_sigquit_handler
|
474
|
-
trap 'QUIT' do
|
475
|
-
restart
|
476
|
-
end
|
477
|
-
end
|
478
|
-
|
479
|
-
def restart
|
480
|
-
Process.kill "KILL", @child if @child
|
481
|
-
|
482
|
-
cmd = [$0, *options[:args]]
|
483
|
-
|
484
|
-
index = $LOAD_PATH.index RbConfig::CONFIG["sitelibdir"]
|
485
|
-
|
486
|
-
if index then
|
487
|
-
extra = $LOAD_PATH[0...index]
|
488
|
-
cmd = [Gem.ruby, "-I", extra.join(":")] + cmd
|
489
|
-
end
|
490
|
-
|
491
|
-
puts cmd.join(" ") if options[:verbose]
|
492
|
-
|
493
|
-
exec(*cmd)
|
494
|
-
end
|
495
|
-
|
496
|
-
##
|
497
|
-
# If there are no files left to test (because they've all passed),
|
498
|
-
# then all is good.
|
499
|
-
|
500
|
-
def all_good
|
501
|
-
files_to_test.empty?
|
502
|
-
end
|
503
|
-
|
504
|
-
##
|
505
|
-
# Convert a path in a string, s, into a class name, changing
|
506
|
-
# underscores to CamelCase, etc.
|
507
|
-
|
508
|
-
def path_to_classname s
|
509
|
-
sep = File::SEPARATOR
|
510
|
-
f = s.sub(/^test#{sep}/, '').sub(/\.rb$/, '').split sep
|
511
|
-
f = f.map { |path| path.split(/_|(\d+)/).map { |seg| seg.capitalize }.join }
|
512
|
-
f = f.map { |path| path =~ /^Test/ ? path : "Test#{path}" }
|
513
|
-
|
514
|
-
f.join '::'
|
515
|
-
end
|
516
|
-
|
517
|
-
##
|
518
|
-
# Returns a hash mapping a file name to the known failures for that
|
519
|
-
# file.
|
520
|
-
|
521
|
-
def consolidate_failures failed
|
522
|
-
filters = new_hash_of_arrays
|
523
|
-
|
524
|
-
class_map = self.class_map
|
525
|
-
|
526
|
-
failed.each do |method, klass|
|
527
|
-
if class_map.has_key? klass then
|
528
|
-
filters[class_map[klass]] << method
|
529
|
-
else
|
530
|
-
output.puts "Unable to map class #{klass} to a file"
|
531
|
-
end
|
532
|
-
end
|
533
|
-
|
534
|
-
filters
|
535
|
-
end
|
536
|
-
|
537
|
-
##
|
538
|
-
# Find the files to process, ignoring temporary files, source
|
539
|
-
# configuration management files, etc., and return a Hash mapping
|
540
|
-
# filename to modification time.
|
541
|
-
|
542
|
-
def find_files
|
543
|
-
result = {}
|
544
|
-
targets = self.find_directories + self.extra_files
|
545
|
-
self.find_order.clear
|
546
|
-
|
547
|
-
targets.each do |target|
|
548
|
-
order = []
|
549
|
-
Find.find target do |f|
|
550
|
-
Find.prune if f =~ self.exceptions
|
551
|
-
Find.prune if f =~ /^\.\/tmp/ # temp dir, used by isolate
|
552
|
-
|
553
|
-
next unless File.file? f
|
554
|
-
next if f =~ /(swp|~|rej|orig)$/ # temporary/patch files
|
555
|
-
next if f =~ /(,v)$/ # RCS files
|
556
|
-
next if f =~ /\/\.?#/ # Emacs autosave/cvs merge files
|
557
|
-
|
558
|
-
filename = f.sub(/^\.\//, '')
|
559
|
-
|
560
|
-
result[filename] = File.stat(filename).mtime rescue next
|
561
|
-
order << filename
|
562
|
-
end
|
563
|
-
self.find_order.push(*order.sort)
|
564
|
-
end
|
565
|
-
|
566
|
-
result
|
567
|
-
end
|
568
|
-
|
569
|
-
##
|
570
|
-
# Find the files which have been modified, update the recorded
|
571
|
-
# timestamps, and use this to update the files to test. Returns
|
572
|
-
# the latest mtime of the files modified or nil when nothing was
|
573
|
-
# modified.
|
574
|
-
|
575
|
-
def find_files_to_test files = find_files
|
576
|
-
updated = files.select { |filename, mtime| self.last_mtime < mtime }
|
577
|
-
|
578
|
-
# nothing to update or initially run
|
579
|
-
unless updated.empty? || self.last_mtime.to_i == 0 then
|
580
|
-
p updated if options[:verbose]
|
581
|
-
|
582
|
-
hook :updated, updated
|
583
|
-
end
|
584
|
-
|
585
|
-
updated.map { |f,m| test_files_for f }.flatten.uniq.each do |filename|
|
586
|
-
self.files_to_test[filename] # creates key with default value
|
587
|
-
end
|
588
|
-
|
589
|
-
if updated.empty? then
|
590
|
-
nil
|
591
|
-
else
|
592
|
-
files.values.max
|
593
|
-
end
|
594
|
-
end
|
595
|
-
|
596
|
-
##
|
597
|
-
# Check results for failures, set the "bar" to red or green, and if
|
598
|
-
# there are failures record this.
|
599
|
-
|
600
|
-
def handle_results results
|
601
|
-
results = results.gsub(/\e\[\d+m/, '') # strip ascii color
|
602
|
-
failed = results.scan(self.failed_results_re).map { |m, k|
|
603
|
-
k, m = $1, $2 if m =~ /(\w+)\#(\w+)/ # minitest 5 output
|
604
|
-
[m, k]
|
605
|
-
}
|
606
|
-
|
607
|
-
completed = results[self.completed_re]
|
608
|
-
|
609
|
-
if completed then
|
610
|
-
completed = completed.scan(/(\d+) (\w+)/).map { |v, k| [k, v.to_i] }
|
611
|
-
|
612
|
-
self.latest_results = Hash[*completed.flatten]
|
613
|
-
self.files_to_test = consolidate_failures failed
|
614
|
-
|
615
|
-
color = failed.empty? ? :green : :red
|
616
|
-
hook color
|
617
|
-
else
|
618
|
-
self.latest_results = nil
|
619
|
-
end
|
620
|
-
|
621
|
-
self.tainted = true unless self.files_to_test.empty?
|
622
|
-
end
|
623
|
-
|
624
|
-
##
|
625
|
-
# Lazy accessor for the known_files hash.
|
626
|
-
|
627
|
-
def known_files
|
628
|
-
unless @known_files then
|
629
|
-
@known_files = {}
|
630
|
-
find_order.each {|f| @known_files[f] = true }
|
631
|
-
end
|
632
|
-
@known_files
|
633
|
-
end
|
634
|
-
|
635
|
-
##
|
636
|
-
# Generate the commands to test the supplied files
|
637
|
-
|
638
|
-
def make_test_cmd files_to_test
|
639
|
-
if options[:debug] then
|
640
|
-
puts "Files to test:"
|
641
|
-
puts
|
642
|
-
pp files_to_test
|
643
|
-
puts
|
644
|
-
end
|
645
|
-
|
646
|
-
cmds = []
|
647
|
-
full, partial = reorder(files_to_test).partition { |k,v| v.empty? }
|
648
|
-
|
649
|
-
diff = self.unit_diff
|
650
|
-
diff = " | #{diff}" if diff and diff !~ /^\|/
|
651
|
-
|
652
|
-
unless full.empty? then
|
653
|
-
classes = full.map {|k,v| k}.flatten.uniq
|
654
|
-
classes.unshift testlib
|
655
|
-
classes = classes.join " "
|
656
|
-
cmds << "#{ruby_cmd} -e \"#{testprefix}; %w[#{classes}].each { |f| require f }\"#{diff}"
|
657
|
-
end
|
658
|
-
|
659
|
-
partial.each do |klass, methods|
|
660
|
-
regexp = Regexp.union(*methods).source
|
661
|
-
cmds << "#{ruby_cmd} #{klass} -n \"/^(#{regexp})$/\"#{diff}"
|
662
|
-
end
|
663
|
-
|
664
|
-
cmds.join "#{SEP} "
|
665
|
-
end
|
666
|
-
|
667
|
-
def new_hash_of_arrays
|
668
|
-
Hash.new { |h,k| h[k] = [] }
|
669
|
-
end
|
670
|
-
|
671
|
-
def reorder files_to_test
|
672
|
-
case self.order
|
673
|
-
when :alpha then
|
674
|
-
files_to_test.sort_by { |k,v| k }
|
675
|
-
when :reverse then
|
676
|
-
files_to_test.sort_by { |k,v| k }.reverse
|
677
|
-
when :random then
|
678
|
-
max = files_to_test.size
|
679
|
-
files_to_test.sort_by { |k,v| rand max }
|
680
|
-
when :natural then
|
681
|
-
(self.find_order & files_to_test.keys).map { |f| [f, files_to_test[f]] }
|
682
|
-
else
|
683
|
-
raise "unknown order type: #{self.order.inspect}"
|
684
|
-
end
|
685
|
-
end
|
686
|
-
|
687
|
-
##
|
688
|
-
# Rerun the tests from cold (reset state)
|
689
|
-
|
690
|
-
def rerun_all_tests
|
691
|
-
reset
|
692
|
-
run_tests
|
693
|
-
|
694
|
-
hook :all_good if all_good
|
695
|
-
end
|
696
|
-
|
697
|
-
##
|
698
|
-
# Clear all state information about test failures and whether
|
699
|
-
# interrupts will kill autotest.
|
700
|
-
|
701
|
-
def reset
|
702
|
-
self.files_to_test.clear
|
703
|
-
self.find_order.clear
|
704
|
-
|
705
|
-
self.interrupted = false
|
706
|
-
self.known_files = nil
|
707
|
-
self.last_mtime = T0
|
708
|
-
self.tainted = false
|
709
|
-
self.wants_to_quit = false
|
710
|
-
|
711
|
-
hook :reset
|
712
|
-
end
|
713
|
-
|
714
|
-
##
|
715
|
-
# Determine and return the path of the ruby executable.
|
716
|
-
|
717
|
-
def ruby
|
718
|
-
ruby = ENV['RUBY']
|
719
|
-
ruby ||= File.join(RbConfig::CONFIG['bindir'],
|
720
|
-
RbConfig::CONFIG['ruby_install_name'])
|
721
|
-
|
722
|
-
ruby.gsub! File::SEPARATOR, File::ALT_SEPARATOR if File::ALT_SEPARATOR
|
723
|
-
|
724
|
-
return ruby
|
725
|
-
end
|
726
|
-
|
727
|
-
##
|
728
|
-
# Returns the base of the ruby command.
|
729
|
-
|
730
|
-
def ruby_cmd
|
731
|
-
"#{prefix}#{ruby} -I#{libs} -rubygems"
|
732
|
-
end
|
733
|
-
|
734
|
-
##
|
735
|
-
# Return the name of the file with the tests for filename by finding
|
736
|
-
# a +test_mapping+ that matches the file and executing the mapping's
|
737
|
-
# proc.
|
738
|
-
|
739
|
-
def test_files_for filename
|
740
|
-
result = []
|
741
|
-
|
742
|
-
self.test_mappings.each do |file_re, proc|
|
743
|
-
if filename =~ file_re then
|
744
|
-
result = [proc.call(filename, $~)].
|
745
|
-
flatten.sort.uniq.select { |f| known_files[f] }
|
746
|
-
break unless result.empty?
|
747
|
-
end
|
748
|
-
end
|
749
|
-
|
750
|
-
pp :test_file_for => [filename, result.first] if result and options[:debug]
|
751
|
-
|
752
|
-
output.puts "No tests matched #{filename}" if
|
753
|
-
options[:verbose] and result.empty?
|
754
|
-
|
755
|
-
return result
|
756
|
-
end
|
757
|
-
|
758
|
-
##
|
759
|
-
# Sleep then look for files to test, until there are some.
|
760
|
-
|
761
|
-
def wait_for_changes
|
762
|
-
hook :waiting
|
763
|
-
Kernel.sleep self.sleep until find_files_to_test
|
764
|
-
end
|
765
|
-
|
766
|
-
############################################################
|
767
|
-
# File Mappings:
|
768
|
-
|
769
|
-
##
|
770
|
-
# Returns all known files in the codebase matching +regexp+.
|
771
|
-
|
772
|
-
def files_matching regexp
|
773
|
-
self.find_order.select { |k| k =~ regexp }
|
774
|
-
end
|
775
|
-
|
776
|
-
##
|
777
|
-
# Adds a file mapping, optionally prepending the mapping to the
|
778
|
-
# front of the list if +prepend+ is true. +regexp+ should match a
|
779
|
-
# file path in the codebase. +proc+ is passed a matched filename and
|
780
|
-
# Regexp.last_match. +proc+ should return an array of tests to run.
|
781
|
-
#
|
782
|
-
# For example, if test_helper.rb is modified, rerun all tests:
|
783
|
-
#
|
784
|
-
# at.add_mapping(/test_helper.rb/) do |f, _|
|
785
|
-
# at.files_matching(/^test.*rb$/)
|
786
|
-
# end
|
787
|
-
|
788
|
-
def add_mapping regexp, prepend = false, &proc
|
789
|
-
if prepend then
|
790
|
-
@test_mappings.unshift [regexp, proc]
|
791
|
-
else
|
792
|
-
@test_mappings.push [regexp, proc]
|
793
|
-
end
|
794
|
-
nil
|
795
|
-
end
|
796
|
-
|
797
|
-
##
|
798
|
-
# Removed a file mapping matching +regexp+.
|
799
|
-
|
800
|
-
def remove_mapping regexp
|
801
|
-
@test_mappings.delete_if do |k,v|
|
802
|
-
k == regexp
|
803
|
-
end
|
804
|
-
nil
|
805
|
-
end
|
806
|
-
|
807
|
-
##
|
808
|
-
# Clears all file mappings. This is DANGEROUS as it entirely
|
809
|
-
# disables autotest. You must add at least one file mapping that
|
810
|
-
# does a good job of rerunning appropriate tests.
|
811
|
-
|
812
|
-
def clear_mappings
|
813
|
-
@test_mappings.clear
|
814
|
-
nil
|
815
|
-
end
|
816
|
-
|
817
|
-
############################################################
|
818
|
-
# Exceptions:
|
819
|
-
|
820
|
-
##
|
821
|
-
# Adds +regexp+ to the list of exceptions for find_file. This must
|
822
|
-
# be called _before_ the exceptions are compiled.
|
823
|
-
|
824
|
-
def add_exception regexp
|
825
|
-
raise "exceptions already compiled" if defined? @exceptions
|
826
|
-
|
827
|
-
@exception_list << regexp
|
828
|
-
nil
|
829
|
-
end
|
830
|
-
|
831
|
-
##
|
832
|
-
# Removes +regexp+ to the list of exceptions for find_file. This
|
833
|
-
# must be called _before_ the exceptions are compiled.
|
834
|
-
|
835
|
-
def remove_exception regexp
|
836
|
-
raise "exceptions already compiled" if defined? @exceptions
|
837
|
-
@exception_list.delete regexp
|
838
|
-
nil
|
839
|
-
end
|
840
|
-
|
841
|
-
##
|
842
|
-
# Clears the list of exceptions for find_file. This must be called
|
843
|
-
# _before_ the exceptions are compiled.
|
844
|
-
|
845
|
-
def clear_exceptions
|
846
|
-
raise "exceptions already compiled" if defined? @exceptions
|
847
|
-
@exception_list.clear
|
848
|
-
nil
|
849
|
-
end
|
850
|
-
|
851
|
-
##
|
852
|
-
# Return a compiled regexp of exceptions for find_files or nil if no
|
853
|
-
# filtering should take place. This regexp is generated from
|
854
|
-
# +exception_list+.
|
855
|
-
|
856
|
-
def exceptions
|
857
|
-
unless defined? @exceptions then
|
858
|
-
@exceptions = if @exception_list.empty? then
|
859
|
-
nil
|
860
|
-
else
|
861
|
-
Regexp.union(*@exception_list)
|
862
|
-
end
|
863
|
-
end
|
864
|
-
|
865
|
-
@exceptions
|
866
|
-
end
|
867
|
-
|
868
|
-
############################################################
|
869
|
-
# Hooks:
|
870
|
-
|
871
|
-
##
|
872
|
-
# Call the event hook named +name+, passing in optional args
|
873
|
-
# depending on the hook itself.
|
874
|
-
#
|
875
|
-
# Returns false if no hook handled the event.
|
876
|
-
#
|
877
|
-
# === Hook Writers!
|
878
|
-
#
|
879
|
-
# This executes all registered hooks <em>until one returns truthy</em>.
|
880
|
-
# Pay attention to the return value of your block!
|
881
|
-
|
882
|
-
def hook name, *args
|
883
|
-
deprecated = {
|
884
|
-
# none currently
|
885
|
-
}
|
886
|
-
|
887
|
-
if deprecated[name] and not HOOKS[name].empty? then
|
888
|
-
warn "hook #{name} has been deprecated, use #{deprecated[name]}"
|
889
|
-
end
|
890
|
-
|
891
|
-
HOOKS[name].any? { |plugin| plugin[self, *args] }
|
892
|
-
end
|
893
|
-
|
894
|
-
##
|
895
|
-
# Add the supplied block to the available hooks, with the given
|
896
|
-
# name.
|
897
|
-
|
898
|
-
def self.add_hook name, &block
|
899
|
-
HOOKS[name] << block
|
900
|
-
end
|
901
|
-
end
|