test-loop 5.0.3 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/README.md +67 -53
  2. data/bin/test-loop +33 -32
  3. metadata +4 -4
data/README.md CHANGED
@@ -14,6 +14,17 @@ test files in your test suite correspond to those changes, and uses diffing to
14
14
  find and run only those test blocks that have changed inside your test files.
15
15
 
16
16
 
17
+ > IMPORTANT note for Ruby on Rails users!
18
+ > ---------------------------------------
19
+ >
20
+ > Ensure that your `config/environments/test.rb` file disables class caching:
21
+ >
22
+ > config.cache_classes = false
23
+ >
24
+ > Otherwise, test-loop will appear to ignore class-level changes in your
25
+ > models, controllers, etc. and will thereby cause you great frustration!
26
+
27
+
17
28
  Features
18
29
  --------
19
30
 
@@ -76,76 +87,79 @@ Configuration
76
87
  -------------
77
88
 
78
89
  test-loop looks for a configuration file named `.test-loop` in the current
79
- working directory. This configuration file is a normal Ruby file whose last
80
- statement yields a hash that may optionally contain the following entries:
90
+ working directory. This configuration file is a normal Ruby script, in which
91
+ you can query and modify the `$test_loop_config` OpenStruct object as follows:
92
+
93
+ * `$test_loop_config.overhead_file_globs` is an array of file globbing
94
+ patterns that describe a set of Ruby scripts that are loaded into the main
95
+ Ruby process as overhead.
96
+
97
+ * `$test_loop_config.reabsorb_file_globs` is an array of file globbing
98
+ patterns that describe a set of files which cause the overhead to be
99
+ reabsorbed whenever they change.
100
+
101
+ * `$test_loop_config.test_file_matchers` is a hash that maps a file globbing
102
+ pattern describing a set of source files to a lambda function yielding a
103
+ file globbing pattern describing a set of test files that need to be run.
104
+ In other words, whenever the source files (the hash key; left-hand side of
105
+ the mapping) change, their associated test files (the hash value; right-hand
106
+ side of the mapping) are run.
81
107
 
82
- * `:overhead_file_globs` is an array of file globbing patterns that describe a
83
- set of Ruby scripts that are loaded into the main Ruby process as overhead.
108
+ For example, if test files had the same names as their source files but the
109
+ letters were in reverse order like this:
84
110
 
85
- * `:reabsorb_file_globs` is an array of file globbing patterns that describe a
86
- set of files which cause the overhead to be reabsorbed whenever they change.
111
+ * lib/hello.rb => test/olleh.rb
112
+ * app/world.rb => spec/ldrow.rb
87
113
 
88
- * `:test_file_matchers` is a hash that maps a file globbing pattern
89
- describing a set of source files to a lambda function yielding a file
90
- globbing pattern describing a set of test files that need to be run. In
91
- other words, whenever the source files (the hash key; left-hand side of the
92
- mapping) change, their associated test files (the hash value; right-hand
93
- side of the mapping) are run.
114
+ Then you would add the following to your configuration file:
94
115
 
95
- For example, if test files had the same names as their source files but the
96
- letters were in reverse order, then you would add the following hash entry
97
- to your configuration file:
98
-
99
- :test_file_matchers => {
100
- '{lib,app}/**/*.rb' => lambda do |path|
101
- extn = File.extname(path)
102
- name = File.basename(path, extn)
103
- "{test,spec}/**/#{name.reverse}#{extn}" # <== notice the reverse()
104
- end
105
- }
116
+ $test_loop_config.test_file_matchers['{lib,app}/**/*.rb'] = lambda do |path|
117
+ extn = File.extname(path)
118
+ name = File.basename(path, extn)
119
+ "{test,spec}/**/#{name.reverse}#{extn}" # <== notice the reverse()
120
+ end
106
121
 
107
- * `:test_name_parser` is a lambda function that is passed a line of source
108
- code to determine whether that line can be considered as a test definition,
109
- in which case it must return the name of the test being defined.
122
+ * `$test_loop_config.test_name_parser` is a lambda function that is passed a
123
+ line of source code to determine whether that line can be considered as a
124
+ test definition, in which case it must return the name of the test being
125
+ defined.
110
126
 
111
- * `:before_each_test` is a lambda function that is executed inside the worker
112
- process before loading the test file. It is passed the path to the test
113
- file and the names of tests (identified by `@test_name_parser`) inside the
114
- test file that have changed since the last time the test file was run.
127
+ * `$test_loop_config.before_each_test` is a lambda function that is executed
128
+ inside the worker process before loading the test file. It is passed (1)
129
+ the path to the test file and (2) the names of tests (which were identified
130
+ by `$test_loop_config.test_name_parser`) inside the test file that have
131
+ changed since the last time the test file was run.
115
132
 
116
133
  These test names should be passed down to your chosen testing library,
117
134
  instructing it to skip all other tests except those passed down to it. This
118
135
  accelerates your test-driven development cycle and improves productivity!
119
136
 
120
- * `@after_all_tests` is a lambda function that is executed inside the master
121
- process after all tests have finished running. It is passed four things:
122
- whether all tests had passed, the time when test execution began, a list of
123
- test files, and the exit statuses of the worker processes that ran them.
137
+ * `$test_loop_config.after_all_tests` is a lambda function that is executed
138
+ inside the master process after all tests have finished running. It is
139
+ passed (1) a list of passing test files along with the exit statuses of the
140
+ worker processes that ran them, (2) a list of failing test files along with
141
+ the exit statuses of the worker processes that ran them, (3) the time when
142
+ test execution began, and (4) how many seconds it took for the overall test
143
+ execution to complete.
124
144
 
125
145
  For example, to display a summary of the test execution results as an OSD
126
- notification via libnotify, add the following hash entry to your
127
- configuration file:
128
-
129
- :after_all_tests => lambda do |success, ran_at, files, statuses|
130
- icon = success ? 'apple-green' : 'apple-red'
131
- title = "#{success ? 'PASS' : 'FAIL'} at #{ran_at}"
132
- details = files.zip(statuses).map do |file, status|
133
- "#{status.success? ? '✔' : '✘'} #{file}"
134
- end
135
- system 'notify-send', '-i', icon, title, details.join("\n")
136
- end
146
+ notification, add the following to your configuration file:
137
147
 
138
- Also add the following at the top of the file if you use Ruby 1.9.x:
148
+ $test_loop_config.after_all_tests = lambda do |passes, fails, started_at, elapsed_time|
149
+ success = fails.empty?
139
150
 
140
- # encoding: utf-8
151
+ title = started_at.strftime("#{success ? 'PASS' : 'FAIL'} at %X on %x")
141
152
 
142
- That will prevent the following errors from occurring:
153
+ message = '%d ran, %d passed, %d failed in %0.1f seconds' %
154
+ [passes.length + fails.length, passes.length, fails.length, elapsed_time]
143
155
 
144
- invalid multibyte char (US-ASCII)
145
-
146
- syntax error, unexpected $end, expecting ':'
147
- "#{status.success? ? '' : ''} #{file}"
148
- ^
156
+ Thread.new do # launch in background
157
+ system 'notify-send', '-i', "dialog-#{success ? 'information' : 'error'}", title, message or
158
+ system 'growlnotify', '-a', 'Xcode', '-m', message, title or
159
+ system 'xmessage', '-timeout', '5', '-title', title, message or
160
+ puts title, message
161
+ end
162
+ end
149
163
 
150
164
 
151
165
  License
data/bin/test-loop CHANGED
@@ -24,24 +24,21 @@
24
24
 
25
25
  process_invocation_vector = [$0, *ARGV].map! {|s| s.dup }
26
26
 
27
+ require 'ostruct'
27
28
  require 'diff/lcs'
28
29
 
29
30
  begin
30
31
  notify = lambda {|message| puts "test-loop: #{message}" }
31
32
 
32
- # load user's configuration and supply default values
33
- notify.call 'Loading configuration...'
34
- config_file = File.join(Dir.pwd, '.test-loop')
35
- config_data = File.read(config_file) if File.exist? config_file
36
- config = eval(config_data.to_s, TOPLEVEL_BINDING, config_file) || {}
33
+ # supply default configuration
34
+ config = $test_loop_config = OpenStruct.new
37
35
 
38
- (config[:overhead_file_globs] ||= []).
39
- push('{test,spec}/*{test,spec}_helper.rb').uniq!
36
+ config.overhead_file_globs = ['{test,spec}/{test,spec}_helper.rb']
40
37
 
41
- (config[:reabsorb_file_globs] ||= []).concat(config[:overhead_file_globs]).
42
- push(config_file, 'config/*.{rb,yml}', 'Gemfile.lock').uniq!
38
+ config.reabsorb_file_globs = config.overhead_file_globs +
39
+ ['config/*.{rb,yml}', 'Gemfile.lock', '.test-loop']
43
40
 
44
- (config[:test_file_matchers] ||= {}).merge!(
41
+ config.test_file_matchers = {
45
42
  # source files that correspond to test files
46
43
  '{lib,app}/**/*.rb' => lambda do |path|
47
44
  extn = File.extname(path)
@@ -51,16 +48,16 @@ begin
51
48
 
52
49
  # the actual test files themselves
53
50
  '{test,spec}/**/*_{test,spec}.rb' => lambda {|path| path }
54
- )
51
+ }
55
52
 
56
- config[:test_name_parser] ||= lambda do |line|
53
+ config.test_name_parser = lambda do |line|
57
54
  case line
58
55
  when /^\s*def\s+test_(\w+)/ then $1
59
56
  when /^\s*(test|context|should|describe|it)\b.+?(['"])(.*?)\2/ then $3
60
57
  end
61
58
  end
62
59
 
63
- config[:before_each_test] ||= lambda do |test_file, test_names|
60
+ config.before_each_test = lambda do |test_file, test_names|
64
61
  unless test_names.empty?
65
62
  test_name_pattern = test_names.map do |name|
66
63
  # sanitize string interpolation and non-method-name characters
@@ -68,23 +65,27 @@ begin
68
65
  end.join('|')
69
66
 
70
67
  case File.basename(test_file)
71
-
72
68
  when /(\b|_)test(\b|_)/ # Test::Unit
73
69
  ARGV.push '--name', "/#{test_name_pattern}/"
74
-
75
70
  when /(\b|_)spec(\b|_)/ # RSpec
76
71
  ARGV.push '--example', test_name_pattern
77
72
  end
78
73
  end
79
74
  end
80
75
 
81
- config[:after_all_tests] ||= lambda {|success, ran_at, files, statuses|}
76
+ config.after_all_tests = lambda {|passes, fails, started_at, elapsed_time|}
77
+
78
+ # load user's configuration overrides
79
+ if File.exist? config_file = File.join(Dir.pwd, '.test-loop')
80
+ notify.call 'Loading configuration...'
81
+ load config_file
82
+ end
82
83
 
83
84
  # absorb test execution overhead into master process
84
85
  $LOAD_PATH.unshift 'lib', 'test', 'spec'
85
86
 
86
87
  notify.call 'Absorbing overhead...'
87
- Dir[*config[:overhead_file_globs]].each do |file|
88
+ Dir[*config.overhead_file_globs].each do |file|
88
89
  require File.basename(file, File.extname(file))
89
90
  end
90
91
 
@@ -97,7 +98,7 @@ begin
97
98
  notify.call 'Ready for testing!'
98
99
  loop do
99
100
  # figure out what test files need to be run
100
- test_files = config[:test_file_matchers].map do |source_glob, test_matcher|
101
+ test_files = config.test_file_matchers.map do |source_glob, test_matcher|
101
102
  Dir[source_glob].select {|file| File.mtime(file) > last_ran_at }.
102
103
  map {|path| Dir[test_matcher.call path] }
103
104
  end.flatten.uniq
@@ -120,14 +121,14 @@ begin
120
121
  # search backwards from the line that changed up to
121
122
  # the first line in the file for test definitions
122
123
  change.position.downto(0) do |i|
123
- if test_name = config[:test_name_parser].call(new_lines[i])
124
+ if test_name = config.test_name_parser.call(new_lines[i])
124
125
  throw :found, test_name
125
126
  end
126
127
  end; nil # prevent unsuccessful search from returning an integer
127
128
  end
128
129
  end.compact.uniq
129
130
 
130
- config[:before_each_test].call test_file, test_names
131
+ config.before_each_test.call test_file, test_names
131
132
 
132
133
  load test_file
133
134
 
@@ -139,20 +140,21 @@ begin
139
140
  end
140
141
 
141
142
  # wait for worker processes to finish and report results
142
- statuses = Process.waitall.map {|pid, status| status }
143
+ test_runs = Process.waitall.map {|pid, status| status }
144
+ elapsed_time = Time.now - last_ran_at
143
145
 
144
- success = true
145
- test_files.zip(statuses).each do |file, status|
146
- success &&= test_passed = status.success?
147
- notify.call "#{test_passed ? 'PASS' : 'FAIL'} #{file}"
148
- end
149
- notify.call "Ran in #{Time.now - last_ran_at} seconds"
146
+ passes, fails = test_files.zip(test_runs).each do |file, run|
147
+ notify.call "#{run.success? ? 'PASS' : 'FAIL'} #{file}"
148
+ end.partition {|file, run| run.success? }
149
+
150
+ notify.call '%d ran, %d passed, %d failed in %0.1f seconds' %
151
+ [test_runs.length, passes.length, fails.length, elapsed_time]
150
152
 
151
- config[:after_all_tests].call success, last_ran_at, test_files, statuses
153
+ config.after_all_tests.call passes, fails, last_ran_at, elapsed_time
152
154
  end
153
155
 
154
156
  # reabsorb test execution overhead as necessary
155
- if Dir[*config[:reabsorb_file_globs]].any? {|file| File.mtime(file) > started_at }
157
+ if Dir[*config.reabsorb_file_globs].any? {|file| File.mtime(file) > started_at }
156
158
  notify.call 'Restarting loop...'
157
159
  exec(*process_invocation_vector)
158
160
  end
@@ -161,9 +163,8 @@ begin
161
163
  end
162
164
 
163
165
  rescue Interrupt
164
- # user wants to quit the loop; terminate worker processes
165
- # NOTE: negative PID causes signal to propagate to all child processes
166
- Process.kill :SIGKILL, -$$
166
+ # user wants to quit the loop so terminate all worker processes
167
+ Process.kill :SIGKILL, -$$ # negative PID propagates signal to children
167
168
 
168
169
  rescue Exception => error
169
170
  STDERR.puts error.inspect, error.backtrace
metadata CHANGED
@@ -3,10 +3,10 @@ name: test-loop
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
- - 5
6
+ - 6
7
7
  - 0
8
- - 3
9
- version: 5.0.3
8
+ - 0
9
+ version: 6.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-25 00:00:00 -08:00
17
+ date: 2011-02-09 00:00:00 -08:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency