test-loop 11.0.0 → 12.2.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 ADDED
@@ -0,0 +1,17 @@
1
+ (the ISC license)
2
+
3
+ Copyright 2010 Suraj N. Kurapati <sunaku@gmail.com>
4
+ Copyright 2011 Brian D. Burns <burns180@gmail.com>
5
+ Copyright 2011 Daniel Pittman <daniel@rimspace.net>
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted, provided that the above
9
+ copyright notice and this permission notice appear in all copies.
10
+
11
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
data/README.md CHANGED
@@ -4,33 +4,50 @@ test-loop - Continuous testing for Ruby with fork/eval
4
4
  test-loop is a fast continuous testing tool for Ruby that automatically
5
5
  detects and tests changes in your application in an efficient manner:
6
6
 
7
- 1. Absorbs the test execution overhead into the main Ruby process.
8
- 2. Forks to run your test files without overhead and in parallel.
9
- 3. Avoids running unchanged test blocks inside changed test files.
7
+ 1. Absorbs the test execution overhead into the main Ruby process.
8
+
9
+ 2. Forks to run your test files without overhead and in parallel.
10
+
11
+ 3. Avoids running unchanged test blocks inside changed test files.
10
12
 
11
13
  ------------------------------------------------------------------------------
12
14
  Features
13
15
  ------------------------------------------------------------------------------
14
16
 
15
- * Tests *changes* in your Ruby application: avoids running (1) unchanged
16
- test files and (2) unchanged test blocks inside changed test files.
17
+ * Tests *changes* in your Ruby application: avoids running (1) unchanged
18
+ test files and (2) unchanged test blocks inside changed test files.
19
+
20
+ * Supports Test::Unit, RSpec, and any other testing framework that (1)
21
+ reflects failures in the process' exit status and (2) is loaded by your
22
+ application's `test/test_helper.rb` or `spec/spec_helper.rb` file.
23
+
24
+ * Reabsorbs test execution overhead if the test or spec helper file changes.
25
+
26
+ * Executes test files in parallel, making full use of multiple processors.
17
27
 
18
- * Supports Test::Unit, RSpec, and any other testing framework that (1)
19
- reflects failures in the process' exit status and (2) is loaded by your
20
- application's `test/test_helper.rb` or `spec/spec_helper.rb` file.
28
+ * Logs the output from your tests into separate files: one log per test.
29
+ The path to a log file is simply the path of its test file plus ".log".
21
30
 
22
- * Reabsorbs test execution overhead if the test or spec helper file changes.
31
+ * Generally I/O bound, so you can keep it running without CPU slowdown.
32
+
33
+ * Configurable through a `.test-loop` file in your working directory.
34
+
35
+ * Implemented in less than 240 lines (SLOC) of pure Ruby code! :-)
36
+
37
+ ------------------------------------------------------------------------------
38
+ Prerequisites
39
+ ------------------------------------------------------------------------------
23
40
 
24
- * Executes test files in parallel, making full use of multiple processors.
41
+ * Ruby 1.8.7 or 1.9.2 or newer.
25
42
 
26
- * Logs the output from your tests into separate files: one log per test file.
27
- The path to a log file is simply the path of its test file plus ".log".
43
+ * Operating system that supports POSIX signals and the `fork()` system call.
28
44
 
29
- * Generally I/O bound, so you can have it always running without CPU slowdown.
45
+ To check if your system qualifies, launch `irb` and enter the following:
30
46
 
31
- * Configurable through a `.test-loop` file in your current working directory.
47
+ Process.respond_to? :fork # must be true
32
48
 
33
- * Implemented in less than 250 lines (SLOC) of pure Ruby code! :-)
49
+ signals = %w[INT TSTP QUIT TERM CHLD].sort
50
+ Signal.list.keys.sort & signals == signals # must be true
34
51
 
35
52
  ------------------------------------------------------------------------------
36
53
  Installation
@@ -59,19 +76,23 @@ If installed as a Git clone:
59
76
 
60
77
  You can monitor your test processes in another terminal:
61
78
 
62
- watch 'ps xf | grep test-loop | sed 1,3d'
79
+ watch 'ps xfw | sed -n "1p; /test-loop/p" | fgrep -v sed'
80
+
81
+ If it stops responding, you can annihilate test-loop from another terminal:
82
+
83
+ pkill -9 -f test-loop
63
84
 
64
85
  ------------------------------------------------------------------------------
65
86
  Operation
66
87
  ------------------------------------------------------------------------------
67
88
 
68
- * Press Control-Z or send the SIGTSTP signal to forcibly run all
69
- tests, even if there are no changes in your Ruby application.
89
+ * Press Control-Z or send the SIGTSTP signal to forcibly run all
90
+ tests, even if there are no changes in your Ruby application.
70
91
 
71
- * Press Control-\ or send the SIGQUIT signal to forcibly reabsorb
72
- the test execution overhead, even if its sources have not changed.
92
+ * Press Control-\\ or send the SIGQUIT signal to forcibly reabsorb
93
+ the test execution overhead, even if its sources have not changed.
73
94
 
74
- * Press Control-C or send the SIGINT signal to quit the test loop.
95
+ * Press Control-C or send the SIGINT signal to quit the test loop.
75
96
 
76
97
  ------------------------------------------------------------------------------
77
98
  Configuration
@@ -81,19 +102,23 @@ test-loop looks for a configuration file named `.test-loop` in the current
81
102
  working directory. This configuration file is a normal Ruby script in which
82
103
  you can query and modify the `Test::Loop` OpenStruct configuration as follows:
83
104
 
105
+ ### Test::Loop.delay_per_iteration
106
+
107
+ Number of seconds to wait after each loop iteration. The default value is 1.
108
+
84
109
  ### Test::Loop.overhead_file_globs
85
110
 
86
- An array of file globbing patterns that describe a set of Ruby scripts that
87
- are loaded into the main Ruby process as overhead.
111
+ Array of file globbing patterns that describe a set of Ruby scripts that are
112
+ loaded into the main Ruby process as overhead.
88
113
 
89
114
  ### Test::Loop.reabsorb_file_globs
90
115
 
91
- An array of file globbing patterns that describe a set of files which cause
92
- the overhead to be reabsorbed whenever they change.
116
+ Array of file globbing patterns that describe a set of files which cause the
117
+ overhead to be reabsorbed whenever they change.
93
118
 
94
119
  ### Test::Loop.test_file_matchers
95
120
 
96
- A hash that maps a file globbing pattern describing a set of source files to a
121
+ Hash that maps a file globbing pattern describing a set of source files to a
97
122
  lambda function yielding a file globbing pattern describing a set of test
98
123
  files that need to be run. In other words, whenever the source files (the
99
124
  hash key; left-hand side of the mapping) change, their associated test files
@@ -102,8 +127,8 @@ hash key; left-hand side of the mapping) change, their associated test files
102
127
  For example, if test files had the same names as their source files followed
103
128
  by an underscore and the file name in reverse like this:
104
129
 
105
- * `lib/hello.rb` => `test/hello_olleh.rb`
106
- * `app/world.rb` => `spec/world_ldrow.rb`
130
+ * `lib/hello.rb` => `test/hello_olleh.rb`
131
+ * `app/world.rb` => `spec/world_ldrow.rb`
107
132
 
108
133
  Then you would add the following to your configuration file:
109
134
 
@@ -128,9 +153,9 @@ will return `nil`, thereby excluding those source files from being tested.
128
153
 
129
154
  ### Test::Loop.test_name_parser
130
155
 
131
- A lambda function that is passed a line of source code to determine whether
132
- that line can be considered as a test definition, in which case it must return
133
- the name of the test being defined.
156
+ Lambda function that is passed a line of source code to determine whether that
157
+ line can be considered as a test definition, in which case it must return the
158
+ name of the test being defined.
134
159
 
135
160
  ### Test::Loop.before_each_test
136
161
 
@@ -188,7 +213,8 @@ preset does this for you):
188
213
  unless run_status.success? or run_status.signaled?
189
214
  title = 'FAIL at %s in %0.1fs' % [started_at.strftime('%r'), elapsed_time]
190
215
 
191
- message = test_file
216
+ statistics = File.readlines(log_file).grep(/^\d+ \w+,/)
217
+ message = test_file + "\n" + statistics.join
192
218
 
193
219
  Thread.new do # run in background
194
220
  system 'notify-send', '-i', 'dialog-error', title, message or
@@ -210,7 +236,8 @@ configuration file:
210
236
  title = '%s at %s in %0.1fs' %
211
237
  [success ? 'PASS' : 'FAIL', started_at.strftime('%X'), elapsed_time]
212
238
 
213
- message = test_file
239
+ statistics = File.readlines(log_file).grep(/^\d+ \w+,/)
240
+ message = test_file + "\n" + statistics.join
214
241
 
215
242
  Thread.new do # run in background
216
243
  system 'notify-send', '-i', "dialog-#{success ? 'information' : 'error'}", title, message or
@@ -220,7 +247,7 @@ configuration file:
220
247
  }
221
248
 
222
249
  ------------------------------------------------------------------------------
223
- Configuration Presets
250
+ Configuration presets
224
251
  ------------------------------------------------------------------------------
225
252
 
226
253
  The following sub-libraries provide "preset" configurations. To use them,
@@ -231,26 +258,34 @@ your application's `test/test_helper.rb` or `spec/spec_helper.rb` file.
231
258
 
232
259
  Shows on-screen-display notifications for test failures.
233
260
 
261
+ ### require 'test/loop/rails'
262
+
263
+ Provides support for the Ruby on Rails web framework.
264
+
265
+ ### require 'test-loop/coco'
266
+
267
+ Integrates the [coco](https://github.com/lkdjiin/coco) code coverage library.
268
+
234
269
  ------------------------------------------------------------------------------
235
270
  Known issues
236
271
  ------------------------------------------------------------------------------
237
272
 
238
273
  ### Ruby on Rails
239
274
 
240
- * Ensure that your `config/environments/test.rb` file disables class caching
241
- as follows (**NOTE:** this is done automatically for you if you use Rails
242
- 3):
275
+ * Ensure that your `config/environments/test.rb` file disables class caching
276
+ as follows (**NOTE:** if you are using Rails 3, the `test/loop/rails`
277
+ preset will automatically do this for you):
243
278
 
244
- config.cache_classes = false
279
+ config.cache_classes = false
245
280
 
246
- Otherwise, test-loop will appear to ignore source-code changes in your
247
- models, controllers, helpers, and other Ruby source files.
281
+ Otherwise, test-loop will appear to ignore source-code changes in your
282
+ models, controllers, helpers, and other Ruby source files.
248
283
 
249
- * SQLite3 [raises `SQLite3::BusyException: database is locked` errors](
250
- https://github.com/sunaku/test-loop/issues/2 ) because test-loop runs your
251
- test files in parallel. You can work around this by using an [in-memory
252
- adapter for SQLite3]( https://github.com/mvz/memory_test_fix ) or by using
253
- different database software (such as MySQL) for your test environment.
284
+ * SQLite3 [raises `SQLite3::BusyException: database is locked` errors](
285
+ https://github.com/sunaku/test-loop/issues/2 ) because test-loop runs your
286
+ test files in parallel. You can work around this by using an [in-memory
287
+ adapter for SQLite3]( https://github.com/mvz/memory_test_fix ) or by using
288
+ different database software (such as MySQL) for your test environment.
254
289
 
255
290
  ------------------------------------------------------------------------------
256
291
  License
data/bin/test-loop CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'test/loop'
3
3
  Test::Loop.run
4
- exit! true # skip at_exit() handlers registered in the master process
4
+ raise SystemExit # prevent empty test suite run in master process
@@ -4,7 +4,8 @@ Test::Loop.after_each_test.push lambda {
4
4
  |test_file, log_file, run_status, started_at, elapsed_time|
5
5
  unless run_status.success? or run_status.signaled?
6
6
  title = 'FAIL at %s in %0.1fs' % [started_at.strftime('%r'), elapsed_time]
7
- message = test_file
7
+ statistics = File.readlines(log_file).grep(/^\d+ \w+,/)
8
+ message = test_file + "\n" + statistics.join
8
9
  Thread.new do # run in background
9
10
  system 'notify-send', '-i', 'dialog-error', title, message or
10
11
  system 'growlnotify', '-a', 'Xcode', '-m', message, title or
@@ -0,0 +1,20 @@
1
+ require 'test/loop'
2
+
3
+ Test::Loop.reabsorb_file_globs.push(
4
+ 'config/**/*.{rb,yml}',
5
+ 'db/schema.rb',
6
+ 'Gemfile.lock'
7
+ )
8
+
9
+ Test::Loop.test_file_matchers['{app,lib,test,spec}/**/*.rb'] =
10
+ Test::Loop.test_file_matchers.delete('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
data/lib/test/loop.rb CHANGED
@@ -4,6 +4,8 @@ require 'diff/lcs'
4
4
  module Test
5
5
  Loop = OpenStruct.new
6
6
 
7
+ Loop.delay_per_iteration = 1
8
+
7
9
  Loop.overhead_file_globs = ['{test,spec}/{test,spec}_helper.rb']
8
10
 
9
11
  Loop.reabsorb_file_globs = Loop.overhead_file_globs.dup
@@ -49,19 +51,15 @@ module Test
49
51
 
50
52
  class << Loop
51
53
  def run
52
- @running_files = []
53
- @running_files_lock = Mutex.new
54
- @lines_by_file = {} # path => readlines
55
- @last_ran_at = @started_at = Time.now
56
-
57
- register_signals
54
+ init_shared_vars
55
+ trap_user_signals
58
56
  load_user_config
59
- absorb_overhead
60
- load_lib_support
61
- run_test_loop
57
+ load_user_overhead
58
+ init_worker_queue
59
+ enter_testing_loop
62
60
 
63
61
  rescue Interrupt
64
- # allow user to break the loop by pressing Ctrl-C or sending SIGINT
62
+ # allow user to break the loop
65
63
 
66
64
  rescue Exception => error
67
65
  STDERR.puts error.inspect, error.backtrace
@@ -69,19 +67,23 @@ module Test
69
67
  reload_master_process
70
68
 
71
69
  ensure
72
- kill_workers
70
+ stop_worker_queue
73
71
  notify 'Goodbye!'
74
72
  end
75
73
 
76
74
  private
77
75
 
78
- EXEC_VECTOR = [$0, *ARGV].map {|s| s.dup.freeze }.freeze
76
+ MASTER_EXECV = [$0, *ARGV].map {|s| s.dup.freeze }.freeze
77
+ MASTER_ENV = Hash[ENV.map {|k,v| [k.freeze, v.freeze] }].freeze
79
78
  RESUME_ENV_KEY = 'TEST_LOOP_RESUME_FILES'.freeze
80
79
 
81
80
  ANSI_CLEAR_LINE = "\e[2K\e[0G".freeze
82
81
  ANSI_GREEN = "\e[32m%s\e[0m".freeze
83
82
  ANSI_RED = "\e[31m%s\e[0m".freeze
84
83
 
84
+ Worker = Struct.new(:test_file, :log_file, :started_at, :finished_at,
85
+ :exit_status)
86
+
85
87
  def notify message
86
88
  # using print() because puts() is not an atomic operation.
87
89
  # also, clear the line before printing because some shells emit
@@ -89,49 +91,48 @@ module Test
89
91
  print "#{ANSI_CLEAR_LINE}test-loop: #{message}\n"
90
92
  end
91
93
 
92
- def register_signals
93
- # this signal is ignored in master and honored in workers, so all
94
- # workers can be killed by sending it to the entire process group
95
- trap :TERM, :IGNORE
96
-
97
- master_pid = $$
98
- master_trap = lambda do |signal, &handler|
99
- trap signal do
100
- if $$ == master_pid
101
- handler.call
102
- else
103
- # ignore future ocurrences of this signal in worker processes
104
- trap signal, :IGNORE
105
- end
106
- end
107
- end
94
+ def pause_momentarily
95
+ sleep delay_per_iteration
96
+ end
108
97
 
109
- master_trap.call(:QUIT) { reload_master_process }
110
- master_trap.call(:TSTP) { forcibly_run_all_tests }
98
+ def init_shared_vars
99
+ @lines_by_file = {} # path => readlines
100
+ @last_ran_at = @started_at = Time.now
101
+ @worker_by_pid = {}
111
102
  end
112
103
 
113
- def kill_workers
114
- notify 'Stopping tests...'
115
- Process.kill :TERM, -$$
116
- Process.waitall
104
+ def trap_user_signals
105
+ trap(:INT) { raise Interrupt }
106
+ trap(:TSTP) { forcibly_run_all_tests }
107
+ trap(:QUIT) { reload_master_process }
108
+ end
109
+
110
+ def forcibly_run_all_tests
111
+ notify 'Running all tests...'
112
+ @last_ran_at = Time.at(0)
113
+ @lines_by_file.clear
117
114
  end
118
115
 
119
116
  # The given test files are passed down (along with currently running
120
117
  # test files) to the next incarnation of test-loop for resumption.
121
118
  def reload_master_process test_files = []
122
- @running_files_lock.synchronize { test_files.concat @running_files }
123
- kill_workers
124
- exec({RESUME_ENV_KEY => test_files.inspect}, *EXEC_VECTOR)
119
+ test_files.concat currently_running_test_files
120
+ stop_worker_queue
121
+ ENV.replace MASTER_ENV.merge(RESUME_ENV_KEY => test_files.inspect)
122
+ exec(*MASTER_EXECV)
125
123
  end
126
124
 
127
125
  def load_user_config
128
126
  if File.exist? config_file = File.join(Dir.pwd, '.test-loop')
129
127
  notify 'Loading configuration...'
130
128
  load config_file
129
+
130
+ # ...and if the configuration file changes, reload everything.
131
+ reabsorb_file_globs.push config_file
131
132
  end
132
133
  end
133
134
 
134
- def absorb_overhead
135
+ def load_user_overhead
135
136
  notify 'Absorbing overhead...'
136
137
  $LOAD_PATH.unshift 'lib', 'test', 'spec'
137
138
  Dir[*overhead_file_globs].each do |file|
@@ -139,23 +140,11 @@ module Test
139
140
  end
140
141
  end
141
142
 
142
- def load_lib_support
143
- Dir[File.expand_path '../loop/support/*.rb', __FILE__].each {|f| load f }
144
- end
145
-
146
- def pause_momentarily
147
- sleep 1
148
- end
149
-
150
- def forcibly_run_all_tests
151
- notify 'Running all tests...'
152
- @last_ran_at = Time.at(0)
153
- @lines_by_file.clear
154
- end
155
-
156
- def run_test_loop
143
+ def enter_testing_loop
157
144
  notify 'Ready for testing!'
158
145
  loop do
146
+ reap_worker_queue
147
+
159
148
  # find test files that have been modified since the last run
160
149
  test_files = test_file_matchers.map do |source_glob, test_matcher|
161
150
  Dir[source_glob].select {|file| File.mtime(file) > @last_ran_at }.
@@ -179,38 +168,85 @@ module Test
179
168
 
180
169
  # fork workers to run the test files in parallel,
181
170
  # excluding test files that are already running
182
- test_files = @running_files_lock.
183
- synchronize { test_files - @running_files }
184
-
171
+ test_files -= currently_running_test_files
185
172
  unless test_files.empty?
186
173
  @last_ran_at = Time.now
187
- test_files.each {|file| run_test_file file }
174
+ test_files.each {|file| fork_worker Worker.new(file) }
188
175
  end
189
176
 
190
177
  pause_momentarily
191
178
  end
192
179
  end
193
180
 
194
- def run_test_file test_file
195
- @running_files_lock.synchronize { @running_files.push test_file }
196
- log_file = test_file + '.log'
181
+ def currently_running_test_files
182
+ @worker_by_pid.values.map(&:test_file)
183
+ end
184
+
185
+ def init_worker_queue
186
+ # collect children (of which some may be workers) for reaping below
187
+ @exited_child_infos = []
188
+ trap :CHLD do
189
+ finished_at = Time.now
190
+ begin
191
+ while wait2_array = Process.wait2(-1, Process::WNOHANG)
192
+ @exited_child_infos.push [wait2_array, finished_at]
193
+ end
194
+ rescue SystemCallError
195
+ # raised by wait() when there are no child processes at all
196
+ end
197
+ end
198
+ end
199
+
200
+ # reap finished workers from previous iterations of the loop
201
+ def reap_worker_queue
202
+ while info = @exited_child_infos.shift
203
+ (child_pid, exit_status), finished_at = info
204
+ if worker = @worker_by_pid.delete(child_pid)
205
+ worker.exit_status = exit_status
206
+ worker.finished_at = finished_at
207
+ reap_worker worker
208
+ end
209
+ end
210
+ end
211
+
212
+ def stop_worker_queue
213
+ notify 'Stopping tests...'
214
+ trap :CHLD, 'DEFAULT'
215
+ @worker_by_pid.each_key do |worker_pid|
216
+ begin
217
+ Process.kill :TERM, -worker_pid
218
+ rescue SystemCallError
219
+ # worker is already terminated
220
+ end
221
+ end
222
+ Process.waitall
223
+ end
224
+
225
+ def fork_worker worker
226
+ notify "TEST #{worker.test_file}"
227
+
228
+ worker.log_file = worker.test_file + '.log'
197
229
 
198
230
  # cache the contents of the test file for diffing below
199
- new_lines = File.readlines(test_file)
200
- old_lines = @lines_by_file[test_file] || new_lines
201
- @lines_by_file[test_file] = new_lines
231
+ new_lines = File.readlines(worker.test_file)
232
+ old_lines = @lines_by_file[worker.test_file] || new_lines
233
+ @lines_by_file[worker.test_file] = new_lines
234
+
235
+ worker.started_at = Time.now
236
+ pid = fork do
237
+ # detach worker from master's terminal device so that
238
+ # it does not receieve the user's control-key presses
239
+ Process.setsid
202
240
 
203
- worker_pid = fork do
204
- # this signal is ignored in master and honored in workers, so all
205
- # workers can be killed by sending it to the entire process group
206
- trap :TERM, :DEFAULT
241
+ # unregister signal handlers inherited from master process
242
+ [:INT, :TSTP, :QUIT].each {|sig| trap sig, 'DEFAULT' }
207
243
 
208
- # this signal is honored by master and ignored in workers
209
- trap :INT, :IGNORE
244
+ # detach worker from master's standard input stream
245
+ STDIN.reopen IO.pipe.first
210
246
 
211
247
  # capture test output in log file because tests are run in parallel
212
248
  # which makes it difficult to understand interleaved output thereof
213
- $stderr.reopen($stdout.reopen(log_file, 'w')).sync = true
249
+ STDERR.reopen(STDOUT.reopen(worker.log_file, 'w')).sync = true
214
250
 
215
251
  # determine which test blocks have changed inside the test file
216
252
  test_names = Diff::LCS.diff(old_lines, new_lines).flatten.map do |change|
@@ -226,39 +262,34 @@ module Test
226
262
  end.compact.uniq
227
263
 
228
264
  # tell the testing framework to run only the changed test blocks
229
- before_each_test.each {|f| f.call test_file, log_file, test_names }
265
+ before_each_test.each do |hook|
266
+ hook.call worker.test_file, worker.log_file, test_names
267
+ end
230
268
 
231
269
  # make the process title Test::Unit friendly and ps(1) searchable
232
- $0 = "test-loop #{test_file}"
270
+ $0 = "test-loop #{worker.test_file}"
233
271
 
234
272
  # after loading the user's test file, the at_exit() hook of the
235
273
  # user's testing framework will take care of running the tests and
236
274
  # reflecting any failures in the worker process' exit status
237
- load test_file
275
+ load worker.test_file
238
276
  end
239
277
 
240
- # monitor and report on the worker's progress
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
278
+ @worker_by_pid[pid] = worker
279
+ end
256
280
 
257
- after_each_test.each do |f|
258
- f.call test_file, log_file, run_status, @last_ran_at, elapsed_time
259
- end
281
+ def reap_worker worker
282
+ # report test results along with any failure logs
283
+ if worker.exit_status.success?
284
+ notify ANSI_GREEN % "PASS #{worker.test_file}"
285
+ elsif worker.exit_status.exited?
286
+ notify ANSI_RED % "FAIL #{worker.test_file}"
287
+ STDERR.print File.read(worker.log_file)
288
+ end
260
289
 
261
- @running_files_lock.synchronize { @running_files.delete test_file }
290
+ after_each_test.each do |hook|
291
+ hook.call worker.test_file, worker.log_file, worker.exit_status,
292
+ worker.started_at, worker.finished_at - worker.started_at
262
293
  end
263
294
  end
264
295
  end
metadata CHANGED
@@ -2,17 +2,17 @@
2
2
  name: test-loop
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 11.0.0
5
+ version: 12.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Suraj N. Kurapati
9
9
  - Brian D. Burns
10
+ - Daniel Pittman
10
11
  autorequire:
11
12
  bindir: bin
12
13
  cert_chain: []
13
14
 
14
- date: 2011-04-14 00:00:00 -07:00
15
- default_executable:
15
+ date: 2011-06-01 00:00:00 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: diff-lcs
@@ -34,14 +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/support/rails.rb
42
- - lib/test/loop/support/minitest.rb
43
- - lib/test/loop/support/testunit.rb
44
- has_rdoc: true
42
+ - lib/test/loop/rails.rb
45
43
  homepage: http://github.com/sunaku/test-loop
46
44
  licenses: []
47
45
 
@@ -65,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
65
63
  requirements: []
66
64
 
67
65
  rubyforge_project:
68
- rubygems_version: 1.6.2
66
+ rubygems_version: 1.8.5
69
67
  signing_key:
70
68
  specification_version: 3
71
69
  summary: Continuous testing for Ruby with fork/eval
@@ -1,5 +0,0 @@
1
- if defined? MiniTest::Unit
2
- Test::Loop.before_each_test.push proc {
3
- MiniTest::Unit.output = $stdout
4
- }
5
- end
@@ -1,21 +0,0 @@
1
- if defined? Rails::VERSION
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
- class << Test::Loop
12
- class Railtie < Rails::Railtie
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
@@ -1,7 +0,0 @@
1
- if defined? Test::Unit::AutoRunner
2
- Test::Loop.before_each_test.push proc {
3
- Test::Unit::AutoRunner.prepare do |config|
4
- config.runner_options[:output] = $stdout
5
- end
6
- }
7
- end