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.
Files changed (3) hide show
  1. data/README.md +23 -23
  2. data/bin/test-loop +22 -25
  3. 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 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
 
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,16 +53,14 @@ 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
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
 
@@ -75,31 +74,29 @@ begin
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
80
  $LOAD_PATH.unshift 'lib', 'test', 'spec'
82
81
 
83
82
  notify.call 'Absorbing overhead...'
84
- Dir[*@overhead_file_globs].each do |file|
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
- epoch_time = Time.at(0)
88
+ test_file_cache = {} # path => readlines
90
89
  started_at = last_ran_at = Time.now
91
- trap(:QUIT) { started_at = epoch_time }
92
- 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 }
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 = @source_file_to_test_file_mapping.
98
- map do |source_file_glob, test_file_glob_mapper|
99
- Dir[source_file_glob].
100
- select {|file| File.mtime(file) > last_ran_at }.
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 = @test_file_cache[test_file] || new_lines
113
- @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
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 = @test_name_parser.call(new_lines[i])
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
- @before_each_test.call test_file, test_names
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
- @after_all_tests.call(success, last_ran_at, test_files, statuses)
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[*@reabsorb_file_globs].any? {|file| File.mtime(file) > started_at }
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
- - 4
6
+ - 5
7
7
  - 0
8
- - 1
9
- version: 4.0.1
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-14 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