nestor 0.1.1 → 0.2.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.
Binary file
data/lib/nestor/cli.rb CHANGED
@@ -3,28 +3,21 @@ require "thor"
3
3
 
4
4
  module Nestor
5
5
  class Cli < Thor # :nodoc:
6
+ default_task :start
7
+
6
8
  desc("start", <<-EODESC.gsub(/^\s{6}/, ""))
7
9
  Starts a continuous test server.
8
- EODESC
9
- method_options :strategy => "test/unit", :watcher => "rails", :script => nil, :debug => false, :include => []
10
- def start
11
- puts "Using #{options[:strategy].inspect} strategy"
12
- begin
13
- # Try the internal version
14
- require "nestor/strategies/#{options[:strategy]}"
15
- rescue LoadError
16
- # Else fallback to something I'm not aware of right now
17
- require options[:strategy]
18
- end
19
10
 
20
- puts "Using #{options[:watcher].inspect} watcher"
21
- begin
22
- require "nestor/watchers/#{options[:watcher]}"
23
- rescue LoadError
24
- # Fallback to something external again
25
- require options[:watcher]
26
- end
11
+ Specify the framework and test library using --framework and --testlib.
12
+ Valid options are:
13
+ --framework: rails
14
+ --testlib: test/unit
27
15
 
16
+ Use --quick to boot without running the full test suite on startup.
17
+ --debug writes extra Watchr debug messages to STDOUT.
18
+ EODESC
19
+ method_options :framework => "rails", :testlib => "test/unit", :script => nil, :debug => false, :quick => false, :require => []
20
+ def start
28
21
  Watchr.options.debug = options[:debug]
29
22
 
30
23
  if options[:script] then
@@ -32,21 +25,64 @@ module Nestor
32
25
  else
33
26
  puts "Launching..."
34
27
  end
35
- Nestor::Watchers::Rails.run(:script => options[:script] ? Pathname.new(options[:script]) : nil)
28
+
29
+ puts "Using #{options[:framework].inspect} framework with #{options[:testlib].inspect} as the testing library"
30
+ mapper = mapper_instance(options[:framework], options[:testlib])
31
+ machine = Nestor::Machine.new(mapper, :quick => options[:quick])
32
+
33
+ script_path = options[:script] ? Pathname.new(options[:script]) : nil
34
+ script = Nestor::Script.new(script_path || mapper.class.default_script_path)
35
+
36
+ options[:require].each do |path|
37
+ puts "Loading #{path.inspect} plugin"
38
+ require path
39
+ end
40
+
41
+ script.nestor_machine = machine
42
+ Watchr::Controller.new(script, Watchr.handler.new).run
36
43
  end
37
44
 
38
45
  desc("customize PATH", <<-EODESC.gsub(/^\s{6}/, ""))
39
46
  Copies the named script file to PATH to allow customizing.
47
+ Will not overwrite existing files, unless --force is specified.
40
48
  EODESC
41
- method_options :strategy => "test/unit", :watcher => "rails"
49
+ method_options :framework => "rails", :testlib => "test/unit", :force => false
42
50
  def customize(path)
43
- puts "Using #{options[:watcher].inspect} watcher"
44
- require "nestor/watchers/#{options[:watcher]}"
51
+ raise "Destination #{path.inspect} already exists: will not overwrite" if !options[:force] && File.file?(path)
45
52
 
46
- raise "Destination #{path.inspect} already exists: will not overwrite" if File.file?(path)
47
- FileUtils.cp(Nestor::Watchers::Rails.path_to_script, path)
53
+ puts "Using #{options[:framework].inspect} framework with #{options[:testlib].inspect} as the testing library"
54
+ klass = mapper_class(options[:framework], options[:testlib])
55
+ FileUtils.cp(klass.default_script_path, path)
48
56
 
49
- puts "Wrote #{options[:watcher]} script to #{path.inspect}"
57
+ puts "Wrote #{klass.name} script to #{path.inspect}"
58
+ end
59
+
60
+ private
61
+
62
+ def require_path(*paths)
63
+ require "nestor/mappers/#{paths.join("/")}"
64
+ rescue LoadError
65
+ require paths.join("/")
66
+ end
67
+
68
+ def mapper_class(framework, testlib)
69
+ require_path(options[:framework], options[:testlib])
70
+ [framework.split("/"), testlib.split("/")].flatten.inject(Nestor::Mappers) do |root, component|
71
+ root.const_get(camelize(component))
72
+ end
73
+ end
74
+
75
+ def mapper_instance(framework, testlib)
76
+ mapper_class(framework, testlib).new
77
+ end
78
+
79
+ # Copied from ActiveSupport 2.3.4
80
+ def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
81
+ if first_letter_in_uppercase
82
+ lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
83
+ else
84
+ lower_case_and_underscored_word.first.downcase + camelize(lower_case_and_underscored_word)[1..-1]
85
+ end
50
86
  end
51
87
  end
52
88
  end
@@ -5,7 +5,7 @@ module Nestor
5
5
  #
6
6
  # == Usage
7
7
  #
8
- # In the Watchr script, use +@strategy+ to access an instance of this class.
8
+ # In the Watchr script, use +@mapper+ to access an instance of this class.
9
9
  #
10
10
  # The available events you may call are:
11
11
  #
@@ -13,12 +13,12 @@ module Nestor
13
13
  # call +ready+ to indicate you are ready to process events. The default
14
14
  # rails template calls #ready when +test/test_helper.rb+ is loaded.
15
15
  #
16
- # <tt>changed!</tt>:: Tells the Machine a file changed. The Watchr script and Strategy are
16
+ # <tt>changed!</tt>:: Tells the Machine a file changed. The Watchr script and mapper are
17
17
  # responsible for assigning meaning to the file. The default Watchr
18
18
  # script knows how to map model, controller and view files to given
19
19
  # tests, and the script thus only tells the Machine about test files.
20
20
  # Nothing prevents another implementation from providing the actual
21
- # implementation files and letting the Strategy decide later what to
21
+ # implementation files and letting the mapper decide later what to
22
22
  # do about those.
23
23
  #
24
24
  # <tt>run_successful!</tt>:: Tells the Machine that the last build was successful. This
@@ -29,14 +29,14 @@ module Nestor
29
29
  # Again, this doesn't mean the whole build failed: only the last couple
30
30
  # of files had something that caused a failure.
31
31
  #
32
- # <tt>run!</tt>:: Tells the machine to tell the +#strategy+ to run the tests, given the current
32
+ # <tt>run!</tt>:: Tells the machine to tell the +#mapper+ to run the tests, given the current
33
33
  # state of affairs. This might be running all tests, or a subset if the Machine
34
34
  # is currently focusing on some items. A separate event is required by the
35
35
  # Machine to allow coalescing multiple change events together.
36
36
  #
37
37
  class Machine
38
38
  # The Machine actually delegates running the tests to another object, and this is it's reference.
39
- attr_reader :strategy # :nodoc:
39
+ attr_reader :mapper # :nodoc:
40
40
 
41
41
  # The list of files we are focusing on, as received by #changed!
42
42
  attr_reader :focused_files # :nodoc:
@@ -47,19 +47,24 @@ module Nestor
47
47
  # The list of failing tests or examples being focused on right now
48
48
  attr_reader :focuses # :nodoc:
49
49
 
50
- # +strategy+ is required, and must implement a couple of methods. See {Nestor::Strategies} for the required calls.
51
- def initialize(strategy)
50
+ # The options as passed to #new.
51
+ attr_reader :options # :nodoc:
52
+
53
+ # +mapper+ is required, and must implement a couple of methods. See {Nestor::Mappers} for the required calls.
54
+ def initialize(mapper, options={})
55
+ @options = options
52
56
  super() # Have to specify no-args, or else it'll raise an ArgumentError
53
57
 
54
- @strategy = strategy
58
+ @mapper = mapper
55
59
  @focused_files, @focuses = [], []
56
60
 
57
61
  log_state_change
58
62
  end
59
63
 
60
- state_machine :initial => :booting do
64
+ state_machine :initial => lambda {|machine| machine.options[:quick] ? :green : :booting} do
61
65
  event :ready do
62
- transition any => :running_all
66
+ transition :booting => :running_all
67
+ transition :green => same # Quick boot: skips initial test run
63
68
  end
64
69
 
65
70
  event :failed do
@@ -73,6 +78,7 @@ module Nestor
73
78
  end
74
79
 
75
80
  event :file_changed do
81
+ transition [:running_all, :running_multi, :running_focused] => same
76
82
  transition [:run_focused, :run_focused_pending] => :run_focused_pending, :if => :changed_file_in_focused_files?
77
83
  transition [:run_focused_pending, :run_multi_pending, :run_focused] => :run_multi_pending
78
84
  transition :green => :run_multi_pending
@@ -84,6 +90,10 @@ module Nestor
84
90
  transition :run_multi_pending => :running_multi
85
91
  end
86
92
 
93
+ event :unfocus do
94
+ transition any => :running_all
95
+ end
96
+
87
97
  after_transition any => any, :do => :log_state_change
88
98
  after_transition :to => :running_all, :do => :run_all_tests
89
99
  after_transition :to => :running_focused, :do => :run_focused_tests
@@ -94,23 +104,31 @@ module Nestor
94
104
  after_transition :on => :file_changed, :do => :add_changed_file_to_focused_files
95
105
  end
96
106
 
97
- # Indicates the run was succesful: a green build. This does not indicate that the
98
- # whole build was successful: only that the files that ran last were successful.
99
- def run_successful!(files, tests)
100
- successful!
101
- end
102
-
103
- # Indicates there were one or more failures. +files+ lists the actual files
104
- # that failed, while +tests+ indicates the test names or examples that failed.
105
- def run_failed!(files, tests)
106
- @focused_files, @focuses = files, tests
107
- failed!
107
+ # Delegate to +@mapper+.
108
+ def log(*args)
109
+ @mapper.log(*args)
108
110
  end
109
111
 
110
112
  # Notifies the Machine that a file changed. This might trigger a state change and schedule a build.
111
113
  def changed!(file)
114
+ mapped_files = mapper.map(file)
115
+ case mapped_files
116
+ when [] # Run all tests
117
+ unfocus!
118
+ when nil # Reset
119
+ reset!
120
+ else
121
+ mapped_files.each do |mapped_file|
122
+ mapper.log "#{file} => #{mapped_file}"
123
+ self.changed_file = mapped_file
124
+ file_changed!
125
+ end
126
+ end
127
+ end
128
+
129
+ def changed_file=(file)
130
+ raise ArgumentError, "Only accepts a single file at a time, got #{file.inspect}" unless String === file
112
131
  @changed_file = file
113
- file_changed!
114
132
  end
115
133
 
116
134
  private
@@ -118,16 +136,25 @@ module Nestor
118
136
  def run_all_tests
119
137
  reset_focused_files
120
138
  reset_focuses
121
- @strategy.run_all
139
+ process!(@mapper.run_all)
122
140
  end
123
141
 
124
142
  def run_multi_tests
125
143
  reset_focuses
126
- @strategy.run(focused_files)
144
+ process!(@mapper.run(focused_files))
127
145
  end
128
146
 
129
147
  def run_focused_tests
130
- @strategy.run(focused_files, focuses)
148
+ process!(@mapper.run(focused_files, focuses))
149
+ end
150
+
151
+ def process!(info)
152
+ @mapper.log(info.inspect)
153
+ return successful! if info[:passed]
154
+
155
+ files, tests = info[:failures].values.uniq, info[:failures].keys
156
+ @focused_files, @focuses = files, tests
157
+ failed!
131
158
  end
132
159
 
133
160
  def reset_focused_files
@@ -135,11 +162,11 @@ module Nestor
135
162
  end
136
163
 
137
164
  def add_changed_file_to_focused_files
138
- @focused_files << @changed_file unless @focused_files.include?(@changed_file)
165
+ @focused_files << changed_file unless @focused_files.include?(changed_file)
139
166
  end
140
167
 
141
168
  def changed_file_in_focused_files?
142
- @strategy.log("changed_file #{changed_file}, in focused_files? #{focused_files.inspect}")
169
+ @mapper.log("changed_file #{changed_file}, in focused_files? #{focused_files.inspect}")
143
170
  focused_files.include?(changed_file)
144
171
  end
145
172
 
@@ -148,15 +175,15 @@ module Nestor
148
175
  end
149
176
 
150
177
  def log_state_change
151
- @strategy.log("Machine entering state: #{state.inspect}")
178
+ @mapper.log("Machine entering state: #{state.inspect}")
152
179
  end
153
180
 
154
181
  def log_focus
155
- @strategy.log("Focusing on #{focuses.inspect}")
182
+ @mapper.log("Focusing on #{focuses.inspect}")
156
183
  end
157
184
 
158
185
  def log_pending_run
159
- @strategy.log("Run pending... Waiting for go ahead")
186
+ @mapper.log("Run pending... Waiting for go ahead")
160
187
  end
161
188
  end
162
189
  end
@@ -0,0 +1,58 @@
1
+ def log(message) #:nodoc:
2
+ @machine.log(message)
3
+ end
4
+
5
+ RAILS_ENV = "test" unless defined?(RAILS_ENV)
6
+ log "Entering #{RAILS_ENV.inspect} environment"
7
+
8
+ log "Creating tmp/ if it doesn't exist"
9
+ Dir.mkdir("tmp") unless File.directory?("tmp")
10
+
11
+ log "Preloading test/test_helper.rb"
12
+ start_load_at = Time.now
13
+ $LOAD_PATH.unshift "test" unless $LOAD_PATH.include?("test")
14
+ require "test_helper"
15
+
16
+ end_load_at = Time.now
17
+ log "Waiting for changes (saving #{end_load_at - start_load_at} seconds per run)..."
18
+
19
+ def sendoff(timeout=0.8, path="tmp/nestor-sendoff") #:nodoc:
20
+ Thread.start(timeout, path) do |timeout, path|
21
+ log "Sendoff pending in #{timeout}s..."
22
+ sleep timeout
23
+ File.open(path, "w") {|io| io.write(rand.to_s)}
24
+ log "Sendoff fired on #{path}"
25
+ end
26
+ end
27
+
28
+ def changed!(filename) #:nodoc:
29
+ return if File.directory?(filename)
30
+ @machine.changed! filename
31
+ sendoff
32
+ end
33
+
34
+ watch 'config/(?:.+)\.(?:rb|ya?ml)' do |md|
35
+ changed! md[0]
36
+ end
37
+
38
+ watch '(?:app|test)/.+\.rb' do |md|
39
+ changed! md[0]
40
+ end
41
+
42
+ watch 'app/views/.+' do |md|
43
+ changed! md[0]
44
+ end
45
+
46
+ watch 'db/schema.rb' do |md|
47
+ log "Detected changed schema: preparing test DB"
48
+ system("rake db:test:prepare")
49
+ changed! md[0]
50
+ end
51
+
52
+ # This is only to trigger the tests after a slight delay, but from the main thread.
53
+ watch 'tmp/nestor-sendoff' do |_|
54
+ log "Sendoff"
55
+ @machine.run!
56
+ end
57
+
58
+ @machine.ready!
@@ -0,0 +1,259 @@
1
+ require "yaml"
2
+ require "pathname"
3
+ require "test/unit/ui/console/testrunner"
4
+
5
+ # Just declare the module to prevent deeply nested code below
6
+ module Nestor
7
+ module Mappers
8
+ module Rails
9
+ end
10
+ end
11
+ end
12
+
13
+ module Nestor::Mappers::Rails
14
+ module Test
15
+ class Unit
16
+ # Returns the path to the script this {Mapper} uses.
17
+ def self.default_script_path
18
+ Pathname.new(File.dirname(__FILE__) + "/rails_test_unit.rb")
19
+ end
20
+
21
+ # Utility method to extract data from a Test::Unit failure.
22
+ #
23
+ # @param failure [Test::Unit::Failure, Test::Unit::Error] The Test::Unit failure or error from which to extract information.
24
+ # @param test_files [Array<String>] The list of files that might have generated this failure. This is used to detect the file that caused the failure.
25
+ #
26
+ # @return [String, String] Returns the filename and test name as a 2 element Array.
27
+ def self.parse_failure(failure, test_files)
28
+ filename = if failure.respond_to?(:location) then
29
+ failure.location.map do |loc|
30
+ filename = loc.split(":", 2).first
31
+ test_files.detect {|tf| filename.include?(tf)}
32
+ end.compact.first
33
+ elsif failure.respond_to?(:exception) then
34
+ failure.exception.backtrace.map do |loc|
35
+ filename = loc.split(":", 2).first
36
+ loc = loc[1..-1] if loc[0,1] == "/"
37
+ test_files.detect {|tf| filename.include?(tf)}
38
+ end.compact.first
39
+ else
40
+ raise "Unknown object type received as failure: #{failure.inspect} doesn't have #exception or #location methods."
41
+ end
42
+
43
+ test_name = failure.test_name.split("(", 2).first.strip.sub(/\.$/, "")
44
+
45
+ [filename, test_name]
46
+ end
47
+
48
+ # Logs a message to STDOUT. This implementation forks, so the #log method also
49
+ # provides the PID of the logger.
50
+ def log(message)
51
+ STDOUT.printf "[%d] %s - %s\n", Process.pid, Time.now.strftime("%H:%M:%S"), message
52
+ STDOUT.flush
53
+ end
54
+
55
+ # Runs absolutely all tests as found by walking test/.
56
+ def run_all
57
+ receive_results do
58
+ log "Run all tests"
59
+ test_files = load_test_files(["test"])
60
+
61
+ ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
62
+ test_runner = ::Nestor::Mappers::Rails::Test::TestRunner.new(nil)
63
+ result = ::Test::Unit::AutoRunner.run(false, nil, []) do |autorunner|
64
+ autorunner.runner = lambda { test_runner }
65
+ end
66
+
67
+ # Returns a Hash which the parent process will retrieve
68
+ report(test_runner, test_files)
69
+ end
70
+ end
71
+
72
+ # Runs only the named files, and optionally focuses on only a couple of tests
73
+ # within the loaded test cases.
74
+ def run(test_files, focuses=[])
75
+ receive_results do
76
+ log "Running #{focuses.length} focused tests"
77
+ load_test_files(test_files)
78
+
79
+ ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
80
+ test_runner = ::Nestor::Mappers::Rails::Test::TestRunner.new(nil)
81
+ result = ::Test::Unit::AutoRunner.run(false, nil, []) do |autorunner|
82
+ autorunner.runner = lambda { test_runner }
83
+ autorunner.filters << proc{|t| focuses.include?(t.method_name)} unless focuses.empty?
84
+ end
85
+
86
+ # Returns a Hash the parent process will retrieve
87
+ report(test_runner, test_files)
88
+ end
89
+ end
90
+
91
+ # Given a path, returns an Array of strings for the tests that should be run.
92
+ #
93
+ # This implementation maps +app/models+ to +test/unit+, +app/controllers+ and +app/views+ to +test/functional+.
94
+ # It is not the responsibility of #map to determine if the files actually exist on disk. The {Nestor::Machine}
95
+ # will verify the files exist before attempting to run them.
96
+ #
97
+ #
98
+ # @example
99
+ #
100
+ # mapper = Nestor::Mappers::Rails::Test::Unit.new
101
+ # mapper.map("app/models/user.rb")
102
+ # #=> ["test/unit/user_test.rb"]
103
+ # mapper.map("app/helpers/users_helper.rb")
104
+ # #=> ["test/unit/helpers/users_helper.rb", "test/functional/users_controller_test.rb"]
105
+ #
106
+ # # Mapper is saying to re-run all functional tests
107
+ # mapper.map("app/controller/application_controller.rb")
108
+ # #=> ["test/functional/"]
109
+ #
110
+ # # Mapper is saying to run all tests
111
+ # mapper.map("config/environment.rb")
112
+ # #=> []
113
+ #
114
+ # @param path [String] A relative path to a file in the project.
115
+ # @return [Array<String>, nil] One or more paths the {Nestor::Machine} should run in response to the change.
116
+ # It is entirely possible and appropriate that this method return an empty array, which implies to run
117
+ # all tests. If a path points to a directory, this implies running all tests under that directory.
118
+ # Returning +nil+ implies no tests need to run (such as when editing a README).
119
+ def map(path)
120
+ case path
121
+ when %r{^app/.+/(.+_observer)\.rb$} # Has to be first, or app/models might kick in first
122
+ orig, plain = $1, $1.sub("_observer", "")
123
+ ["test/unit/#{orig}_test.rb", "test/unit/#{plain}_test.rb"]
124
+
125
+ when "app/controllers/application_controller.rb" # Again, special cases first
126
+ ["test/functional/"]
127
+
128
+ when "app/helpers/application_helper.rb" # Again, special cases first
129
+ ["test/unit/helpers/", "test/functional/"]
130
+
131
+ when %r{^app/models/(.+)\.rb$}, %r{^lib/(.+)\.rb$}
132
+ ["test/unit/#{$1}_test.rb"]
133
+
134
+ when %r{^app/controllers/(.+)\.rb$}
135
+ ["test/functional/#{$1}_test.rb"]
136
+
137
+ when %r{^app/views/(.+)/(.+)\.\w+$}
138
+ ["test/functional/#{$1}_controller_test.rb"]
139
+
140
+ when %r{^app/helpers/(.+)_helper\.rb$}
141
+ ["test/unit/helpers/#{$1}_helper_test.rb", "test/functional/#{$1}_controller_test.rb"]
142
+
143
+ when %r{^(?:test/test_helper.rb|config/.+\.(?:rb|ya?ml|xml)|db/schema\.rb)$}
144
+ # Rerun all tests because something fundamental changed
145
+ []
146
+
147
+ when %r{^test/.+_test.rb}
148
+ # Rerun the sole test when it's a test
149
+ Array(path)
150
+
151
+ else
152
+ # I don't know how to map this, so it's probably a README or something
153
+ nil
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ def setup_lifeline
160
+ ppid = Process.ppid
161
+ log "Setting up lifeline on #{Process.pid} for #{Process.ppid}"
162
+
163
+ Thread.start do
164
+ sleep 0.5
165
+ next if ppid == Process.ppid
166
+
167
+ # Parent must have died because we don't have the same parent PID
168
+ # Die ourselves
169
+ log "Dying because parent changed"
170
+ exit!
171
+ end
172
+ end
173
+
174
+ def receive_results
175
+ rd, wr = IO.pipe
176
+ fork do
177
+ log "Setting up lifeline"
178
+ setup_lifeline
179
+
180
+ log "Closing read-end of the pipe"
181
+ rd.close
182
+
183
+ log "Doing whatever..."
184
+ info = yield
185
+
186
+ log "Returning YAML info to parent process"
187
+ wr.write info.to_yaml
188
+ end
189
+
190
+ log "Closing write-end of the pipe"
191
+ wr.close
192
+
193
+ log "Waiting for child process"
194
+ Process.wait
195
+
196
+ info = YAML.load(rd.read)
197
+ end
198
+
199
+ def load_test_files(test_files)
200
+ test_files.inject([]) do |memo, f|
201
+ case
202
+ when File.directory?(f)
203
+ Dir["#{f}/**/*_test.rb"].each do |f1|
204
+ log(f1)
205
+ load f1
206
+ memo << f1
207
+ end
208
+ when File.file?(f)
209
+ log(f)
210
+ load f
211
+ memo << f
212
+ else
213
+ # Ignore
214
+ end
215
+
216
+ memo
217
+ end
218
+ end
219
+
220
+ # Print to STDOUT the results of the run. The parent's listening on the pipe to get the data.
221
+ def report(test_runner, test_files, io=STDOUT)
222
+ info = {:passed => test_runner.passed?, :failures => {}}
223
+ failures = info[:failures]
224
+ test_runner.faults.each do |failure|
225
+ filename, test_name = self.class.parse_failure(failure, test_files)
226
+ if filename.nil? then
227
+ log("Could not map #{failure.test_name.inspect} to a specific test file: mapping to #{test_files.length} files")
228
+ failures[test_name] = test_files
229
+ else
230
+ log("Failed #{failure.test_name.inspect} in #{filename.inspect}")
231
+ failures[test_name] = filename
232
+ end
233
+ end
234
+
235
+ info
236
+ end
237
+ end
238
+
239
+ # A helper class that allows me to get more information from the build.
240
+ #
241
+ # This is something that definitely will change when Nestor is tested on Ruby 1.9.
242
+ class TestRunner < ::Test::Unit::UI::Console::TestRunner #:nodoc:
243
+ attr_reader :faults
244
+
245
+ # This is a duck-typing method. Test::Unit's design requiers a #run method,
246
+ # but it is implemented as a class method. I fake it here to allow me to
247
+ # pass an instance and have the actual TestRunner instance available afterwards.
248
+ def run(suite, output_level=NORMAL)
249
+ @suite = suite.respond_to?(:suite) ? suite.suite : suite
250
+ start
251
+ end
252
+
253
+ # Returns pass/fail status.
254
+ def passed?
255
+ @faults.empty?
256
+ end
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,26 @@
1
+ module Nestor
2
+ # {Nestor::Cli} will require a file named +nestor/mappers/#{framework}/#{test_framework}+. If you want
3
+ # to provide custom mappers, it is possible to do so.
4
+ #
5
+ # Mappers are simple objects that implement the following protocol:
6
+ #
7
+ # <tt>default_script_path</tt>:: Class method to return the path to the default Watchr script.
8
+ # This *must* be a physical file on the filesystem, as Watchr
9
+ # cannot accept anything else at the moment.
10
+ #
11
+ # <tt>log(message)</tt>:: Logs a simple message, either to the console or a logfile.
12
+ # The {Nestor::Machine} will use the +log+ method to notify about it's
13
+ # state transitions.
14
+ #
15
+ # <tt>run_all</tt>:: Runs all the tests, no matter what. In the {Nestor::Mappers::Rails::Test::Unit}
16
+ # case, this means <tt>Dir["test/**/*_test.rb"]</tt>.
17
+ #
18
+ # <tt>run(tests_files, focused_cases=[])</tt>:: Runs only a subset of the tests, maybe
19
+ # focusing on only a couple of tests / examples.
20
+ #
21
+ # <tt>map(file)</tt>:: Given an implementation file, returns the corresponding test to run.
22
+ # {Nestor::Mappers::Rails::Test::Unit} maps +app/models/user.rb+ to
23
+ # +test/unit/user_test.rb+.
24
+ module Mappers
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module Nestor
2
+ class Script < Watchr::Script
3
+ # Let's the script have a reference to the machine, to generate events.
4
+ # The actual instance variable name is +@machine+, not +@nestor_machine+.
5
+ def nestor_machine=(machine)
6
+ @machine = machine
7
+ end
8
+ end
9
+ end
data/lib/nestor.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require "watchr"
2
2
  require "nestor/machine"
3
+ require "nestor/script"
3
4
 
4
5
  begin
5
6
  require "ruby-debug"