test-loop 9.3.0 → 9.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +101 -80
- data/bin/test-loop +1 -0
- data/lib/test/loop.rb +27 -20
- metadata +2 -2
data/README.md
CHANGED
@@ -59,7 +59,7 @@ If installed as a Git clone:
|
|
59
59
|
|
60
60
|
You can monitor your test processes in another terminal:
|
61
61
|
|
62
|
-
watch
|
62
|
+
watch 'ps xf | grep test-loop | sed 1,3d'
|
63
63
|
|
64
64
|
------------------------------------------------------------------------------
|
65
65
|
Operation
|
@@ -81,119 +81,140 @@ test-loop looks for a configuration file named `.test-loop` in the current
|
|
81
81
|
working directory. This configuration file is a normal Ruby script in which
|
82
82
|
you can query and modify the `Test::Loop` OpenStruct configuration as follows:
|
83
83
|
|
84
|
-
|
85
|
-
describe a set of Ruby scripts that are loaded into the main Ruby process as
|
86
|
-
overhead.
|
84
|
+
### Test::Loop.overhead_file_globs
|
87
85
|
|
88
|
-
|
89
|
-
|
90
|
-
they change.
|
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.
|
91
88
|
|
92
|
-
|
93
|
-
describing a set of source files to a lambda function yielding a file
|
94
|
-
globbing pattern describing a set of test files that need to be run. In
|
95
|
-
other words, whenever the source files (the hash key; left-hand side of the
|
96
|
-
mapping) change, their associated test files (the hash value; right-hand
|
97
|
-
side of the mapping) are run.
|
89
|
+
### Test::Loop.reabsorb_file_globs
|
98
90
|
|
99
|
-
|
100
|
-
|
91
|
+
An array of file globbing patterns that describe a set of files which cause
|
92
|
+
the overhead to be reabsorbed whenever they change.
|
101
93
|
|
102
|
-
|
103
|
-
* `app/world.rb` => `spec/world_ldrow.rb`
|
94
|
+
### Test::Loop.test_file_matchers
|
104
95
|
|
105
|
-
|
96
|
+
A hash that maps a file globbing pattern describing a set of source files to a
|
97
|
+
lambda function yielding a file globbing pattern describing a set of test
|
98
|
+
files that need to be run. In other words, whenever the source files (the
|
99
|
+
hash key; left-hand side of the mapping) change, their associated test files
|
100
|
+
(the hash value; right-hand side of the mapping) are run.
|
106
101
|
|
107
|
-
|
108
|
-
|
109
|
-
name = File.basename(path, extn)
|
110
|
-
"{test,spec}/**/#{name}_#{name.reverse}#{extn}"
|
111
|
-
end
|
112
|
-
|
113
|
-
* `Test::Loop.test_name_parser` is a lambda function that is passed a line of
|
114
|
-
source code to determine whether that line can be considered as a test
|
115
|
-
definition, in which case it must return the name of the test being defined.
|
102
|
+
For example, if test files had the same names as their source files followed
|
103
|
+
by an underscore and the file name in reverse like this:
|
116
104
|
|
117
|
-
* `
|
118
|
-
|
119
|
-
to the test file, (2) the path to the log file containing the live output of
|
120
|
-
running the test file, and (3) an array containing the names of tests (which
|
121
|
-
were identified by `Test::Loop.test_name_parser`) inside the test file that
|
122
|
-
have changed since the last run of the test file.
|
105
|
+
* `lib/hello.rb` => `test/hello_olleh.rb`
|
106
|
+
* `app/world.rb` => `spec/world_ldrow.rb`
|
123
107
|
|
124
|
-
|
125
|
-
to only run certain test blocks inside the test file. This accelerates your
|
126
|
-
test-driven development cycle and improves productivity!
|
108
|
+
Then you would add the following to your configuration file:
|
127
109
|
|
128
|
-
|
110
|
+
Test::Loop.test_file_matchers['{lib,app}/**/*.rb'] = lambda do |path|
|
111
|
+
extn = File.extname(path)
|
112
|
+
name = File.basename(path, extn)
|
113
|
+
"{test,spec}/**/#{name}_#{name.reverse}#{extn}"
|
114
|
+
end
|
129
115
|
|
130
|
-
|
116
|
+
In addition, these lambda functions can return `nil` if they do not wish for a
|
117
|
+
particular source file to be tested. For example, to ignore tests for all
|
118
|
+
source files except those within a `models/` directory, you would write:
|
131
119
|
|
132
|
-
|
133
|
-
|
134
|
-
|
120
|
+
Test::Loop.test_file_matchers['{lib,app}/**/*.rb'] = lambda do |path|
|
121
|
+
if path.include? '/models/'
|
122
|
+
"{test,spec}/**/#{File.basename(path)}"
|
135
123
|
end
|
124
|
+
end
|
136
125
|
|
137
|
-
|
126
|
+
For source files not satisfying the above constraint, this lambda function
|
127
|
+
will return `nil`, thereby excluding those source files from being tested.
|
138
128
|
|
139
|
-
|
140
|
-
# your replacement here ...
|
141
|
-
end
|
129
|
+
### Test::Loop.test_name_parser
|
142
130
|
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
running the test file, (3) a `Process::Status` object describing the exit
|
147
|
-
status of the worker process that ran the test file, (4) the time when test
|
148
|
-
execution began, and (5) how many seconds it took for the overall test
|
149
|
-
execution to complete.
|
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.
|
150
134
|
|
151
|
-
|
152
|
-
your configuration file:
|
135
|
+
### Test::Loop.before_each_test
|
153
136
|
|
154
|
-
|
155
|
-
|
156
|
-
|
137
|
+
A lambda function that is executed inside the worker process before loading
|
138
|
+
the test file. It is passed (1) the path to the test file, (2) the path to
|
139
|
+
the log file containing the live output of running the test file, and (3) an
|
140
|
+
array containing the names of tests (which were identified by
|
141
|
+
`Test::Loop.test_name_parser`) inside the test file that have changed since
|
142
|
+
the last run of the test file.
|
157
143
|
|
158
|
-
|
159
|
-
|
144
|
+
The default implementation of this function instructs Test::Unit and RSpec to
|
145
|
+
only run certain test blocks inside the test file. This accelerates your
|
146
|
+
test-driven development cycle and improves productivity!
|
160
147
|
|
161
|
-
|
162
|
-
unless run_status.success?
|
163
|
-
title = 'FAIL at %s in %0.1fs' % [started_at.strftime('%r'), elapsed_time]
|
148
|
+
If you wish to add extend the default implementation, store and recall it:
|
164
149
|
|
165
|
-
|
150
|
+
default_implementation = Test::Loop.before_each_test
|
166
151
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
end
|
172
|
-
end
|
173
|
-
end
|
152
|
+
Test::Loop.before_each_test = lambda do |test_file, log_file, test_names|
|
153
|
+
default_implementation.call test_file, log_file, test_names
|
154
|
+
# do something additional ...
|
155
|
+
end
|
174
156
|
|
175
|
-
|
157
|
+
Or if you want to completely replace the default implementation:
|
176
158
|
|
177
|
-
|
159
|
+
Test::Loop.before_each_test = lambda do |test_file, log_file, test_names|
|
160
|
+
# your replacement here ...
|
161
|
+
end
|
162
|
+
|
163
|
+
### Test::Loop.after_each_test
|
164
|
+
|
165
|
+
A lambda function that is executed inside the master process after a test has
|
166
|
+
finished running. It is passed (1) the path to the test file, (2) the path to
|
167
|
+
the log file containing the output of running the test file, (3) a
|
168
|
+
`Process::Status` object describing the exit status of the worker process that
|
169
|
+
ran the test file, (4) the time when test execution began, and (5) how many
|
170
|
+
seconds it took for the overall test execution to complete.
|
178
171
|
|
179
|
-
|
180
|
-
|
181
|
-
configuration file:
|
172
|
+
For example, to delete log files for successful tests, add the following to
|
173
|
+
your configuration file:
|
182
174
|
|
183
|
-
|
184
|
-
|
175
|
+
Test::Loop.after_each_test = lambda do |test_file, log_file, run_status, started_at, elapsed_time|
|
176
|
+
File.delete(log_file) if run_status.success?
|
177
|
+
end
|
185
178
|
|
186
|
-
|
187
|
-
|
179
|
+
For example, to see on-screen-display notifications only about test failures,
|
180
|
+
add the following to your configuration file:
|
181
|
+
|
182
|
+
Test::Loop.after_each_test = lambda do |test_file, log_file, run_status, started_at, elapsed_time|
|
183
|
+
unless run_status.success?
|
184
|
+
title = 'FAIL at %s in %0.1fs' % [started_at.strftime('%r'), elapsed_time]
|
188
185
|
|
189
186
|
message = test_file
|
190
187
|
|
191
188
|
Thread.new do # run in background
|
192
|
-
system 'notify-send', '-i',
|
189
|
+
system 'notify-send', '-i', 'dialog-error', title, message or
|
193
190
|
system 'growlnotify', '-a', 'Xcode', '-m', message, title or
|
194
191
|
system 'xmessage', '-timeout', '5', '-title', title, message
|
195
192
|
end
|
196
193
|
end
|
194
|
+
end
|
195
|
+
|
196
|
+
Note that the above functionality is available as a configuration preset:
|
197
|
+
|
198
|
+
require 'test/loop/notify'
|
199
|
+
|
200
|
+
For example, to see on-screen-display notifications about completed test runs,
|
201
|
+
regardless of whether they passed or failed, add the following to your
|
202
|
+
configuration file:
|
203
|
+
|
204
|
+
Test::Loop.after_each_test = lambda do |test_file, log_file, run_status, started_at, elapsed_time|
|
205
|
+
success = run_status.success?
|
206
|
+
|
207
|
+
title = '%s at %s in %0.1fs' %
|
208
|
+
[success ? 'PASS' : 'FAIL', started_at.strftime('%X'), elapsed_time]
|
209
|
+
|
210
|
+
message = test_file
|
211
|
+
|
212
|
+
Thread.new do # run in background
|
213
|
+
system 'notify-send', '-i', "dialog-#{success ? 'information' : 'error'}", title, message or
|
214
|
+
system 'growlnotify', '-a', 'Xcode', '-m', message, title or
|
215
|
+
system 'xmessage', '-timeout', '5', '-title', title, message
|
216
|
+
end
|
217
|
+
end
|
197
218
|
|
198
219
|
------------------------------------------------------------------------------
|
199
220
|
Configuration Presets
|
data/bin/test-loop
CHANGED
data/lib/test/loop.rb
CHANGED
@@ -48,14 +48,21 @@ module Test
|
|
48
48
|
|
49
49
|
class << Loop
|
50
50
|
def run
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
51
|
+
@running_files = []
|
52
|
+
@running_files_lock = Mutex.new
|
53
|
+
@lines_by_file = {} # path => readlines
|
54
|
+
@last_ran_at = @started_at = Time.now
|
55
|
+
|
56
|
+
catch self do
|
57
|
+
register_signals
|
58
|
+
load_user_config
|
59
|
+
absorb_overhead
|
60
|
+
run_test_loop
|
61
|
+
end
|
62
|
+
|
63
|
+
kill_workers
|
58
64
|
notify 'Goodbye!'
|
65
|
+
|
59
66
|
rescue Exception => error
|
60
67
|
STDERR.puts error.inspect, error.backtrace
|
61
68
|
pause_momentarily
|
@@ -76,22 +83,16 @@ module Test
|
|
76
83
|
print "test-loop: #{message}\n"
|
77
84
|
end
|
78
85
|
|
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
|
-
|
86
86
|
def register_signals
|
87
|
+
# this signal is ignored in master and honored in workers, so all
|
88
|
+
# workers can be killed by sending it to the entire process group
|
89
|
+
trap :TERM, :IGNORE
|
90
|
+
|
87
91
|
# clear line to shield normal output from control-key interference:
|
88
92
|
# some shells like BASH emit text when control-key combos are pressed
|
89
|
-
trap(:INT) { print ANSI_CLEAR_LINE;
|
93
|
+
trap(:INT) { print ANSI_CLEAR_LINE; throw self }
|
90
94
|
trap(:QUIT) { print ANSI_CLEAR_LINE; reload_master_process }
|
91
95
|
trap(:TSTP) { print ANSI_CLEAR_LINE; forcibly_run_all_tests }
|
92
|
-
|
93
|
-
master_pid = $$
|
94
|
-
trap(:TERM) { exit unless $$ == master_pid }
|
95
96
|
end
|
96
97
|
|
97
98
|
def kill_workers
|
@@ -139,7 +140,7 @@ module Test
|
|
139
140
|
# find test files that have been modified since the last run
|
140
141
|
test_files = test_file_matchers.map do |source_glob, test_matcher|
|
141
142
|
Dir[source_glob].select {|file| File.mtime(file) > @last_ran_at }.
|
142
|
-
map {|path| Dir[test_matcher.call
|
143
|
+
map {|path| Dir[test_matcher.call(path).to_s] }
|
143
144
|
end.flatten.uniq
|
144
145
|
|
145
146
|
# resume test files stopped by the previous incarnation of test-loop
|
@@ -181,6 +182,9 @@ module Test
|
|
181
182
|
@lines_by_file[test_file] = new_lines
|
182
183
|
|
183
184
|
worker_pid = fork do
|
185
|
+
# unregister custom signal handlers meant for master process
|
186
|
+
[:TERM, :INT, :QUIT, :TSTP].each {|sig| trap sig, :DEFAULT }
|
187
|
+
|
184
188
|
# capture test output in log file because tests are run in parallel
|
185
189
|
# which makes it difficult to understand interleaved output thereof
|
186
190
|
$stdout.reopen log_file, 'w'
|
@@ -203,10 +207,13 @@ module Test
|
|
203
207
|
# tell the testing framework to run only the changed test blocks
|
204
208
|
before_each_test.call test_file, log_file, test_names
|
205
209
|
|
210
|
+
# make the process title Test::Unit friendly and ps(1) searchable
|
211
|
+
$0 = "test-loop #{test_file}"
|
212
|
+
|
206
213
|
# after loading the user's test file, the at_exit() hook of the
|
207
214
|
# user's testing framework will take care of running the tests and
|
208
215
|
# reflecting any failures in the worker process' exit status
|
209
|
-
load
|
216
|
+
load test_file
|
210
217
|
end
|
211
218
|
|
212
219
|
# monitor and report on the worker's progress
|
metadata
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
name: test-loop
|
3
3
|
version: !ruby/object:Gem::Version
|
4
4
|
prerelease:
|
5
|
-
version: 9.
|
5
|
+
version: 9.4.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-06 00:00:00 -07:00
|
15
15
|
default_executable:
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|