nestor 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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"