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.
Files changed (4) hide show
  1. data/README.md +101 -80
  2. data/bin/test-loop +1 -0
  3. data/lib/test/loop.rb +27 -20
  4. 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 "ps xf | egrep 'test-loop|(test|spec)/.+\.rb' | sed '1,3d'"
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
- * `Test::Loop.overhead_file_globs` is an array of file globbing patterns that
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
- * `Test::Loop.reabsorb_file_globs` is an array of file globbing patterns that
89
- describe a set of files which cause the overhead to be reabsorbed whenever
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
- * `Test::Loop.test_file_matchers` is a hash that maps a file globbing pattern
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
- For example, if test files had the same names as their source files followed
100
- by an underscore and the file name in reverse like this:
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
- * `lib/hello.rb` => `test/hello_olleh.rb`
103
- * `app/world.rb` => `spec/world_ldrow.rb`
94
+ ### Test::Loop.test_file_matchers
104
95
 
105
- Then you would add the following to your configuration file:
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
- Test::Loop.test_file_matchers['{lib,app}/**/*.rb'] = lambda do |path|
108
- extn = File.extname(path)
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
- * `Test::Loop.before_each_test` is a lambda function that is executed inside
118
- the worker process before loading the test file. It is passed (1) the path
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
- The default implementation of this function instructs Test::Unit and RSpec
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
- If you wish to add extend the default implementation, store and recall it:
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
- default_implementation = Test::Loop.before_each_test
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
- Test::Loop.before_each_test = lambda do |test_file, log_file, test_names|
133
- default_implementation.call test_file, log_file, test_names
134
- # do something additional ...
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
- Or if you want to completely replace the default implementation:
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
- Test::Loop.before_each_test = lambda do |test_file, log_file, test_names|
140
- # your replacement here ...
141
- end
129
+ ### Test::Loop.test_name_parser
142
130
 
143
- * `Test::Loop.after_each_test` is a lambda function that is executed inside
144
- the master process after a test has finished running. It is passed (1) the
145
- path to the test file, (2) the path to the log file containing the output of
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
- For example, to delete log files for successful tests, add the following to
152
- your configuration file:
135
+ ### Test::Loop.before_each_test
153
136
 
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
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
- For example, to see on-screen-display notifications only about test
159
- failures, add the following to your configuration file:
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
- Test::Loop.after_each_test = lambda do |test_file, log_file, run_status, started_at, elapsed_time|
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
- message = test_file
150
+ default_implementation = Test::Loop.before_each_test
166
151
 
167
- Thread.new do # run in background
168
- system 'notify-send', '-i', 'dialog-error', title, message or
169
- system 'growlnotify', '-a', 'Xcode', '-m', message, title or
170
- system 'xmessage', '-timeout', '5', '-title', title, message
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
- Note that the above functionality is available as a configuration preset:
157
+ Or if you want to completely replace the default implementation:
176
158
 
177
- require 'test/loop/notify'
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
- For example, to see on-screen-display notifications about completed test
180
- runs, regardless of whether they passed or failed, add the following to your
181
- configuration file:
172
+ For example, to delete log files for successful tests, add the following to
173
+ your configuration file:
182
174
 
183
- Test::Loop.after_each_test = lambda do |test_file, log_file, run_status, started_at, elapsed_time|
184
- success = run_status.success?
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
- title = '%s at %s in %0.1fs' %
187
- [success ? 'PASS' : 'FAIL', started_at.strftime('%X'), elapsed_time]
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', "dialog-#{success ? 'information' : 'error'}", title, message or
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
@@ -1,3 +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
data/lib/test/loop.rb CHANGED
@@ -48,14 +48,21 @@ module Test
48
48
 
49
49
  class << Loop
50
50
  def run
51
- init_test_loop
52
- register_signals
53
- load_user_config
54
- absorb_overhead
55
- run_test_loop
56
- rescue SystemExit
57
- # allow exit() to terminate the test loop
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; kill_workers; exit }
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 path] }
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 $0 = test_file # set $0 because Test::Unit outputs it
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.3.0
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-01 00:00:00 -07:00
14
+ date: 2011-04-06 00:00:00 -07:00
15
15
  default_executable:
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency