test-loop 4.0.0 → 5.0.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 +24 -24
- data/bin/test-loop +30 -35
- metadata +3 -3
data/README.md
CHANGED
|
@@ -5,8 +5,8 @@ test-loop is a fast continuous testing tool for Ruby that continuously detects
|
|
|
5
5
|
and tests changes in your Ruby application in an efficient manner, whereby it:
|
|
6
6
|
|
|
7
7
|
1. Absorbs the test execution overhead into the main Ruby process.
|
|
8
|
-
2. Forks to
|
|
9
|
-
3.
|
|
8
|
+
2. Forks to run (eval) your test files directly, without overhead.
|
|
9
|
+
3. Avoids running unchanged test blocks inside changed test files.
|
|
10
10
|
|
|
11
11
|
It relies on file modification times to determine what parts of your Ruby
|
|
12
12
|
application have changed, applies a lambda mapping function to determine which
|
|
@@ -17,8 +17,8 @@ find and run only those test blocks that have changed inside your test files.
|
|
|
17
17
|
Features
|
|
18
18
|
--------
|
|
19
19
|
|
|
20
|
-
* Tests *changes* in your Ruby application:
|
|
21
|
-
|
|
20
|
+
* Tests *changes* in your Ruby application: avoids running (1) unchanged
|
|
21
|
+
test files and (2) unchanged test blocks inside changed test files.
|
|
22
22
|
|
|
23
23
|
* Reabsorbs test execution overhead if the test or spec helper file changes.
|
|
24
24
|
|
|
@@ -75,27 +75,27 @@ Configuration
|
|
|
75
75
|
-------------
|
|
76
76
|
|
|
77
77
|
test-loop looks for a configuration file named `.test-loop` in the current
|
|
78
|
-
working directory. This configuration file is a normal Ruby
|
|
79
|
-
|
|
78
|
+
working directory. This configuration file is a normal Ruby file whose last
|
|
79
|
+
statement yields a hash that may optionally contain the following entries:
|
|
80
80
|
|
|
81
|
-
*
|
|
81
|
+
* `:overhead_file_globs` is an array of file globbing patterns that describe a
|
|
82
82
|
set of Ruby scripts that are loaded into the main Ruby process as overhead.
|
|
83
83
|
|
|
84
|
-
*
|
|
84
|
+
* `:reabsorb_file_globs` is an array of file globbing patterns that describe a
|
|
85
85
|
set of files which cause the overhead to be reabsorbed whenever they change.
|
|
86
86
|
|
|
87
|
-
*
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
87
|
+
* `:test_file_matchers` is a hash that maps a file globbing pattern
|
|
88
|
+
describing a set of source files to a lambda function yielding a file
|
|
89
|
+
globbing pattern describing a set of test files that need to be run. In
|
|
90
|
+
other words, whenever the source files (the hash key; left-hand side of the
|
|
91
|
+
mapping) change, their associated test files (the hash value; right-hand
|
|
92
92
|
side of the mapping) are run.
|
|
93
93
|
|
|
94
94
|
For example, if test files had the same names as their source files but the
|
|
95
|
-
letters were in reverse order, then you would add the following
|
|
96
|
-
`.test-loop` file:
|
|
95
|
+
letters were in reverse order, then you would add the following hash entry
|
|
96
|
+
to your `.test-loop` file:
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
:test_file_matchers => {
|
|
99
99
|
'{lib,app}/**/*.rb' => lambda do |path|
|
|
100
100
|
extn = File.extname(path)
|
|
101
101
|
name = File.basename(path, extn)
|
|
@@ -103,11 +103,11 @@ define the following instance variables:
|
|
|
103
103
|
end
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
*
|
|
106
|
+
* `:test_name_parser` is a lambda function that is passed a line of source
|
|
107
107
|
code to determine whether that line can be considered as a test definition,
|
|
108
108
|
in which case it must return the name of the test being defined.
|
|
109
109
|
|
|
110
|
-
*
|
|
110
|
+
* `:before_each_test` is a lambda function that is executed inside the worker
|
|
111
111
|
process before loading the test file. It is passed the path to the test
|
|
112
112
|
file and the names of tests (identified by `@test_name_parser`) inside the
|
|
113
113
|
test file that have changed since the last time the test file was run.
|
|
@@ -119,13 +119,13 @@ define the following instance variables:
|
|
|
119
119
|
* `@after_all_tests` is a lambda function that is executed inside the master
|
|
120
120
|
process after all tests have finished running. It is passed four things:
|
|
121
121
|
whether all tests had passed, the time when test execution began, a list of
|
|
122
|
-
test files, and the exit statuses of the worker processes that
|
|
123
|
-
those test files.
|
|
122
|
+
test files, and the exit statuses of the worker processes that ran them.
|
|
124
123
|
|
|
125
124
|
For example, to display a summary of the test execution results as an OSD
|
|
126
|
-
notification via libnotify, add the following to your
|
|
125
|
+
notification via libnotify, add the following hash entry to your
|
|
126
|
+
`.test-loop` file:
|
|
127
127
|
|
|
128
|
-
|
|
128
|
+
:after_all_tests => lambda do |success, ran_at, files, statuses|
|
|
129
129
|
icon = success ? 'apple-green' : 'apple-red'
|
|
130
130
|
title = "#{success ? 'PASS' : 'FAIL'} at #{ran_at}"
|
|
131
131
|
details = files.zip(statuses).map do |file, status|
|
|
@@ -134,7 +134,7 @@ define the following instance variables:
|
|
|
134
134
|
system 'notify-send', '-i', icon, title, details.join("\n")
|
|
135
135
|
end
|
|
136
136
|
|
|
137
|
-
Also add the following at the
|
|
137
|
+
Also add the following at the top of the file if you use Ruby 1.9.x:
|
|
138
138
|
|
|
139
139
|
# encoding: utf-8
|
|
140
140
|
|
|
@@ -144,7 +144,7 @@ define the following instance variables:
|
|
|
144
144
|
|
|
145
145
|
syntax error, unexpected $end, expecting ':'
|
|
146
146
|
"#{status.success? ? '✔' : '✘'} #{file}"
|
|
147
|
-
|
|
147
|
+
^
|
|
148
148
|
|
|
149
149
|
|
|
150
150
|
License
|
data/bin/test-loop
CHANGED
|
@@ -32,15 +32,16 @@ begin
|
|
|
32
32
|
# load user's configuration and supply default values
|
|
33
33
|
notify.call 'Loading configuration...'
|
|
34
34
|
config_file = File.join(Dir.pwd, '.test-loop')
|
|
35
|
-
|
|
35
|
+
config_data = File.read(config_file) if File.exist? config_file
|
|
36
|
+
config = eval(config_data.to_s, TOPLEVEL_BINDING, config_file) || {}
|
|
36
37
|
|
|
37
|
-
(
|
|
38
|
+
(config[:overhead_file_globs] ||= []).
|
|
38
39
|
push('{test,spec}/*{test,spec}_helper.rb').uniq!
|
|
39
40
|
|
|
40
|
-
(
|
|
41
|
+
(config[:reabsorb_file_globs] ||= []).concat(config[:overhead_file_globs]).
|
|
41
42
|
push(config_file, 'config/*.{rb,yml}', 'Gemfile').uniq!
|
|
42
43
|
|
|
43
|
-
(
|
|
44
|
+
(config[:test_file_matchers] ||= {}).merge!(
|
|
44
45
|
# source files that correspond to test files
|
|
45
46
|
'{lib,app}/**/*.rb' => lambda do |path|
|
|
46
47
|
extn = File.extname(path)
|
|
@@ -52,55 +53,50 @@ begin
|
|
|
52
53
|
'{test,spec}/**/*_{test,spec}.rb' => lambda {|path| path }
|
|
53
54
|
)
|
|
54
55
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
@test_name_parser ||= lambda do |line|
|
|
56
|
+
config[:test_name_parser] ||= lambda do |line|
|
|
58
57
|
case line
|
|
59
58
|
when /^\s*def test_(\w+)/ then $1
|
|
60
|
-
when /^\s*(test|context|should|describe|it)\b (['"])(.*?)\2/ then $3
|
|
59
|
+
when /^\s*(test|context|should|describe|it)\b (['"])(.*?)\2/ then $3.strip
|
|
61
60
|
end
|
|
62
61
|
end
|
|
63
62
|
|
|
64
|
-
|
|
63
|
+
config[:before_each_test] ||= lambda do |test_file, test_names|
|
|
65
64
|
unless test_names.empty?
|
|
66
65
|
case File.basename(test_file)
|
|
67
66
|
|
|
68
|
-
when /(\b|_)test(\b|_)/ #
|
|
69
|
-
test_methods = test_names.map {|name| name.
|
|
70
|
-
ARGV.push '
|
|
67
|
+
when /(\b|_)test(\b|_)/ # Test::Unit
|
|
68
|
+
test_methods = test_names.map {|name| name.gsub(/\W+/, '_') }
|
|
69
|
+
ARGV.push '--name', "/#{test_methods.join('|')}/"
|
|
71
70
|
|
|
72
71
|
when /(\b|_)spec(\b|_)/ # RSpec
|
|
73
|
-
ARGV.push '
|
|
72
|
+
ARGV.push '--example', test_names.map {|n| Regexp.escape n }.join('|')
|
|
74
73
|
end
|
|
75
74
|
end
|
|
76
75
|
end
|
|
77
76
|
|
|
78
|
-
|
|
77
|
+
config[:after_all_tests] ||= lambda {|success, ran_at, files, statuses|}
|
|
79
78
|
|
|
80
79
|
# absorb test execution overhead into master process
|
|
81
|
-
$LOAD_PATH.unshift 'lib'
|
|
80
|
+
$LOAD_PATH.unshift 'lib', 'test', 'spec'
|
|
82
81
|
|
|
83
82
|
notify.call 'Absorbing overhead...'
|
|
84
|
-
Dir[
|
|
85
|
-
$LOAD_PATH.insert 1, File.dirname(file)
|
|
83
|
+
Dir[*config[:overhead_file_globs]].each do |file|
|
|
86
84
|
require File.basename(file, File.extname(file))
|
|
87
85
|
end
|
|
88
86
|
|
|
89
87
|
# continuously watch for and test changed code
|
|
90
|
-
|
|
88
|
+
test_file_cache = {} # path => readlines
|
|
91
89
|
started_at = last_ran_at = Time.now
|
|
92
|
-
trap(:QUIT) { started_at =
|
|
93
|
-
trap(:TSTP) { last_ran_at =
|
|
90
|
+
trap(:QUIT) { started_at = Time.at(0) }
|
|
91
|
+
trap(:TSTP) { last_ran_at = Time.at(0); test_file_cache.clear }
|
|
94
92
|
|
|
95
93
|
notify.call 'Ready for testing!'
|
|
96
94
|
loop do
|
|
97
95
|
# figure out what test files need to be run
|
|
98
|
-
test_files =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
map {|path| Dir[test_file_glob_mapper.call(path)] }
|
|
103
|
-
end.flatten.uniq
|
|
96
|
+
test_files = config[:test_file_matchers].map do |source_glob, test_matcher|
|
|
97
|
+
Dir[source_glob].select {|file| File.mtime(file) > last_ran_at }.
|
|
98
|
+
map {|path| Dir[test_matcher.call path] }
|
|
99
|
+
end.flatten.uniq
|
|
104
100
|
|
|
105
101
|
# fork worker processes to run the test files in parallel
|
|
106
102
|
unless test_files.empty?
|
|
@@ -108,12 +104,10 @@ begin
|
|
|
108
104
|
last_ran_at = Time.now
|
|
109
105
|
|
|
110
106
|
test_files.each do |test_file|
|
|
111
|
-
notify.call test_file
|
|
112
|
-
|
|
113
107
|
# cache the contents of the test file for diffing below
|
|
114
108
|
new_lines = File.readlines(test_file)
|
|
115
|
-
old_lines =
|
|
116
|
-
|
|
109
|
+
old_lines = test_file_cache[test_file] || new_lines
|
|
110
|
+
test_file_cache[test_file] = new_lines
|
|
117
111
|
|
|
118
112
|
fork do
|
|
119
113
|
# determine which test blocks have changed inside the test file
|
|
@@ -122,14 +116,14 @@ begin
|
|
|
122
116
|
# search backwards from the line that changed up to
|
|
123
117
|
# the first line in the file for test definitions
|
|
124
118
|
diff[0][1].downto(0) do |i| # [[+/-, line number, line value]]
|
|
125
|
-
if test_name =
|
|
119
|
+
if test_name = config[:test_name_parser].call(new_lines[i])
|
|
126
120
|
throw :found, test_name
|
|
127
121
|
end
|
|
128
122
|
end; nil # prevent unsuccessful search from returning an integer
|
|
129
123
|
end
|
|
130
124
|
end.compact.uniq
|
|
131
125
|
|
|
132
|
-
|
|
126
|
+
config[:before_each_test].call test_file, test_names
|
|
133
127
|
|
|
134
128
|
load test_file
|
|
135
129
|
|
|
@@ -148,12 +142,13 @@ begin
|
|
|
148
142
|
success &&= test_passed = status.success?
|
|
149
143
|
notify.call "#{test_passed ? 'PASS' : 'FAIL'} #{file}"
|
|
150
144
|
end
|
|
145
|
+
notify.call "Ran in #{Time.now - last_ran_at} seconds"
|
|
151
146
|
|
|
152
|
-
|
|
147
|
+
config[:after_all_tests].call success, last_ran_at, test_files, statuses
|
|
153
148
|
end
|
|
154
149
|
|
|
155
150
|
# reabsorb test execution overhead as necessary
|
|
156
|
-
if Dir[
|
|
151
|
+
if Dir[*config[:reabsorb_file_globs]].any? {|file| File.mtime(file) > started_at }
|
|
157
152
|
notify.call 'Restarting loop...'
|
|
158
153
|
exec(*process_invocation_vector)
|
|
159
154
|
end
|
|
@@ -165,6 +160,6 @@ rescue Interrupt
|
|
|
165
160
|
# user wants to quit the loop
|
|
166
161
|
|
|
167
162
|
rescue Exception => error
|
|
168
|
-
puts error.inspect, error.backtrace
|
|
163
|
+
STDERR.puts error.inspect, error.backtrace
|
|
169
164
|
sleep 1 and exec(*process_invocation_vector)
|
|
170
165
|
end
|
metadata
CHANGED
|
@@ -3,10 +3,10 @@ name: test-loop
|
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
|
5
5
|
segments:
|
|
6
|
-
-
|
|
6
|
+
- 5
|
|
7
7
|
- 0
|
|
8
8
|
- 0
|
|
9
|
-
version:
|
|
9
|
+
version: 5.0.0
|
|
10
10
|
platform: ruby
|
|
11
11
|
authors:
|
|
12
12
|
- Suraj N. Kurapati
|
|
@@ -14,7 +14,7 @@ autorequire:
|
|
|
14
14
|
bindir: bin
|
|
15
15
|
cert_chain: []
|
|
16
16
|
|
|
17
|
-
date: 2011-01-
|
|
17
|
+
date: 2011-01-17 00:00:00 -08:00
|
|
18
18
|
default_executable:
|
|
19
19
|
dependencies:
|
|
20
20
|
- !ruby/object:Gem::Dependency
|