test-loop 11.0.1 → 12.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +16 -0
- data/README.md +26 -12
- data/lib/test/loop.rb +65 -57
- data/lib/test/loop/rails.rb +20 -0
- metadata +4 -5
- data/lib/test/loop/support/minitest.rb +0 -5
- data/lib/test/loop/support/rails.rb +0 -21
- data/lib/test/loop/support/testunit.rb +0 -7
data/LICENSE
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
(the ISC license)
|
2
|
+
|
3
|
+
Copyright 2010 Suraj N. Kurapati <sunaku@gmail.com>
|
4
|
+
Copyright 2011 Brian D. Burns <burns180@gmail.com>
|
5
|
+
|
6
|
+
Permission to use, copy, modify, and/or distribute this software for any
|
7
|
+
purpose with or without fee is hereby granted, provided that the above
|
8
|
+
copyright notice and this permission notice appear in all copies.
|
9
|
+
|
10
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
11
|
+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
12
|
+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
13
|
+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
14
|
+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
15
|
+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
16
|
+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
data/README.md
CHANGED
@@ -32,6 +32,12 @@ Features
|
|
32
32
|
|
33
33
|
* Implemented in less than 250 lines (SLOC) of pure Ruby code! :-)
|
34
34
|
|
35
|
+
------------------------------------------------------------------------------
|
36
|
+
Prerequisites
|
37
|
+
------------------------------------------------------------------------------
|
38
|
+
|
39
|
+
* Operating system that supports POSIX signals and the `fork()` system call.
|
40
|
+
|
35
41
|
------------------------------------------------------------------------------
|
36
42
|
Installation
|
37
43
|
------------------------------------------------------------------------------
|
@@ -61,6 +67,10 @@ You can monitor your test processes in another terminal:
|
|
61
67
|
|
62
68
|
watch 'ps xf | grep test-loop | sed 1,3d'
|
63
69
|
|
70
|
+
If it stops responding, you can annihilate test-loop from another terminal:
|
71
|
+
|
72
|
+
pkill -9 -f test-loop
|
73
|
+
|
64
74
|
------------------------------------------------------------------------------
|
65
75
|
Operation
|
66
76
|
------------------------------------------------------------------------------
|
@@ -220,7 +230,7 @@ configuration file:
|
|
220
230
|
}
|
221
231
|
|
222
232
|
------------------------------------------------------------------------------
|
223
|
-
Configuration
|
233
|
+
Configuration presets
|
224
234
|
------------------------------------------------------------------------------
|
225
235
|
|
226
236
|
The following sub-libraries provide "preset" configurations. To use them,
|
@@ -231,26 +241,30 @@ your application's `test/test_helper.rb` or `spec/spec_helper.rb` file.
|
|
231
241
|
|
232
242
|
Shows on-screen-display notifications for test failures.
|
233
243
|
|
244
|
+
### require 'test/loop/rails'
|
245
|
+
|
246
|
+
Provides support for the Ruby on Rails web framework.
|
247
|
+
|
234
248
|
------------------------------------------------------------------------------
|
235
249
|
Known issues
|
236
250
|
------------------------------------------------------------------------------
|
237
251
|
|
238
252
|
### Ruby on Rails
|
239
253
|
|
240
|
-
* Ensure that your `config/environments/test.rb` file disables class caching
|
241
|
-
|
242
|
-
|
254
|
+
* Ensure that your `config/environments/test.rb` file disables class caching
|
255
|
+
as follows (**NOTE:** if you are using Rails 3, the `test/loop/rails`
|
256
|
+
preset will automatically do this for you):
|
243
257
|
|
244
|
-
|
258
|
+
config.cache_classes = false
|
245
259
|
|
246
|
-
|
247
|
-
|
260
|
+
Otherwise, test-loop will appear to ignore source-code changes in your
|
261
|
+
models, controllers, helpers, and other Ruby source files.
|
248
262
|
|
249
|
-
* SQLite3 [raises `SQLite3::BusyException: database is locked` errors](
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
263
|
+
* SQLite3 [raises `SQLite3::BusyException: database is locked` errors](
|
264
|
+
https://github.com/sunaku/test-loop/issues/2 ) because test-loop runs your
|
265
|
+
test files in parallel. You can work around this by using an [in-memory
|
266
|
+
adapter for SQLite3]( https://github.com/mvz/memory_test_fix ) or by using
|
267
|
+
different database software (such as MySQL) for your test environment.
|
254
268
|
|
255
269
|
------------------------------------------------------------------------------
|
256
270
|
License
|
data/lib/test/loop.rb
CHANGED
@@ -49,19 +49,14 @@ module Test
|
|
49
49
|
|
50
50
|
class << Loop
|
51
51
|
def run
|
52
|
-
|
53
|
-
@running_files_lock = Mutex.new
|
54
|
-
@lines_by_file = {} # path => readlines
|
55
|
-
@last_ran_at = @started_at = Time.now
|
56
|
-
|
52
|
+
initialize_vars
|
57
53
|
register_signals
|
58
54
|
load_user_config
|
59
55
|
absorb_overhead
|
60
|
-
load_lib_support
|
61
56
|
run_test_loop
|
62
57
|
|
63
58
|
rescue Interrupt
|
64
|
-
# allow user to break the loop
|
59
|
+
# allow user to break the loop
|
65
60
|
|
66
61
|
rescue Exception => error
|
67
62
|
STDERR.puts error.inspect, error.backtrace
|
@@ -75,13 +70,16 @@ module Test
|
|
75
70
|
|
76
71
|
private
|
77
72
|
|
78
|
-
|
73
|
+
MASTER_ARGV = [$0, *ARGV].map {|s| s.dup.freeze }.freeze
|
79
74
|
RESUME_ENV_KEY = 'TEST_LOOP_RESUME_FILES'.freeze
|
75
|
+
MASTER_ENV = ENV.to_hash.delete_if {|k,v| k == RESUME_ENV_KEY }.freeze
|
80
76
|
|
81
77
|
ANSI_CLEAR_LINE = "\e[2K\e[0G".freeze
|
82
78
|
ANSI_GREEN = "\e[32m%s\e[0m".freeze
|
83
79
|
ANSI_RED = "\e[31m%s\e[0m".freeze
|
84
80
|
|
81
|
+
Worker = Struct.new(:test_file, :log_file, :started_at)
|
82
|
+
|
85
83
|
def notify message
|
86
84
|
# using print() because puts() is not an atomic operation.
|
87
85
|
# also, clear the line before printing because some shells emit
|
@@ -89,25 +87,53 @@ module Test
|
|
89
87
|
print "#{ANSI_CLEAR_LINE}test-loop: #{message}\n"
|
90
88
|
end
|
91
89
|
|
90
|
+
def initialize_vars
|
91
|
+
@running_files = []
|
92
|
+
@lines_by_file = {} # path => readlines
|
93
|
+
@last_ran_at = @started_at = Time.now
|
94
|
+
@worker_by_pid = {}
|
95
|
+
end
|
96
|
+
|
92
97
|
def register_signals
|
93
98
|
# this signal is ignored in master and honored in workers, so all
|
94
99
|
# workers can be killed by sending it to the entire process group
|
95
100
|
trap :TERM, :IGNORE
|
96
101
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
102
|
+
trap :CHLD do
|
103
|
+
finished_at = Time.now
|
104
|
+
|
105
|
+
begin
|
106
|
+
worker_pid = Process.wait
|
107
|
+
run_status = $?
|
108
|
+
|
109
|
+
worker = @worker_by_pid.delete(worker_pid)
|
110
|
+
elapsed_time = finished_at - worker.started_at
|
111
|
+
|
112
|
+
# report test results along with any failure logs
|
113
|
+
if run_status.success?
|
114
|
+
notify ANSI_GREEN % "PASS #{worker.test_file}"
|
115
|
+
elsif run_status.exited?
|
116
|
+
notify ANSI_RED % "FAIL #{worker.test_file}"
|
117
|
+
STDERR.print File.read(worker.log_file)
|
118
|
+
end
|
119
|
+
|
120
|
+
after_each_test.each do |hook|
|
121
|
+
hook.call worker.test_file, worker.log_file, run_status,
|
122
|
+
worker.started_at, elapsed_time
|
105
123
|
end
|
124
|
+
|
125
|
+
@running_files.delete worker.test_file
|
126
|
+
|
127
|
+
rescue Errno::ECHILD
|
128
|
+
# could not get the terminated child's PID.
|
129
|
+
# Ruby's backtick operator can cause this:
|
130
|
+
# http://stackoverflow.com/questions/1495354
|
106
131
|
end
|
107
132
|
end
|
108
133
|
|
109
|
-
|
110
|
-
|
134
|
+
trap(:INT) { raise Interrupt }
|
135
|
+
trap(:QUIT) { reload_master_process }
|
136
|
+
trap(:TSTP) { forcibly_run_all_tests }
|
111
137
|
end
|
112
138
|
|
113
139
|
def kill_workers
|
@@ -119,9 +145,10 @@ module Test
|
|
119
145
|
# The given test files are passed down (along with currently running
|
120
146
|
# test files) to the next incarnation of test-loop for resumption.
|
121
147
|
def reload_master_process test_files = []
|
122
|
-
|
148
|
+
test_files.concat @running_files
|
123
149
|
kill_workers
|
124
|
-
exec(
|
150
|
+
exec MASTER_ENV.merge(RESUME_ENV_KEY => test_files.inspect),
|
151
|
+
*MASTER_ARGV, {:unsetenv_others => true}
|
125
152
|
end
|
126
153
|
|
127
154
|
def load_user_config
|
@@ -139,10 +166,6 @@ module Test
|
|
139
166
|
end
|
140
167
|
end
|
141
168
|
|
142
|
-
def load_lib_support
|
143
|
-
Dir[File.expand_path '../loop/support/*.rb', __FILE__].each {|f| load f }
|
144
|
-
end
|
145
|
-
|
146
169
|
def pause_momentarily
|
147
170
|
sleep 1
|
148
171
|
end
|
@@ -179,9 +202,7 @@ module Test
|
|
179
202
|
|
180
203
|
# fork workers to run the test files in parallel,
|
181
204
|
# excluding test files that are already running
|
182
|
-
test_files
|
183
|
-
synchronize { test_files - @running_files }
|
184
|
-
|
205
|
+
test_files -= @running_files
|
185
206
|
unless test_files.empty?
|
186
207
|
@last_ran_at = Time.now
|
187
208
|
test_files.each {|file| run_test_file file }
|
@@ -192,7 +213,9 @@ module Test
|
|
192
213
|
end
|
193
214
|
|
194
215
|
def run_test_file test_file
|
195
|
-
|
216
|
+
notify "TEST #{test_file}"
|
217
|
+
|
218
|
+
@running_files.push test_file
|
196
219
|
log_file = test_file + '.log'
|
197
220
|
|
198
221
|
# cache the contents of the test file for diffing below
|
@@ -200,17 +223,24 @@ module Test
|
|
200
223
|
old_lines = @lines_by_file[test_file] || new_lines
|
201
224
|
@lines_by_file[test_file] = new_lines
|
202
225
|
|
226
|
+
started_at = Time.now
|
203
227
|
worker_pid = fork do
|
204
|
-
#
|
205
|
-
|
206
|
-
trap :TERM, :DEFAULT
|
228
|
+
# handle signals meant for worker process
|
229
|
+
[:TERM, :CHLD].each {|sig| trap sig, :DEFAULT }
|
207
230
|
|
208
|
-
#
|
209
|
-
|
231
|
+
# ignore signals meant for master process
|
232
|
+
[:INT, :QUIT, :TSTP].each {|sig| trap sig, :IGNORE }
|
233
|
+
|
234
|
+
# detach worker from master's terminal device so that
|
235
|
+
# it does not receieve the user's control-key presses
|
236
|
+
Process.setsid
|
237
|
+
|
238
|
+
# detach worker from master's standard input stream
|
239
|
+
STDIN.reopen IO.pipe.first
|
210
240
|
|
211
241
|
# capture test output in log file because tests are run in parallel
|
212
242
|
# which makes it difficult to understand interleaved output thereof
|
213
|
-
|
243
|
+
STDERR.reopen(STDOUT.reopen(log_file, 'w')).sync = true
|
214
244
|
|
215
245
|
# determine which test blocks have changed inside the test file
|
216
246
|
test_names = Diff::LCS.diff(old_lines, new_lines).flatten.map do |change|
|
@@ -237,29 +267,7 @@ module Test
|
|
237
267
|
load test_file
|
238
268
|
end
|
239
269
|
|
240
|
-
|
241
|
-
Thread.new do
|
242
|
-
notify "TEST #{test_file}"
|
243
|
-
|
244
|
-
# wait for worker to finish
|
245
|
-
Process.waitpid worker_pid
|
246
|
-
run_status = $?
|
247
|
-
elapsed_time = Time.now - @last_ran_at
|
248
|
-
|
249
|
-
# report test results along with any failure logs
|
250
|
-
if run_status.success?
|
251
|
-
notify ANSI_GREEN % "PASS #{test_file}"
|
252
|
-
elsif run_status.exited?
|
253
|
-
notify ANSI_RED % "FAIL #{test_file}"
|
254
|
-
STDERR.print File.read(log_file)
|
255
|
-
end
|
256
|
-
|
257
|
-
after_each_test.each do |f|
|
258
|
-
f.call test_file, log_file, run_status, @last_ran_at, elapsed_time
|
259
|
-
end
|
260
|
-
|
261
|
-
@running_files_lock.synchronize { @running_files.delete test_file }
|
262
|
-
end
|
270
|
+
@worker_by_pid[worker_pid] = Worker.new(test_file, log_file, started_at)
|
263
271
|
end
|
264
272
|
end
|
265
273
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'test/loop'
|
2
|
+
|
3
|
+
Test::Loop.reabsorb_file_globs.push(
|
4
|
+
'config/**/*.{rb,yml}',
|
5
|
+
'test/factories/*.rb',
|
6
|
+
'Gemfile.lock'
|
7
|
+
)
|
8
|
+
|
9
|
+
Test::Loop.test_file_matchers['app/**/*.rb'] =
|
10
|
+
Test::Loop.test_file_matchers['lib/**/*.rb']
|
11
|
+
|
12
|
+
require 'rails/railtie'
|
13
|
+
Class.new Rails::Railtie do
|
14
|
+
config.before_initialize do |app|
|
15
|
+
if app.config.cache_classes
|
16
|
+
warn "test-loop: Setting #{app.class}.config.cache_classes = false"
|
17
|
+
app.config.cache_classes = false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: test-loop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version:
|
5
|
+
version: 12.0.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Suraj N. Kurapati
|
@@ -11,7 +11,7 @@ autorequire:
|
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
13
|
|
14
|
-
date: 2011-04-
|
14
|
+
date: 2011-04-19 00:00:00 -07:00
|
15
15
|
default_executable:
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
@@ -34,13 +34,12 @@ extensions: []
|
|
34
34
|
extra_rdoc_files: []
|
35
35
|
|
36
36
|
files:
|
37
|
+
- LICENSE
|
37
38
|
- README.md
|
38
39
|
- bin/test-loop
|
39
40
|
- lib/test/loop.rb
|
40
41
|
- lib/test/loop/notify.rb
|
41
|
-
- lib/test/loop/
|
42
|
-
- lib/test/loop/support/minitest.rb
|
43
|
-
- lib/test/loop/support/testunit.rb
|
42
|
+
- lib/test/loop/rails.rb
|
44
43
|
has_rdoc: true
|
45
44
|
homepage: http://github.com/sunaku/test-loop
|
46
45
|
licenses: []
|
@@ -1,21 +0,0 @@
|
|
1
|
-
if defined? Rails
|
2
|
-
Test::Loop.reabsorb_file_globs.push(
|
3
|
-
'config/**/*.{rb,yml}',
|
4
|
-
'test/factories/*.rb',
|
5
|
-
'Gemfile.lock'
|
6
|
-
)
|
7
|
-
|
8
|
-
Test::Loop.test_file_matchers['app/**/*.rb'] =
|
9
|
-
Test::Loop.test_file_matchers['lib/**/*.rb']
|
10
|
-
|
11
|
-
if defined? Rails::Railtie
|
12
|
-
Class.new Rails::Railtie do
|
13
|
-
config.before_initialize do |app|
|
14
|
-
if app.config.cache_classes
|
15
|
-
warn "test-loop: Setting #{app.class}.config.cache_classes = false"
|
16
|
-
app.config.cache_classes = false
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|