testr 14.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,19 @@
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
+ Copyright 2011 Jacob Helwig <jacob@technosorcery.net>
7
+ Copyright 2011 Corné Verbruggen <corne@g-majeur.nl>
8
+
9
+ Permission to use, copy, modify, and/or distribute this software for any
10
+ purpose with or without fee is hereby granted, provided that the above
11
+ copyright notice and this permission notice appear in all copies.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
14
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
15
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
17
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
18
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
19
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
@@ -0,0 +1,269 @@
1
+ TestR - Continuous testing tool for Ruby
2
+ ==============================================================================
3
+
4
+ TestR is a continuous testing tool for Ruby that automatically detects and
5
+ tests changes in your Ruby application or test suite in an efficient manner:
6
+
7
+ 1. Absorbs test execution overhead into the master Ruby process.
8
+
9
+ 2. Forks to run your test files in parallel and without overhead.
10
+
11
+ 3. Avoids running unchanged tests inside changed test files.
12
+
13
+ ------------------------------------------------------------------------------
14
+ Features
15
+ ------------------------------------------------------------------------------
16
+
17
+ * Executes test files in parallel, making full use of multi-core CPUs.
18
+
19
+ * Tests *changes* in your Ruby application: avoids running (1) unchanged
20
+ test files and (2) unchanged tests inside changed test files.
21
+
22
+ * Supports MiniTest, Test::Unit, RSpec, and any testing framework that (1)
23
+ reflects failures in the process' exit status and (2) is loaded by your
24
+ application's `test/test_helper.rb` or `spec/spec_helper.rb` file.
25
+
26
+ * Logs the output from your tests into separate files: one log per test.
27
+ The path of a log file is simply the path of its test file plus ".log".
28
+
29
+ * Configurable through a Ruby script in your current working directory.
30
+
31
+ * Implemented in less than 360 lines (SLOC) of pure Ruby code! :-)
32
+
33
+ ------------------------------------------------------------------------------
34
+ Architecture
35
+ ------------------------------------------------------------------------------
36
+
37
+ Following UNIX philosophy, TestR is made of simple text-based programs:
38
+
39
+ * `testr` is an interactive command-line user interface (CLI) for driver
40
+ * `testr-herald` monitors current directory tree and reports changed files
41
+ * `testr-driver` tells master to run tests and keeps track of test results
42
+ * `testr-master` absorbs test execution overhead and forks to run your tests
43
+
44
+ You can build your own custom TestR user interface by wrapping `testr-driver`!
45
+
46
+ ------------------------------------------------------------------------------
47
+ Prerequisites
48
+ ------------------------------------------------------------------------------
49
+
50
+ * Ruby 1.8.7 or 1.9.2 or newer.
51
+
52
+ * Operating system that supports POSIX signals and the `fork()` system call.
53
+
54
+ To check if your system qualifies, launch `irb` and enter the following:
55
+
56
+ Process.respond_to? :fork # must be true
57
+ Signal.list.key? 'TERM' # must be true
58
+ Signal.list.key? 'KILL' # must be true
59
+
60
+ ------------------------------------------------------------------------------
61
+ Installation
62
+ ------------------------------------------------------------------------------
63
+
64
+ As a Ruby gem:
65
+
66
+ gem install testr
67
+
68
+ As a Git clone:
69
+
70
+ git clone git://github.com/sunaku/testr
71
+ cd testr
72
+ bundle install
73
+
74
+ ------------------------------------------------------------------------------
75
+ Invocation
76
+ ------------------------------------------------------------------------------
77
+
78
+ If installed as a Ruby gem:
79
+
80
+ testr
81
+
82
+ If installed as a Git clone:
83
+
84
+ bundle exec ruby -Ilib bin/testr
85
+
86
+ You can monitor your test processes in another terminal:
87
+
88
+ watch 'ps xuw | sed -n "1p; /test[r]/p" | fgrep -v sed'
89
+
90
+ You can forcefully terminate TestR from another terminal:
91
+
92
+ pkill -f testr
93
+
94
+ ------------------------------------------------------------------------------
95
+ Configuration
96
+ ------------------------------------------------------------------------------
97
+
98
+ TestR looks for a configuration file named `.testr.rb` in its current working
99
+ directory. The configuration file is a normal Ruby script. Inside it, you
100
+ can query and modify the `TestR::Config` object (OpenStruct) according to the
101
+ configuration options listed below.
102
+
103
+ ------------------------------------------------------------------------------
104
+ Configuration options
105
+ ------------------------------------------------------------------------------
106
+
107
+ ### TestR::Config.max_forked_workers
108
+
109
+ Maximum number of worker processes at any given time. The default value is
110
+ the number of processors detected on your system, or 1 if detection fails.
111
+
112
+ ### TestR::Config.overhead_load_paths
113
+
114
+ Array of paths that are prepended to Ruby's `$LOAD_PATH` before the
115
+ test execution overhead is loaded into `testr-master`.
116
+
117
+ ### TestR::Config.overhead_file_globs
118
+
119
+ Array of file globbing patterns that describe a set of Ruby scripts that are
120
+ loaded into `testr-master` as test execution overhead.
121
+
122
+ ### TestR::Config.reabsorb_file_greps
123
+
124
+ Array of regular expressions that describe a set of file paths that cause the
125
+ test execution overhead to be reabsorbed in `testr-master` when they change.
126
+
127
+ ### TestR::Config.all_test_file_globs
128
+
129
+ Array of file globbing patterns that describe the set of all test files in
130
+ your Ruby application.
131
+
132
+ ### TestR::Config.test_file_globbers
133
+
134
+ Hash that maps (1) a regular expression describing a set of file paths to (2)
135
+ a lambda function yielding a file globbing pattern describing a set of
136
+ test files that need to be run. In other words, whenever the source files
137
+ (the hash key; left-hand side of the mapping) change, their associated test
138
+ files (the hash value; right-hand side of the mapping) are run.
139
+
140
+ For example, if test files had the same names as their source files followed
141
+ by an underscore and the file name in reverse like this:
142
+
143
+ * `lib/hello.rb` => `test/hello_olleh.rb`
144
+ * `app/world.rb` => `spec/world_ldrow.rb`
145
+
146
+ Then you would add the following to your configuration file:
147
+
148
+ TestR::Config.test_file_globbers[%r<^(lib|app)/.+\.rb$>] = lambda do |path|
149
+ name = File.basename(path, '.rb')
150
+ "{test,spec}/**/#{name}_#{name.reverse}.rb"
151
+ end
152
+
153
+ In addition, these lambda functions can return `nil` if they do not wish for a
154
+ particular source file to be tested. For example, to ignore tests for all
155
+ source files except those within a `models/` directory, you would write:
156
+
157
+ TestR::Config.test_file_globbers[%r<^(lib|app)/.+\.rb$>] = lambda do |path|
158
+ if path.include? '/models/'
159
+ "{test,spec}/**/#{File.basename(path)}"
160
+ end
161
+ end
162
+
163
+ ### TestR::Config.test_name_extractor
164
+
165
+ Lambda function that is given a line of source code to determine whether it
166
+ can be considered as a test definition. In which case, the function must
167
+ extract and return the name of the test being defined.
168
+
169
+ ### TestR::Config.before_fork_hooks
170
+
171
+ Array of lambda functions that are executed inside `testr-master` before a
172
+ worker process is forked to run a test file. These functions are given:
173
+
174
+ 1. The sequence number of the worker process that will be forked shortly.
175
+
176
+ 2. The path of the log file containing the live output of the worker process.
177
+
178
+ 3. The path of the test file that will be run by the worker process.
179
+
180
+ 4. An array of names of tests inside the test file that will be run. If this
181
+ array is empty, then all tests in the test file will be run.
182
+
183
+ For example, to see some real values:
184
+
185
+ TestR::Config.before_fork_hooks << lambda {
186
+ |worker_number, log_file, test_file, test_names|
187
+
188
+ p :before_fork_hooks => {
189
+ :worker_number => worker_number,
190
+ :log_file => log_file,
191
+ :test_file => test_file,
192
+ :test_names => test_names,
193
+ }
194
+ }
195
+
196
+ ### TestR::Config.after_fork_hooks
197
+
198
+ Array of lambda functions that are executed inside a worker process forked
199
+ by `testr-master`. These functions are given:
200
+
201
+ 1. The sequence number of the worker process.
202
+
203
+ 2. The path of the log file containing the live output of the worker process.
204
+
205
+ 3. The path of the test file that will be run by the worker process.
206
+
207
+ 4. An array of names of tests inside the test file that will be run. If this
208
+ array is empty, then all tests in the test file will be run.
209
+
210
+ For example, to see some real values, including the worker process' PID:
211
+
212
+ TestR::Config.after_fork_hooks << lambda {
213
+ |worker_number, log_file, test_file, test_names|
214
+
215
+ p :after_fork_hooks => {
216
+ :worker_pid => $$,
217
+ :worker_number => worker_number,
218
+ :log_file => log_file,
219
+ :test_file => test_file,
220
+ :test_names => test_names,
221
+ }
222
+ }
223
+
224
+ The first function in this array instructs Test::Unit and RSpec to only run
225
+ those tests that correspond to the given `test_names` values. This
226
+ accelerates your test-driven development cycle and improves productivity!
227
+
228
+ ------------------------------------------------------------------------------
229
+ Configuration helpers
230
+ ------------------------------------------------------------------------------
231
+
232
+ The following libraries assist you with configuring TestR. To use them,
233
+ simply add the require() lines shown below to your configuration file.
234
+
235
+ ### require 'testr/config/rails'
236
+
237
+ Support for the [Ruby on Rails](http://rubyonrails.org) web framework.
238
+
239
+ ### require 'testr/config/parallel_tests'
240
+
241
+ Support for the [parallel_tests](https://github.com/grosser/parallel_tests)
242
+ library.
243
+
244
+ ------------------------------------------------------------------------------
245
+ Known issues
246
+ ------------------------------------------------------------------------------
247
+
248
+ ### Ruby on Rails
249
+
250
+ * Ensure that your `config/environments/test.rb` file disables class caching
251
+ as follows (**NOTE:** if you are using Rails 3, the `testr/config/rails`
252
+ configuration helper can do this for you automatically):
253
+
254
+ config.cache_classes = false
255
+
256
+ Otherwise, TestR will appear to ignore source-code changes in your
257
+ models, controllers, helpers, and other Ruby source files.
258
+
259
+ * SQLite3 [raises `SQLite3::BusyException: database is locked` errors](
260
+ https://github.com/sunaku/test-loop/issues/2 ) because TestR runs your
261
+ test files in parallel. You can work around this by using an [in-memory
262
+ adapter for SQLite3]( https://github.com/mvz/memory_test_fix ) or by using
263
+ different database software (such as MySQL) for your test environment.
264
+
265
+ ------------------------------------------------------------------------------
266
+ License
267
+ ------------------------------------------------------------------------------
268
+
269
+ Released under the ISC license. See the LICENSE file for details.
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'json'
4
+ require 'testr/client'
5
+
6
+ @driver = TestR::Client::Transceiver.new('testr-driver') do |line|
7
+ event, *details = JSON.load(line)
8
+
9
+ case event = event.to_sym
10
+ when :load then warn 'testr: Overhead absorbed; Ready for testing!'
11
+ when :over then warn 'testr: Reabsorbing changed overhead files...'
12
+ else
13
+ test_file, test_names, *details = details
14
+ message = [event.upcase, test_file, test_names.inspect, details].join(' ')
15
+
16
+ color = case event
17
+ when :pass then "\e[32m%s\e[0m" # green
18
+ when :fail then "\e[31m%s\e[0m" # red
19
+ end
20
+ message = color % message if color and STDOUT.tty?
21
+ message = [message, File.read(test_file + '.log'), message] if event == :fail
22
+
23
+ puts message
24
+ end
25
+ end
26
+
27
+ COMMANDS = {
28
+ 'r' => :run_all_test_files,
29
+ 's' => :stop_running_test_files,
30
+ 'p' => :rerun_passed_test_files,
31
+ 'f' => :rerun_failed_test_files,
32
+ 'o' => :reabsorb_overhead_files,
33
+ 'q' => :quit,
34
+ }
35
+
36
+ def COMMANDS.show
37
+ each {|key, cmd| warn "testr: Type #{key} to #{cmd.to_s.tr('_', ' ')}." }
38
+ end
39
+
40
+ COMMANDS.show # instruct newbies
41
+
42
+ while key = STDIN.gets.chomp
43
+ if command = COMMANDS[key]
44
+ if command == :quit
45
+ @driver.quit
46
+ break
47
+ else
48
+ @driver.send [command]
49
+ end
50
+ else
51
+ COMMANDS.show
52
+ end
53
+ end
54
+
55
+ Process.waitall
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'testr/driver'
3
+ TestR::Driver.loop
4
+ Process.waitall
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+ STDOUT.sync = true
3
+
4
+ require 'guard'
5
+ require 'guard/listener'
6
+
7
+ listener = Guard::Listener.select_and_init
8
+ listener.on_change {|files| puts files }
9
+ listener.start
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'testr/master'
3
+ TestR::Master.loop
4
+ raise SystemExit # prevent empty test suite from running in the master process
@@ -0,0 +1,39 @@
1
+ module TestR
2
+ module Client
3
+
4
+ class Receiver < Thread
5
+ def initialize *popen_args
6
+ (@io = IO.popen(*popen_args)).sync = true
7
+ super() { loop { yield @io.gets } }
8
+ end
9
+
10
+ def quit
11
+ kill # stop the receive loop
12
+ Process.kill :SIGTERM, @io.pid
13
+ Process.wait @io.pid # reap zombie
14
+ @io.close # prevent subsequent I/O
15
+ end
16
+ end
17
+
18
+ class Transceiver < Receiver
19
+ def initialize *popen_args
20
+ @io_write_lock = Mutex.new
21
+ popen_args[1] = 'w+'
22
+ super
23
+ end
24
+
25
+ def send command
26
+ @io_write_lock.synchronize do
27
+ warn "#{caller[2]} SEND #{command.inspect}" if $DEBUG
28
+ @io.puts JSON.dump(command)
29
+ end
30
+ end
31
+
32
+ def quit
33
+ send [:quit]
34
+ super
35
+ end
36
+ end
37
+
38
+ end
39
+ end
@@ -0,0 +1,63 @@
1
+ require 'ostruct'
2
+
3
+ module TestR
4
+ _user_config_file = '.testr.rb'
5
+
6
+ Config = OpenStruct.new
7
+
8
+ Config.max_forked_workers = [
9
+ # http://stackoverflow.com/questions/891537#6420817
10
+ 'fgrep -c processor /proc/cpuinfo', # Linux
11
+ 'sysctl -n hw.ncpu', # BSD
12
+ 'hwprefs cpu_count', # Darwin 9
13
+ 'hwprefs thread_count', # Darwin 10
14
+ ].map {|cmd| `#{cmd} 2>/dev/null`.to_i }.push(1).max
15
+
16
+ Config.overhead_load_paths = ['lib', 'test', 'spec']
17
+
18
+ Config.overhead_file_globs = ['{test,spec}/{test,spec}_helper.rb']
19
+
20
+ Config.reabsorb_file_greps = [/^#{Regexp.quote(_user_config_file)}$/,
21
+ %r<(test|spec)/\1_helper\.rb>]
22
+
23
+ Config.all_test_file_globs = ['{test,spec}/**/*_{test,spec}.rb']
24
+
25
+ Config.test_file_globbers = {
26
+ # source files that correspond to test files
27
+ %r<^lib/.+\.rb$> => lambda do |path|
28
+ base = File.basename(path, '.rb')
29
+ "{test,spec}/**/#{base}_{test,spec}.rb"
30
+ end,
31
+
32
+ # the actual test files themselves
33
+ %r<^(test|spec)/.+_\1\.rb$> => lambda {|path| path }
34
+ }
35
+
36
+ Config.test_name_extractor = lambda do |line|
37
+ case line
38
+ when /^\s*def\s+test_(\w+)/ then $1
39
+ when /^\s*(test|context|should|describe|it)\b.+?(['"])(.*?)\2/ then $3
40
+ end
41
+ end
42
+
43
+ Config.before_fork_hooks = []
44
+
45
+ Config.after_fork_hooks = [
46
+ # tell testing framework to only run the named tests inside the test file
47
+ lambda do |worker_number, log_file, test_file, test_names|
48
+ unless test_names.empty?
49
+ regexp = Regexp.union(test_names.map {|name|
50
+ # sanitize string interpolations and invalid method name characters
51
+ name.gsub(/\#\{.*?\}/, ' ').strip.gsub(/\W+/, '.*')
52
+ })
53
+
54
+ case File.basename(test_file)
55
+ when /(\b|_)test(\b|_)/ then ARGV.push '--name', regexp.inspect
56
+ when /(\b|_)spec(\b|_)/ then ARGV.push '--example', regexp.source
57
+ end
58
+ end
59
+ end
60
+ ]
61
+
62
+ load _user_config_file if File.exist? _user_config_file
63
+ end