test-loop 4.0.1 → 5.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +23 -23
- data/bin/test-loop +22 -25
- metadata +4 -4
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
|
|
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,16 +53,14 @@ 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
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
|
|
@@ -75,31 +74,29 @@ begin
|
|
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
80
|
$LOAD_PATH.unshift 'lib', 'test', 'spec'
|
82
81
|
|
83
82
|
notify.call 'Absorbing overhead...'
|
84
|
-
Dir[
|
83
|
+
Dir[*config[:overhead_file_globs]].each do |file|
|
85
84
|
require File.basename(file, File.extname(file))
|
86
85
|
end
|
87
86
|
|
88
87
|
# continuously watch for and test changed code
|
89
|
-
|
88
|
+
test_file_cache = {} # path => readlines
|
90
89
|
started_at = last_ran_at = Time.now
|
91
|
-
trap(:QUIT) { started_at =
|
92
|
-
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 }
|
93
92
|
|
94
93
|
notify.call 'Ready for testing!'
|
95
94
|
loop do
|
96
95
|
# figure out what test files need to be run
|
97
|
-
test_files =
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
map {|path| Dir[test_file_glob_mapper.call(path)] }
|
102
|
-
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
|
103
100
|
|
104
101
|
# fork worker processes to run the test files in parallel
|
105
102
|
unless test_files.empty?
|
@@ -109,8 +106,8 @@ begin
|
|
109
106
|
test_files.each do |test_file|
|
110
107
|
# cache the contents of the test file for diffing below
|
111
108
|
new_lines = File.readlines(test_file)
|
112
|
-
old_lines =
|
113
|
-
|
109
|
+
old_lines = test_file_cache[test_file] || new_lines
|
110
|
+
test_file_cache[test_file] = new_lines
|
114
111
|
|
115
112
|
fork do
|
116
113
|
# determine which test blocks have changed inside the test file
|
@@ -119,14 +116,14 @@ begin
|
|
119
116
|
# search backwards from the line that changed up to
|
120
117
|
# the first line in the file for test definitions
|
121
118
|
diff[0][1].downto(0) do |i| # [[+/-, line number, line value]]
|
122
|
-
if test_name =
|
119
|
+
if test_name = config[:test_name_parser].call(new_lines[i])
|
123
120
|
throw :found, test_name
|
124
121
|
end
|
125
122
|
end; nil # prevent unsuccessful search from returning an integer
|
126
123
|
end
|
127
124
|
end.compact.uniq
|
128
125
|
|
129
|
-
|
126
|
+
config[:before_each_test].call test_file, test_names
|
130
127
|
|
131
128
|
load test_file
|
132
129
|
|
@@ -147,11 +144,11 @@ begin
|
|
147
144
|
end
|
148
145
|
notify.call "Ran in #{Time.now - last_ran_at} seconds"
|
149
146
|
|
150
|
-
|
147
|
+
config[:after_all_tests].call success, last_ran_at, test_files, statuses
|
151
148
|
end
|
152
149
|
|
153
150
|
# reabsorb test execution overhead as necessary
|
154
|
-
if Dir[
|
151
|
+
if Dir[*config[:reabsorb_file_globs]].any? {|file| File.mtime(file) > started_at }
|
155
152
|
notify.call 'Restarting loop...'
|
156
153
|
exec(*process_invocation_vector)
|
157
154
|
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
|
-
-
|
9
|
-
version:
|
8
|
+
- 0
|
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
|