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.
Files changed (3) hide show
  1. data/README.md +24 -24
  2. data/bin/test-loop +30 -35
  3. 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 evaluate your test files directly and without overhead.
9
- 3. Tries to run only the test blocks that changed in your test files.
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: ignores unmodified test files
21
- as well as unmodified test blocks inside modified test files.
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 script which can
79
- define the following instance variables:
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
- * `@overhead_file_globs` is an array of file globbing patterns that describe a
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
- * `@reabsorb_file_globs` is an array of file globbing patterns that describe a
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
- * `@source_file_to_test_file_mapping` is a hash that maps a file globbing
88
- pattern describing a set of source files to a lambda function yielding a
89
- file globbing pattern describing a set of test files that need to be run.
90
- In other words, whenever the source files (the hash key; left-hand side of
91
- the mapping) change, their associated test files (the hash value; right-hand
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 to your
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
- @source_file_to_test_file_mapping = {
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
- * `@test_name_parser` is a lambda function that is passed a line of source
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
- * `@before_each_test` is a lambda function that is executed inside the worker
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 evaluated
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 `.test-loop` file:
125
+ notification via libnotify, add the following hash entry to your
126
+ `.test-loop` file:
127
127
 
128
- @after_all_tests = lambda do |success, ran_at, files, statuses|
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 very top if you use Ruby 1.9.x:
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
- load config_file if File.exist? config_file
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
- (@overhead_file_globs ||= []).
38
+ (config[:overhead_file_globs] ||= []).
38
39
  push('{test,spec}/*{test,spec}_helper.rb').uniq!
39
40
 
40
- (@reabsorb_file_globs ||= []).concat(@overhead_file_globs).
41
+ (config[:reabsorb_file_globs] ||= []).concat(config[:overhead_file_globs]).
41
42
  push(config_file, 'config/*.{rb,yml}', 'Gemfile').uniq!
42
43
 
43
- (@source_file_to_test_file_mapping ||= {}).merge!(
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
- @test_file_cache = {} # path => readlines
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
- @before_each_test ||= lambda do |test_file, test_names|
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|_)/ # Minitest
69
- test_methods = test_names.map {|name| name.strip.gsub(/\W+/, '_') }
70
- ARGV.push '-n', "/#{test_methods.join('|')}/"
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 '-e', test_names.map {|name| Regexp.quote(name) }.join('|')
72
+ ARGV.push '--example', test_names.map {|n| Regexp.escape n }.join('|')
74
73
  end
75
74
  end
76
75
  end
77
76
 
78
- @after_all_tests ||= lambda {|success, ran_at, files, statuses|}
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' # for non-Rails applications
80
+ $LOAD_PATH.unshift 'lib', 'test', 'spec'
82
81
 
83
82
  notify.call 'Absorbing overhead...'
84
- Dir[*@overhead_file_globs].each do |file|
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
- epoch_time = Time.at(0)
88
+ test_file_cache = {} # path => readlines
91
89
  started_at = last_ran_at = Time.now
92
- trap(:QUIT) { started_at = epoch_time }
93
- trap(:TSTP) { last_ran_at = epoch_time; @test_file_cache.clear }
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 = @source_file_to_test_file_mapping.
99
- map do |source_file_glob, test_file_glob_mapper|
100
- Dir[source_file_glob].
101
- select {|file| File.mtime(file) > last_ran_at }.
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 = @test_file_cache[test_file] || new_lines
116
- @test_file_cache[test_file] = new_lines
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 = @test_name_parser.call(new_lines[i])
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
- @before_each_test.call test_file, test_names
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
- @after_all_tests.call(success, last_ran_at, test_files, statuses)
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[*@reabsorb_file_globs].any? {|file| File.mtime(file) > started_at }
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
- - 4
6
+ - 5
7
7
  - 0
8
8
  - 0
9
- version: 4.0.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-13 00:00:00 -08:00
17
+ date: 2011-01-17 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency