test-loop 11.0.1 → 12.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/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
|