test-loop 9.3.0 → 9.4.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/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
|