test-loop 9.2.0 → 9.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +50 -34
- data/lib/test/loop.rb +58 -51
- metadata +3 -13
data/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
test-loop - Continuous testing for Ruby with fork/eval
|
2
|
-
|
2
|
+
==============================================================================
|
3
3
|
|
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:
|
@@ -8,9 +8,9 @@ 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
|
-
|
11
|
+
------------------------------------------------------------------------------
|
12
12
|
Features
|
13
|
-
|
13
|
+
------------------------------------------------------------------------------
|
14
14
|
|
15
15
|
* Tests *changes* in your Ruby application: avoids running (1) unchanged
|
16
16
|
test files and (2) unchanged test blocks inside changed test files.
|
@@ -30,11 +30,11 @@ Features
|
|
30
30
|
|
31
31
|
* Configurable through a `.test-loop` file in your current working directory.
|
32
32
|
|
33
|
-
* Implemented in less than
|
34
|
-
|
33
|
+
* Implemented in less than 200 lines (SLOC) of pure Ruby code! :-)
|
35
34
|
|
35
|
+
------------------------------------------------------------------------------
|
36
36
|
Installation
|
37
|
-
|
37
|
+
------------------------------------------------------------------------------
|
38
38
|
|
39
39
|
As a Ruby gem:
|
40
40
|
|
@@ -43,12 +43,11 @@ As a Ruby gem:
|
|
43
43
|
As a Git clone:
|
44
44
|
|
45
45
|
gem install diff-lcs -v '>= 1.1.2'
|
46
|
-
gem install ansi -v '>= 1.2.2'
|
47
46
|
git clone git://github.com/sunaku/test-loop
|
48
47
|
|
49
|
-
|
48
|
+
------------------------------------------------------------------------------
|
50
49
|
Invocation
|
51
|
-
|
50
|
+
------------------------------------------------------------------------------
|
52
51
|
|
53
52
|
If installed as a Ruby gem:
|
54
53
|
|
@@ -58,9 +57,13 @@ If installed as a Git clone:
|
|
58
57
|
|
59
58
|
env RUBYLIB=lib ruby bin/test-loop
|
60
59
|
|
60
|
+
You can monitor your test processes in another terminal:
|
61
|
+
|
62
|
+
watch "ps xf | egrep 'test-loop|(test|spec)/.+\.rb' | sed '1,3d'"
|
61
63
|
|
64
|
+
------------------------------------------------------------------------------
|
62
65
|
Operation
|
63
|
-
|
66
|
+
------------------------------------------------------------------------------
|
64
67
|
|
65
68
|
* Press Control-Z or send the SIGTSTP signal to forcibly run all
|
66
69
|
tests, even if there are no changes in your Ruby application.
|
@@ -70,25 +73,9 @@ Operation
|
|
70
73
|
|
71
74
|
* Press Control-C or send the SIGINT signal to quit the test loop.
|
72
75
|
|
73
|
-
|
74
|
-
Configuration Presets
|
75
|
-
---------------------
|
76
|
-
|
77
|
-
The following sub-libraries provide "preset" configurations. To use them,
|
78
|
-
simply add the require() lines shown below to your `.test-loop` file or to
|
79
|
-
your `{test,spec}/{test,spec}_helper.rb` files.
|
80
|
-
|
81
|
-
* Defaults for Ruby on Rails testing:
|
82
|
-
|
83
|
-
require 'test/loop/rails'
|
84
|
-
|
85
|
-
* OSD notifications on test failures:
|
86
|
-
|
87
|
-
require 'test/loop/notify'
|
88
|
-
|
89
|
-
|
76
|
+
------------------------------------------------------------------------------
|
90
77
|
Configuration
|
91
|
-
|
78
|
+
------------------------------------------------------------------------------
|
92
79
|
|
93
80
|
test-loop looks for a configuration file named `.test-loop` in the current
|
94
81
|
working directory. This configuration file is a normal Ruby script in which
|
@@ -112,8 +99,8 @@ you can query and modify the `Test::Loop` OpenStruct configuration as follows:
|
|
112
99
|
For example, if test files had the same names as their source files followed
|
113
100
|
by an underscore and the file name in reverse like this:
|
114
101
|
|
115
|
-
* lib/hello.rb => test/hello_olleh.rb
|
116
|
-
* app/world.rb => spec/world_ldrow.rb
|
102
|
+
* `lib/hello.rb` => `test/hello_olleh.rb`
|
103
|
+
* `app/world.rb` => `spec/world_ldrow.rb`
|
117
104
|
|
118
105
|
Then you would add the following to your configuration file:
|
119
106
|
|
@@ -161,6 +148,13 @@ you can query and modify the `Test::Loop` OpenStruct configuration as follows:
|
|
161
148
|
execution began, and (5) how many seconds it took for the overall test
|
162
149
|
execution to complete.
|
163
150
|
|
151
|
+
For example, to delete log files for successful tests, add the following to
|
152
|
+
your configuration file:
|
153
|
+
|
154
|
+
Test::Loop.after_each_test = lambda do |test_file, log_file, run_status, started_at, elapsed_time|
|
155
|
+
File.delete(log_file) if run_status.success?
|
156
|
+
end
|
157
|
+
|
164
158
|
For example, to see on-screen-display notifications only about test
|
165
159
|
failures, add the following to your configuration file:
|
166
160
|
|
@@ -178,6 +172,10 @@ you can query and modify the `Test::Loop` OpenStruct configuration as follows:
|
|
178
172
|
end
|
179
173
|
end
|
180
174
|
|
175
|
+
Note that the above functionality is available as a configuration preset:
|
176
|
+
|
177
|
+
require 'test/loop/notify'
|
178
|
+
|
181
179
|
For example, to see on-screen-display notifications about completed test
|
182
180
|
runs, regardless of whether they passed or failed, add the following to your
|
183
181
|
configuration file:
|
@@ -197,9 +195,27 @@ you can query and modify the `Test::Loop` OpenStruct configuration as follows:
|
|
197
195
|
end
|
198
196
|
end
|
199
197
|
|
198
|
+
------------------------------------------------------------------------------
|
199
|
+
Configuration Presets
|
200
|
+
------------------------------------------------------------------------------
|
200
201
|
|
202
|
+
The following sub-libraries provide "preset" configurations. To use them,
|
203
|
+
simply add the require() lines shown below to your `.test-loop` file or to
|
204
|
+
your application's `test/test_helper.rb` or `spec/spec_helper.rb` file.
|
205
|
+
|
206
|
+
* Support for Ruby on Rails testing:
|
207
|
+
|
208
|
+
require 'test/loop/rails'
|
209
|
+
|
210
|
+
* On-screen-display notifications for test failures:
|
211
|
+
|
212
|
+
require 'test/loop/notify'
|
213
|
+
|
214
|
+
------------------------------------------------------------------------------
|
201
215
|
Known issues
|
202
|
-
|
216
|
+
------------------------------------------------------------------------------
|
217
|
+
|
218
|
+
If using Ruby on Rails:
|
203
219
|
|
204
220
|
* Ensure that your `config/environments/test.rb` file disables class caching:
|
205
221
|
|
@@ -214,8 +230,8 @@ Known issues
|
|
214
230
|
adapter for SQLite3]( https://github.com/mvz/memory_test_fix ) or by using
|
215
231
|
different database software (such as MySQL) for your test environment.
|
216
232
|
|
217
|
-
|
233
|
+
------------------------------------------------------------------------------
|
218
234
|
License
|
219
|
-
|
235
|
+
------------------------------------------------------------------------------
|
220
236
|
|
221
|
-
Released under the ISC license. See the
|
237
|
+
Released under the ISC license. See the LICENSE file for details.
|
data/lib/test/loop.rb
CHANGED
@@ -1,29 +1,5 @@
|
|
1
|
-
#
|
2
|
-
# test-loop - Continuous testing for Ruby with fork/eval
|
3
|
-
# https://github.com/sunaku/test-loop#readme
|
4
|
-
#
|
5
|
-
####
|
6
|
-
#
|
7
|
-
# (the ISC license)
|
8
|
-
#
|
9
|
-
# Copyright 2010 Suraj N. Kurapati <sunaku@gmail.com>
|
10
|
-
#
|
11
|
-
# Permission to use, copy, modify, and/or distribute this software for any
|
12
|
-
# purpose with or without fee is hereby granted, provided that the above
|
13
|
-
# copyright notice and this permission notice appear in all copies.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
16
|
-
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
17
|
-
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
18
|
-
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
19
|
-
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
20
|
-
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
21
|
-
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
22
|
-
#
|
23
|
-
|
24
1
|
require 'ostruct'
|
25
2
|
require 'diff/lcs'
|
26
|
-
require 'ansi'
|
27
3
|
|
28
4
|
module Test
|
29
5
|
Loop = OpenStruct.new
|
@@ -77,6 +53,9 @@ module Test
|
|
77
53
|
load_user_config
|
78
54
|
absorb_overhead
|
79
55
|
run_test_loop
|
56
|
+
rescue SystemExit
|
57
|
+
# allow exit() to terminate the test loop
|
58
|
+
notify 'Goodbye!'
|
80
59
|
rescue Exception => error
|
81
60
|
STDERR.puts error.inspect, error.backtrace
|
82
61
|
pause_momentarily
|
@@ -86,31 +65,47 @@ module Test
|
|
86
65
|
private
|
87
66
|
|
88
67
|
EXEC_VECTOR = [$0, *ARGV].map {|s| s.dup.freeze }.freeze
|
68
|
+
RESUME_ENV_KEY = 'TEST_LOOP_RESUME_FILES'.freeze
|
69
|
+
|
70
|
+
ANSI_CLEAR_LINE = "\e[2K\e[0G".freeze
|
71
|
+
ANSI_GREEN = "\e[32m%s\e[0m".freeze
|
72
|
+
ANSI_RED = "\e[31m%s\e[0m".freeze
|
89
73
|
|
90
74
|
def notify message
|
91
75
|
# using print() because puts() is not an atomic operation
|
92
76
|
print "test-loop: #{message}\n"
|
93
77
|
end
|
94
78
|
|
79
|
+
def init_test_loop
|
80
|
+
@running_files = []
|
81
|
+
@running_files_lock = Mutex.new
|
82
|
+
@lines_by_file = {} # path => readlines
|
83
|
+
@last_ran_at = @started_at = Time.now
|
84
|
+
end
|
85
|
+
|
95
86
|
def register_signals
|
96
|
-
#
|
87
|
+
# clear line to shield normal output from control-key interference:
|
97
88
|
# some shells like BASH emit text when control-key combos are pressed
|
98
|
-
trap(:INT)
|
99
|
-
trap(:QUIT) {
|
100
|
-
trap(:TSTP) {
|
101
|
-
end
|
89
|
+
trap(:INT) { print ANSI_CLEAR_LINE; kill_workers; exit }
|
90
|
+
trap(:QUIT) { print ANSI_CLEAR_LINE; reload_master_process }
|
91
|
+
trap(:TSTP) { print ANSI_CLEAR_LINE; forcibly_run_all_tests }
|
102
92
|
|
103
|
-
|
104
|
-
|
93
|
+
master_pid = $$
|
94
|
+
trap(:TERM) { exit unless $$ == master_pid }
|
105
95
|
end
|
106
96
|
|
107
|
-
def
|
108
|
-
notify '
|
109
|
-
|
97
|
+
def kill_workers
|
98
|
+
notify 'Stopping tests...'
|
99
|
+
Process.kill :TERM, -$$
|
100
|
+
Process.waitall
|
110
101
|
end
|
111
102
|
|
112
|
-
|
113
|
-
|
103
|
+
# The given test files are passed down (along with currently running
|
104
|
+
# test files) to the next incarnation of test-loop for resumption.
|
105
|
+
def reload_master_process test_files = []
|
106
|
+
@running_files_lock.synchronize { test_files.concat @running_files }
|
107
|
+
kill_workers
|
108
|
+
exec({RESUME_ENV_KEY => test_files.inspect}, *EXEC_VECTOR)
|
114
109
|
end
|
115
110
|
|
116
111
|
def load_user_config
|
@@ -128,14 +123,12 @@ module Test
|
|
128
123
|
end
|
129
124
|
end
|
130
125
|
|
131
|
-
def
|
132
|
-
|
133
|
-
@running_files_lock = Mutex.new
|
134
|
-
@lines_by_file = {} # path => readlines
|
135
|
-
@last_ran_at = @started_at = Time.now
|
126
|
+
def pause_momentarily
|
127
|
+
sleep 1
|
136
128
|
end
|
137
129
|
|
138
130
|
def forcibly_run_all_tests
|
131
|
+
notify 'Running all tests...'
|
139
132
|
@last_ran_at = Time.at(0)
|
140
133
|
@lines_by_file.clear
|
141
134
|
end
|
@@ -143,22 +136,36 @@ module Test
|
|
143
136
|
def run_test_loop
|
144
137
|
notify 'Ready for testing!'
|
145
138
|
loop do
|
146
|
-
#
|
139
|
+
# find test files that have been modified since the last run
|
147
140
|
test_files = test_file_matchers.map do |source_glob, test_matcher|
|
148
141
|
Dir[source_glob].select {|file| File.mtime(file) > @last_ran_at }.
|
149
142
|
map {|path| Dir[test_matcher.call path] }
|
150
143
|
end.flatten.uniq
|
151
144
|
|
145
|
+
# resume test files stopped by the previous incarnation of test-loop
|
146
|
+
if ENV.key? RESUME_ENV_KEY
|
147
|
+
resume_files = eval(ENV.delete(RESUME_ENV_KEY))
|
148
|
+
unless resume_files.empty?
|
149
|
+
notify 'Resuming tests...'
|
150
|
+
test_files.concat(resume_files).uniq!
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# reabsorb test execution overhead as necessary
|
155
|
+
if Dir[*reabsorb_file_globs].any? {|f| File.mtime(f) > @started_at }
|
156
|
+
notify 'Overhead changed!'
|
157
|
+
reload_master_process test_files
|
158
|
+
end
|
159
|
+
|
160
|
+
# fork workers to run the test files in parallel,
|
161
|
+
# excluding test files that are already running
|
152
162
|
test_files = @running_files_lock.
|
153
163
|
synchronize { test_files - @running_files }
|
154
164
|
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
# reabsorb test execution overhead as necessary
|
160
|
-
reload_master_process if Dir[*reabsorb_file_globs].
|
161
|
-
any? {|file| File.mtime(file) > @started_at }
|
165
|
+
unless test_files.empty?
|
166
|
+
@last_ran_at = Time.now
|
167
|
+
test_files.each {|file| run_test_file file }
|
168
|
+
end
|
162
169
|
|
163
170
|
pause_momentarily
|
164
171
|
end
|
@@ -213,9 +220,9 @@ module Test
|
|
213
220
|
|
214
221
|
# report test results along with any failure logs
|
215
222
|
if run_status.success?
|
216
|
-
notify
|
223
|
+
notify ANSI_GREEN % "PASS #{test_file}"
|
217
224
|
else
|
218
|
-
notify
|
225
|
+
notify ANSI_RED % "FAIL #{test_file}"
|
219
226
|
STDERR.print File.read(log_file)
|
220
227
|
end
|
221
228
|
|
metadata
CHANGED
@@ -2,15 +2,16 @@
|
|
2
2
|
name: test-loop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 9.
|
5
|
+
version: 9.3.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Suraj N. Kurapati
|
9
|
+
- Brian D. Burns
|
9
10
|
autorequire:
|
10
11
|
bindir: bin
|
11
12
|
cert_chain: []
|
12
13
|
|
13
|
-
date: 2011-
|
14
|
+
date: 2011-04-01 00:00:00 -07:00
|
14
15
|
default_executable:
|
15
16
|
dependencies:
|
16
17
|
- !ruby/object:Gem::Dependency
|
@@ -24,17 +25,6 @@ dependencies:
|
|
24
25
|
version: 1.1.2
|
25
26
|
type: :runtime
|
26
27
|
version_requirements: *id001
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: ansi
|
29
|
-
prerelease: false
|
30
|
-
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
-
none: false
|
32
|
-
requirements:
|
33
|
-
- - ">="
|
34
|
-
- !ruby/object:Gem::Version
|
35
|
-
version: 1.2.2
|
36
|
-
type: :runtime
|
37
|
-
version_requirements: *id002
|
38
28
|
description:
|
39
29
|
email:
|
40
30
|
executables:
|