test-loop 7.0.1 → 8.0.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/README.md +61 -52
- data/bin/test-loop +165 -170
- metadata +4 -4
data/README.md
CHANGED
|
@@ -8,11 +8,6 @@ detects and tests changes in your application in an efficient manner:
|
|
|
8
8
|
2. Forks to run your test files without overhead and in parallel.
|
|
9
9
|
3. Avoids running unchanged test blocks inside changed test files.
|
|
10
10
|
|
|
11
|
-
It relies on file modification times to determine what parts of your Ruby
|
|
12
|
-
application have changed, applies a lambda mapping function to determine which
|
|
13
|
-
test files in your test suite correspond to those changes, and uses diffing to
|
|
14
|
-
find and run only those test blocks that have changed inside your test files.
|
|
15
|
-
|
|
16
11
|
|
|
17
12
|
> IMPORTANT note for Ruby on Rails users!
|
|
18
13
|
> ---------------------------------------
|
|
@@ -22,7 +17,7 @@ find and run only those test blocks that have changed inside your test files.
|
|
|
22
17
|
> config.cache_classes = false
|
|
23
18
|
>
|
|
24
19
|
> Otherwise, test-loop will appear to ignore class-level changes in your
|
|
25
|
-
> models, controllers, etc.
|
|
20
|
+
> models, controllers, helpers, etc. thereby causing you great frustration!
|
|
26
21
|
|
|
27
22
|
|
|
28
23
|
Features
|
|
@@ -92,67 +87,81 @@ Configuration
|
|
|
92
87
|
-------------
|
|
93
88
|
|
|
94
89
|
test-loop looks for a configuration file named `.test-loop` in the current
|
|
95
|
-
working directory. This configuration file is a Ruby script in which
|
|
96
|
-
can query and modify the `Test::Loop
|
|
97
|
-
|
|
98
|
-
* `Test::Loop
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
* `Test::Loop
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
* `Test::Loop
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
90
|
+
working directory. This configuration file is a normal Ruby script in which
|
|
91
|
+
you can query and modify the `Test::Loop` OpenStruct configuration as follows:
|
|
92
|
+
|
|
93
|
+
* `Test::Loop.overhead_file_globs` is an array of file globbing patterns that
|
|
94
|
+
describe a set of Ruby scripts that are loaded into the main Ruby process as
|
|
95
|
+
overhead.
|
|
96
|
+
|
|
97
|
+
* `Test::Loop.reabsorb_file_globs` is an array of file globbing patterns that
|
|
98
|
+
describe a set of files which cause the overhead to be reabsorbed whenever
|
|
99
|
+
they change.
|
|
100
|
+
|
|
101
|
+
* `Test::Loop.test_file_matchers` is a hash that maps a file globbing pattern
|
|
102
|
+
describing a set of source files to a lambda function yielding a file
|
|
103
|
+
globbing pattern describing a set of test files that need to be run. In
|
|
104
|
+
other words, whenever the source files (the hash key; left-hand side of the
|
|
105
|
+
mapping) change, their associated test files (the hash value; right-hand
|
|
111
106
|
side of the mapping) are run.
|
|
112
107
|
|
|
113
|
-
For example, if test files had the same names as their source files
|
|
114
|
-
|
|
108
|
+
For example, if test files had the same names as their source files followed
|
|
109
|
+
by an underscore and the file name in reverse like this:
|
|
115
110
|
|
|
116
|
-
* lib/hello.rb => test/
|
|
117
|
-
* app/world.rb => spec/
|
|
111
|
+
* lib/hello.rb => test/hello_olleh.rb
|
|
112
|
+
* app/world.rb => spec/world_ldrow.rb
|
|
118
113
|
|
|
119
114
|
Then you would add the following to your configuration file:
|
|
120
115
|
|
|
121
|
-
Test::Loop
|
|
116
|
+
Test::Loop.test_file_matchers['{lib,app}/**/*.rb'] = lambda do |path|
|
|
122
117
|
extn = File.extname(path)
|
|
123
118
|
name = File.basename(path, extn)
|
|
124
|
-
"{test,spec}/**/#{name.reverse}#{extn}"
|
|
119
|
+
"{test,spec}/**/#{name}_#{name.reverse}#{extn}"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
* `Test::Loop.test_name_parser` is a lambda function that is passed a line of
|
|
123
|
+
source code to determine whether that line can be considered as a test
|
|
124
|
+
definition, in which case it must return the name of the test being defined.
|
|
125
|
+
|
|
126
|
+
* `Test::Loop.before_each_test` is a lambda function that is executed inside
|
|
127
|
+
the worker process before loading the test file. It is passed (1) the path
|
|
128
|
+
to the test file, (2) the path to the log file containing the live output of
|
|
129
|
+
running the test file, and (3) an array containing the names of tests (which
|
|
130
|
+
were identified by `Test::Loop.test_name_parser`) inside the test file that
|
|
131
|
+
have changed since the last run of the test file.
|
|
132
|
+
|
|
133
|
+
The default implementation of this function instructs Test::Unit and RSpec
|
|
134
|
+
to only run certain test blocks inside the test file. This accelerates your
|
|
135
|
+
test-driven development cycle and improves productivity!
|
|
136
|
+
|
|
137
|
+
If you wish to add extend the default implementation, store and recall it:
|
|
138
|
+
|
|
139
|
+
default_implementation = Test::Loop.before_each_test
|
|
140
|
+
|
|
141
|
+
Test::Loop.before_each_test = lambda do |test_file, log_file, test_names|
|
|
142
|
+
default_implementation.call test_file, log_file, test_names
|
|
143
|
+
# do something additional ...
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
Or if you want to completely replace the default implementation:
|
|
147
|
+
|
|
148
|
+
Test::Loop.before_each_test = lambda do |test_file, log_file, test_names|
|
|
149
|
+
# your replacement here ...
|
|
125
150
|
end
|
|
126
151
|
|
|
127
|
-
* `Test::Loop
|
|
128
|
-
|
|
129
|
-
test
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
the path to the test file, (2) the path to the log file containing the live
|
|
135
|
-
output of running the test file, and (3) an array containing the names of
|
|
136
|
-
tests (which were identified by `Test::Loop::Config.test_name_parser`)
|
|
137
|
-
inside the test file that have changed since the last run of the test file.
|
|
138
|
-
|
|
139
|
-
These test names should be passed down to your chosen testing library,
|
|
140
|
-
instructing it to skip all other tests except those passed down to it. This
|
|
141
|
-
accelerates your test-driven development cycle and improves productivity!
|
|
142
|
-
|
|
143
|
-
* `Test::Loop::Config.after_each_test` is a lambda function that is executed
|
|
144
|
-
inside the master process after a test has finished running. It is passed
|
|
145
|
-
(1) the path to the test file, (2) the path to the log file containing the
|
|
146
|
-
output of running the test file, (3) a `Process::Status` object describing
|
|
147
|
-
the exit status of the worker process that ran the test file, (4) the time
|
|
148
|
-
when test execution began, and (5) how many seconds it took for the overall
|
|
149
|
-
test execution to complete.
|
|
152
|
+
* `Test::Loop.after_each_test` is a lambda function that is executed inside
|
|
153
|
+
the master process after a test has finished running. It is passed (1) the
|
|
154
|
+
path to the test file, (2) the path to the log file containing the output of
|
|
155
|
+
running the test file, (3) a `Process::Status` object describing the exit
|
|
156
|
+
status of the worker process that ran the test file, (4) the time when test
|
|
157
|
+
execution began, and (5) how many seconds it took for the overall test
|
|
158
|
+
execution to complete.
|
|
150
159
|
|
|
151
160
|
For example, to display a summary of the test execution results as an
|
|
152
161
|
on-screen-display notification while also displaying the log file if the
|
|
153
162
|
test failed, add the following to your configuration file:
|
|
154
163
|
|
|
155
|
-
Test::Loop
|
|
164
|
+
Test::Loop.after_each_test = lambda do |test_file, log_file, run_status, started_at, elapsed_time|
|
|
156
165
|
success = run_status.success?
|
|
157
166
|
|
|
158
167
|
title = '%s at %s in %0.1fs' %
|
data/bin/test-loop
CHANGED
|
@@ -26,207 +26,202 @@ require 'ostruct'
|
|
|
26
26
|
require 'diff/lcs'
|
|
27
27
|
|
|
28
28
|
module Test
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
File.basename(CONFIG_FILE)],
|
|
38
|
-
|
|
39
|
-
:test_file_matchers => {
|
|
40
|
-
# source files that correspond to test files
|
|
41
|
-
'{lib,app}/**/*.rb' => lambda do |path|
|
|
42
|
-
extn = File.extname(path)
|
|
43
|
-
name = File.basename(path, extn)
|
|
44
|
-
"{test,spec}/**/#{name}_{test,spec}#{extn}"
|
|
45
|
-
end,
|
|
46
|
-
|
|
47
|
-
# the actual test files themselves
|
|
48
|
-
'{test,spec}/**/*_{test,spec}.rb' => lambda {|path| path }
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
:test_name_parser => lambda do |line|
|
|
52
|
-
case line
|
|
53
|
-
when /^\s*def\s+test_(\w+)/ then $1
|
|
54
|
-
when /^\s*(test|context|should|describe|it)\b.+?(['"])(.*?)\2/ then $3
|
|
55
|
-
end
|
|
56
|
-
end,
|
|
57
|
-
|
|
58
|
-
:before_each_test => lambda do |test_file, log_file, test_names|
|
|
59
|
-
unless test_names.empty?
|
|
60
|
-
test_name_pattern = test_names.map do |name|
|
|
61
|
-
# sanitize string interpolation and non-method-name characters
|
|
62
|
-
name.gsub(/\#\{.*?\}/, ' ').strip.gsub(/\W+/, '.*')
|
|
63
|
-
end.join('|')
|
|
64
|
-
|
|
65
|
-
case File.basename(test_file)
|
|
66
|
-
when /(\b|_)test(\b|_)/ # Test::Unit
|
|
67
|
-
ARGV.push '--name', "/#{test_name_pattern}/"
|
|
68
|
-
when /(\b|_)spec(\b|_)/ # RSpec
|
|
69
|
-
ARGV.push '--example', test_name_pattern
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
end,
|
|
29
|
+
Loop = OpenStruct.new
|
|
30
|
+
|
|
31
|
+
Loop.config_file_path = File.join(Dir.pwd, '.test-loop')
|
|
32
|
+
|
|
33
|
+
Loop.overhead_file_globs = ['{test,spec}/{test,spec}_helper.rb']
|
|
34
|
+
|
|
35
|
+
Loop.reabsorb_file_globs = Loop.overhead_file_globs +
|
|
36
|
+
['config/*.{rb,yml}', 'Gemfile.lock', File.basename(Loop.config_file_path)]
|
|
73
37
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
38
|
+
Loop.test_file_matchers = {
|
|
39
|
+
# source files that correspond to test files
|
|
40
|
+
'{lib,app}/**/*.rb' => lambda do |path|
|
|
41
|
+
extn = File.extname(path)
|
|
42
|
+
name = File.basename(path, extn)
|
|
43
|
+
"{test,spec}/**/#{name}_{test,spec}#{extn}"
|
|
44
|
+
end,
|
|
77
45
|
|
|
78
|
-
|
|
46
|
+
# the actual test files themselves
|
|
47
|
+
'{test,spec}/**/*_{test,spec}.rb' => lambda {|path| path }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
Loop.test_name_parser = lambda do |line|
|
|
51
|
+
case line
|
|
52
|
+
when /^\s*def\s+test_(\w+)/ then $1
|
|
53
|
+
when /^\s*(test|context|should|describe|it)\b.+?(['"])(.*?)\2/ then $3
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
Loop.before_each_test = lambda do |test_file, log_file, test_names|
|
|
58
|
+
unless test_names.empty?
|
|
59
|
+
test_name_pattern = test_names.map do |name|
|
|
60
|
+
# sanitize string interpolation and non-method-name characters
|
|
61
|
+
name.gsub(/\#\{.*?\}/, ' ').strip.gsub(/\W+/, '.*')
|
|
62
|
+
end.join('|')
|
|
63
|
+
|
|
64
|
+
case File.basename(test_file)
|
|
65
|
+
when /(\b|_)test(\b|_)/ # Test::Unit
|
|
66
|
+
ARGV.push '--name', "/#{test_name_pattern}/"
|
|
67
|
+
when /(\b|_)spec(\b|_)/ # RSpec
|
|
68
|
+
ARGV.push '--example', test_name_pattern
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
Loop.after_each_test =
|
|
74
|
+
lambda {|test_file, log_file, run_status, started_at, elapsed_time|}
|
|
75
|
+
|
|
76
|
+
class << Loop
|
|
77
|
+
def run
|
|
79
78
|
register_signals
|
|
80
79
|
load_user_config
|
|
81
80
|
absorb_overhead
|
|
82
81
|
run_test_loop
|
|
83
82
|
rescue Exception => error
|
|
84
83
|
STDERR.puts error.inspect, error.backtrace
|
|
85
|
-
|
|
84
|
+
pause_momentarily
|
|
86
85
|
reload_master_process
|
|
87
86
|
end
|
|
88
87
|
|
|
89
|
-
|
|
90
|
-
SCRIPT_NAME = File.basename($0)
|
|
91
|
-
EXEC_VECTOR = [$0, *ARGV].map {|s| s.dup.freeze }.freeze
|
|
88
|
+
private
|
|
92
89
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
print "#{SCRIPT_NAME}: #{message}\n"
|
|
96
|
-
end
|
|
90
|
+
SCRIPT_NAME = File.basename($0).freeze
|
|
91
|
+
EXEC_VECTOR = [$0, *ARGV].map {|s| s.dup.freeze }.freeze
|
|
97
92
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
trap(:QUIT) { reload_master_process }
|
|
103
|
-
trap(:TSTP) {} # ignore until ready for testing in run_test_loop()
|
|
104
|
-
end
|
|
93
|
+
def notify message
|
|
94
|
+
# using print() because puts() is not an atomic operation
|
|
95
|
+
print "#{SCRIPT_NAME}: #{message}\n"
|
|
96
|
+
end
|
|
105
97
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
def register_signals
|
|
99
|
+
trap(:INT) { destroy_process_group }
|
|
100
|
+
master_pid = $$ # kill only the workers, not the master
|
|
101
|
+
trap(:USR1) { destroy_process_group unless $$ == master_pid }
|
|
102
|
+
trap(:QUIT) { reload_master_process }
|
|
103
|
+
trap(:TSTP) {} # ignore until ready for testing in run_test_loop()
|
|
104
|
+
end
|
|
109
105
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
exec(*EXEC_VECTOR)
|
|
114
|
-
end
|
|
106
|
+
def destroy_process_group
|
|
107
|
+
Process.kill :KILL, -$$
|
|
108
|
+
end
|
|
115
109
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
110
|
+
def reload_master_process
|
|
111
|
+
notify 'Restarting loop...'
|
|
112
|
+
Process.kill :USR1, -$$
|
|
113
|
+
exec(*EXEC_VECTOR)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def pause_momentarily
|
|
117
|
+
sleep 1
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def load_user_config
|
|
121
|
+
if File.exist? config_file_path
|
|
122
|
+
notify 'Loading configuration...'
|
|
123
|
+
load config_file_path
|
|
121
124
|
end
|
|
125
|
+
end
|
|
122
126
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
end
|
|
127
|
+
def absorb_overhead
|
|
128
|
+
notify 'Absorbing overhead...'
|
|
129
|
+
$LOAD_PATH.unshift 'lib', 'test', 'spec'
|
|
130
|
+
Dir[*overhead_file_globs].each do |file|
|
|
131
|
+
require File.basename(file, File.extname(file))
|
|
129
132
|
end
|
|
133
|
+
end
|
|
130
134
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
@lines_by_file = {} # path => readlines
|
|
136
|
-
@last_ran_at = @started_at = Time.now
|
|
137
|
-
trap(:TSTP) { @last_ran_at = Time.at(0); @lines_by_file.clear }
|
|
138
|
-
|
|
139
|
-
notify 'Ready for testing!'
|
|
140
|
-
loop do
|
|
141
|
-
# figure out what test files need to be run
|
|
142
|
-
test_files = Config.test_file_matchers.map \
|
|
143
|
-
do |source_glob, test_matcher|
|
|
144
|
-
Dir[source_glob].select {|file| File.mtime(file) > @last_ran_at }.
|
|
145
|
-
map {|path| Dir[test_matcher.call path] }
|
|
146
|
-
end.flatten.uniq
|
|
147
|
-
|
|
148
|
-
test_files = @running_files_lock.
|
|
149
|
-
synchronize { test_files - @running_files }
|
|
150
|
-
|
|
151
|
-
# fork worker processes to run the test files in parallel
|
|
152
|
-
@last_ran_at = Time.now
|
|
153
|
-
test_files.each {|f| run_test_file f }
|
|
154
|
-
|
|
155
|
-
# reabsorb test execution overhead as necessary
|
|
156
|
-
if Dir[*Config.reabsorb_file_globs].
|
|
157
|
-
any? {|file| File.mtime(file) > @started_at }
|
|
158
|
-
then
|
|
159
|
-
reload_master_process
|
|
160
|
-
end
|
|
135
|
+
def run_test_loop
|
|
136
|
+
@running_files = []
|
|
137
|
+
@running_files_lock = Mutex.new
|
|
161
138
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
139
|
+
@lines_by_file = {} # path => readlines
|
|
140
|
+
@last_ran_at = @started_at = Time.now
|
|
141
|
+
trap(:TSTP) { @last_ran_at = Time.at(0); @lines_by_file.clear }
|
|
165
142
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
@lines_by_file[test_file] = new_lines
|
|
174
|
-
|
|
175
|
-
worker_pid = fork do
|
|
176
|
-
# capture test output in log file because tests are run in parallel
|
|
177
|
-
# which makes it difficult to understand interleaved output thereof
|
|
178
|
-
$stdout.reopen log_file, 'w'
|
|
179
|
-
$stdout.sync = true
|
|
180
|
-
$stderr.reopen $stdout
|
|
181
|
-
|
|
182
|
-
# determine which test blocks have changed inside the test file
|
|
183
|
-
test_names = Diff::LCS.diff(old_lines, new_lines).flatten.map \
|
|
184
|
-
do |change|
|
|
185
|
-
catch :found do
|
|
186
|
-
# search backwards from the line that changed up to
|
|
187
|
-
# the first line in the file for test definitions
|
|
188
|
-
change.position.downto(0) do |i|
|
|
189
|
-
if test_name = Config.test_name_parser.call(new_lines[i])
|
|
190
|
-
throw :found, test_name
|
|
191
|
-
end
|
|
192
|
-
end; nil # prevent unsuccessful search from returning an integer
|
|
193
|
-
end
|
|
194
|
-
end.compact.uniq
|
|
195
|
-
|
|
196
|
-
# tell the testing framework to run only the changed test blocks
|
|
197
|
-
Config.before_each_test.call test_file, log_file, test_names
|
|
198
|
-
|
|
199
|
-
# after loading the user's test file, the at_exit() hook of the
|
|
200
|
-
# user's testing framework will take care of running the tests and
|
|
201
|
-
# reflecting any failures in the worker process' exit status
|
|
202
|
-
load $0 = test_file # set $0 because Test::Unit outputs it
|
|
203
|
-
end
|
|
143
|
+
notify 'Ready for testing!'
|
|
144
|
+
loop do
|
|
145
|
+
# figure out what test files need to be run
|
|
146
|
+
test_files = test_file_matchers.map do |source_glob, test_matcher|
|
|
147
|
+
Dir[source_glob].select {|file| File.mtime(file) > @last_ran_at }.
|
|
148
|
+
map {|path| Dir[test_matcher.call path] }
|
|
149
|
+
end.flatten.uniq
|
|
204
150
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
151
|
+
test_files = @running_files_lock.
|
|
152
|
+
synchronize { test_files - @running_files }
|
|
153
|
+
|
|
154
|
+
# fork worker processes to run the test files in parallel
|
|
155
|
+
@last_ran_at = Time.now
|
|
156
|
+
test_files.each {|file| run_test_file file }
|
|
211
157
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
158
|
+
# reabsorb test execution overhead as necessary
|
|
159
|
+
reload_master_process if Dir[*reabsorb_file_globs].
|
|
160
|
+
any? {|file| File.mtime(file) > @started_at }
|
|
161
|
+
|
|
162
|
+
pause_momentarily
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def run_test_file test_file
|
|
167
|
+
@running_files_lock.synchronize { @running_files.push test_file }
|
|
168
|
+
log_file = test_file + '.log'
|
|
169
|
+
|
|
170
|
+
# cache the contents of the test file for diffing below
|
|
171
|
+
new_lines = File.readlines(test_file)
|
|
172
|
+
old_lines = @lines_by_file[test_file] || new_lines
|
|
173
|
+
@lines_by_file[test_file] = new_lines
|
|
174
|
+
|
|
175
|
+
worker_pid = fork do
|
|
176
|
+
# capture test output in log file because tests are run in parallel
|
|
177
|
+
# which makes it difficult to understand interleaved output thereof
|
|
178
|
+
$stdout.reopen log_file, 'w'
|
|
179
|
+
$stdout.sync = true
|
|
180
|
+
$stderr.reopen $stdout
|
|
181
|
+
|
|
182
|
+
# determine which test blocks have changed inside the test file
|
|
183
|
+
test_names = Diff::LCS.diff(old_lines, new_lines).flatten.map do |change|
|
|
184
|
+
catch :found do
|
|
185
|
+
# search backwards from the line that changed up to
|
|
186
|
+
# the first line in the file for test definitions
|
|
187
|
+
change.position.downto(0) do |i|
|
|
188
|
+
if test_name = test_name_parser.call(new_lines[i])
|
|
189
|
+
throw :found, test_name
|
|
190
|
+
end
|
|
191
|
+
end; nil # prevent unsuccessful search from returning an integer
|
|
217
192
|
end
|
|
218
|
-
|
|
193
|
+
end.compact.uniq
|
|
219
194
|
|
|
220
|
-
|
|
195
|
+
# tell the testing framework to run only the changed test blocks
|
|
196
|
+
before_each_test.call test_file, log_file, test_names
|
|
221
197
|
|
|
222
|
-
|
|
223
|
-
|
|
198
|
+
# after loading the user's test file, the at_exit() hook of the
|
|
199
|
+
# user's testing framework will take care of running the tests and
|
|
200
|
+
# reflecting any failures in the worker process' exit status
|
|
201
|
+
load $0 = test_file # set $0 because Test::Unit outputs it
|
|
202
|
+
end
|
|
224
203
|
|
|
225
|
-
|
|
204
|
+
# monitor and report on the worker's progress
|
|
205
|
+
Thread.new do
|
|
206
|
+
report = lambda do |state|
|
|
207
|
+
notify [state, worker_pid, test_file].join("\t")
|
|
226
208
|
end
|
|
209
|
+
report.call :RUN
|
|
210
|
+
|
|
211
|
+
# wait for worker to finish
|
|
212
|
+
begin
|
|
213
|
+
Process.waitpid worker_pid
|
|
214
|
+
rescue Errno::ECHILD
|
|
215
|
+
# worker finished and the OS has forgotten about it already
|
|
216
|
+
end
|
|
217
|
+
elapsed_time = Time.now - @last_ran_at
|
|
218
|
+
|
|
219
|
+
report.call $?.success? && :PASS || :FAIL
|
|
220
|
+
after_each_test.call test_file, log_file, $?, @last_ran_at, elapsed_time
|
|
221
|
+
@running_files_lock.synchronize { @running_files.delete test_file }
|
|
227
222
|
end
|
|
228
223
|
end
|
|
229
224
|
end
|
|
230
225
|
end
|
|
231
226
|
|
|
232
|
-
Test::Loop.run
|
|
227
|
+
Test::Loop.run if $0 == __FILE__
|
metadata
CHANGED
|
@@ -3,10 +3,10 @@ name: test-loop
|
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
|
5
5
|
segments:
|
|
6
|
-
-
|
|
6
|
+
- 8
|
|
7
7
|
- 0
|
|
8
|
-
-
|
|
9
|
-
version:
|
|
8
|
+
- 0
|
|
9
|
+
version: 8.0.0
|
|
10
10
|
platform: ruby
|
|
11
11
|
authors:
|
|
12
12
|
- Suraj N. Kurapati
|
|
@@ -14,7 +14,7 @@ autorequire:
|
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
16
|
|
|
17
|
-
date: 2011-02-
|
|
17
|
+
date: 2011-02-12 00:00:00 -08:00
|
|
18
18
|
default_executable:
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|