mislav-rspactor 0.3.2 → 0.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -1,38 +1,57 @@
1
- begin
2
- require 'jeweler'
3
-
4
- Jeweler.class_eval do
5
- protected
6
-
7
- def fill_in_gemspec_defaults(gemspec)
8
- if gemspec.files.nil? || gemspec.files.empty?
9
- gemspec.files = FileList['Rakefile', '{bin,lib,images,spec}/**/*', 'README*', 'LICENSE*']
10
- end
1
+ task :default => :spec
11
2
 
12
- if gemspec.executables.nil? || gemspec.executables.empty?
13
- gemspec.executables = Dir["#{@base_dir}/bin/*"].map do |f|
14
- File.basename(f)
15
- end
16
- end
3
+ desc "starts RSpactor"
4
+ task :spec do
5
+ system "ruby -Ilib bin/rspactor"
6
+ end
17
7
 
18
- gemspec
19
- end
20
- end
21
-
22
- Jeweler::Tasks.new do |gem|
8
+ desc "generates .gemspec file"
9
+ task :gemspec => "version:read" do
10
+ spec = Gem::Specification.new do |gem|
23
11
  gem.name = "rspactor"
24
12
  gem.summary = "RSpactor is a command line tool to automatically run your changed specs (much like autotest)."
25
13
  gem.email = "mislav.marohnic@gmail.com"
26
14
  gem.homepage = "http://github.com/mislav/rspactor"
27
15
  gem.authors = ["Mislav Marohnić", "Andreas Wolff", "Pelle Braendgaard"]
28
16
  gem.has_rdoc = false
17
+
18
+ gem.version = GEM_VERSION
19
+ gem.files = FileList['Rakefile', '{bin,lib,images,spec}/**/*', 'README*', 'LICENSE*']
20
+ gem.executables = Dir['bin/*'].map { |f| File.basename(f) }
21
+ end
22
+
23
+ spec_string = spec.to_ruby
24
+
25
+ begin
26
+ Thread.new { eval("$SAFE = 3\n#{spec_string}", binding) }.join
27
+ rescue
28
+ abort "unsafe gemspec: #{$!}"
29
+ else
30
+ File.open("#{spec.name}.gemspec", 'w') { |file| file.write spec_string }
29
31
  end
30
- rescue LoadError
31
- puts "Jeweler gem (technicalpickles-jeweler) not found"
32
32
  end
33
33
 
34
- task :spec do
35
- system 'ruby -Ilib bin/rspactor'
34
+ task :bump => ["version:bump", :gemspec]
35
+
36
+ namespace :version do
37
+ task :read do
38
+ unless defined? GEM_VERSION
39
+ GEM_VERSION = File.read("VERSION")
40
+ end
41
+ end
42
+
43
+ task :bump => :read do
44
+ if ENV['VERSION']
45
+ GEM_VERSION.replace ENV['VERSION']
46
+ else
47
+ GEM_VERSION.sub!(/\d+$/) { |num| num.to_i + 1 }
48
+ end
49
+
50
+ File.open("VERSION", 'w') { |v| v.write GEM_VERSION }
51
+ end
36
52
  end
37
53
 
38
- task :default => :spec
54
+ task :release => :bump do
55
+ system %(git commit VERSION *.gemspec -m "release v#{GEM_VERSION}")
56
+ system %(git tag -am "release v#{GEM_VERSION}" v#{GEM_VERSION})
57
+ end
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rspactor/runner'
3
3
 
4
- RSpactor::Runner.load
4
+ RSpactor::Runner.start(:coral => ARGV.include?('--coral'))
@@ -10,6 +10,9 @@ module RSpactor
10
10
 
11
11
  def determine_spec_files(file)
12
12
  candidates = translate(file)
13
+ candidates.reject { |candidate| candidate.index('.') }.each do |dir|
14
+ candidates.reject! { |candidate| candidate.index("#{dir}/") == 0 }
15
+ end
13
16
  spec_files = candidates.select { |candidate| File.exists? candidate }
14
17
 
15
18
  if spec_files.empty?
@@ -41,6 +44,8 @@ module RSpactor
41
44
  candidates << append_spec_file_extension($1)
42
45
  elsif file =~ %r:app/helpers/(\w+)_helper.rb:
43
46
  candidates << "views/#{$1}"
47
+ elsif file =~ /_observer.rb$/
48
+ candidates << candidates.last.sub('_observer', '')
44
49
  end
45
50
  end
46
51
  when %r:^lib/:
@@ -2,52 +2,95 @@ require 'rspactor'
2
2
 
3
3
  module RSpactor
4
4
  class Runner
5
- def self.load
6
- dotfile = File.join(ENV['HOME'], '.rspactor')
7
- Kernel.load dotfile if File.exists?(dotfile)
8
-
9
- dir = Dir.pwd
10
- @inspector = Inspector.new(dir)
11
- @interactor = Interactor.new
12
-
5
+ def self.start(options = {})
6
+ new(Dir.pwd, options).start
7
+ end
8
+
9
+ attr_reader :dir, :options, :inspector, :interactor
10
+
11
+ def initialize(dir, options = {})
12
+ @dir = dir
13
+ @options = options
14
+ read_git_head
15
+ end
16
+
17
+ def start
18
+ load_dotfile
13
19
  puts "** RSpactor is now watching at '#{dir}'"
14
-
15
- aborted = initial_spec_run_abort
20
+ start_interactor
21
+ start_listener
22
+ end
23
+
24
+ def start_interactor
25
+ @interactor = Interactor.new
26
+ aborted = @interactor.wait_for_enter_key("** Hit <enter> to skip initial spec run", 3)
16
27
  @interactor.start_termination_handler
17
28
  run_all_specs unless aborted
29
+ end
30
+
31
+ def start_listener
32
+ @inspector = Inspector.new(dir)
18
33
 
19
34
  Listener.new(Inspector::EXTENSIONS) do |files|
20
- files_to_spec = []
21
- files.each do |file|
22
- spec_files = @inspector.determine_spec_files(file)
23
- unless spec_files.empty?
24
- puts spec_files.join("\n")
25
- files_to_spec.concat spec_files
26
- end
27
- end
28
- run_spec_command(files_to_spec)
35
+ spec_changed_files(files) unless git_head_changed?
29
36
  end.run(dir)
30
37
  end
31
-
32
- def self.initial_spec_run_abort
33
- @interactor.wait_for_enter_key("** Hit <enter> to skip initial spec run", 3)
38
+
39
+ def load_dotfile
40
+ dotfile = File.join(ENV['HOME'], '.rspactor')
41
+ if File.exists?(dotfile)
42
+ begin
43
+ Kernel.load dotfile
44
+ rescue => e
45
+ $stderr.puts "Error while loading #{dotfile}: #{e}"
46
+ end
47
+ end
34
48
  end
35
49
 
36
- def self.run_all_specs
50
+ def run_all_specs
37
51
  run_spec_command('spec')
38
52
  end
39
53
 
40
- def self.run_spec_command(paths)
54
+ def run_spec_command(paths)
41
55
  paths = Array(paths)
42
- return if paths.empty?
43
- run_command [ruby_opts, spec_runner, paths, spec_opts].flatten.join(' ')
56
+ if paths.empty?
57
+ @last_run_failed = nil
58
+ else
59
+ cmd = [ruby_opts, spec_runner, paths, spec_opts].flatten.join(' ')
60
+ @last_run_failed = run_command(cmd)
61
+ end
62
+ end
63
+
64
+ def last_run_failed?
65
+ @last_run_failed == false
44
66
  end
67
+
68
+ protected
45
69
 
46
- def self.run_command(cmd)
70
+ def run_command(cmd)
47
71
  system(cmd)
72
+ $?.success?
48
73
  end
74
+
75
+ def spec_changed_files(files)
76
+ files_to_spec = files.inject([]) do |all, file|
77
+ all.concat inspector.determine_spec_files(file)
78
+ end
79
+ unless files_to_spec.empty?
80
+ puts files_to_spec.join("\n")
81
+
82
+ previous_run_failed = last_run_failed?
83
+ run_spec_command(files_to_spec)
84
+
85
+ if options[:retry_failed] and previous_run_failed and not last_run_failed?
86
+ run_all_specs
87
+ end
88
+ end
89
+ end
90
+
91
+ private
49
92
 
50
- def self.spec_opts
93
+ def spec_opts
51
94
  if File.exist?('spec/spec.opts')
52
95
  opts = File.read('spec/spec.opts').gsub("\n", ' ')
53
96
  else
@@ -61,11 +104,11 @@ module RSpactor
61
104
  opts
62
105
  end
63
106
 
64
- def self.formatter_opts
107
+ def formatter_opts
65
108
  "-r #{File.dirname(__FILE__)}/../rspec_growler.rb -f RSpecGrowler:STDOUT"
66
109
  end
67
110
 
68
- def self.spec_runner
111
+ def spec_runner
69
112
  if File.exist?("script/spec")
70
113
  "script/spec"
71
114
  else
@@ -73,10 +116,22 @@ module RSpactor
73
116
  end
74
117
  end
75
118
 
76
- def self.ruby_opts
119
+ def ruby_opts
77
120
  other = ENV['RUBYOPT'] ? " #{ENV['RUBYOPT']}" : ''
121
+ other << ' -rcoral' if options[:coral]
78
122
  %(RUBYOPT='-Ilib:spec#{other}')
79
123
  end
124
+
125
+ def git_head_changed?
126
+ old_git_head = @git_head
127
+ read_git_head
128
+ @git_head and old_git_head and @git_head != old_git_head
129
+ end
130
+
131
+ def read_git_head
132
+ git_head_file = File.join(dir, '.git', 'HEAD')
133
+ @git_head = File.exists?(git_head_file) && File.read(git_head_file)
134
+ end
80
135
  end
81
136
  end
82
137
 
@@ -64,6 +64,10 @@ describe RSpactor::Inspector do
64
64
  translate('db/schema.rb').should == ['spec/models']
65
65
  end
66
66
 
67
+ it "should consider related model when its observer changes" do
68
+ translate('app/models/user_observer.rb').should == ['spec/models/user_observer_spec.rb', 'spec/models/user_spec.rb']
69
+ end
70
+
67
71
  it "should consider all specs when spec_helper changes" do
68
72
  translate('spec/spec_helper.rb').should == ['spec']
69
73
  end
@@ -78,4 +82,38 @@ describe RSpactor::Inspector do
78
82
  translate('config/boot.rb').should == ['spec']
79
83
  end
80
84
  end
85
+
86
+ describe "#determine_spec_files" do
87
+ def determine(file)
88
+ @inspector.determine_spec_files(file)
89
+ end
90
+
91
+ it "should filter out files that don't exist on the filesystem" do
92
+ @inspector.should_receive(:translate).with('foo').and_return(%w(valid_spec.rb invalid_spec.rb))
93
+ File.should_receive(:exists?).with('valid_spec.rb').and_return(true)
94
+ File.should_receive(:exists?).with('invalid_spec.rb').and_return(false)
95
+ determine('foo').should == ['valid_spec.rb']
96
+ end
97
+
98
+ it "should filter out files in subdirectories that are already on the list" do
99
+ @inspector.should_receive(:translate).with('foo').and_return(%w(
100
+ spec/foo_spec.rb
101
+ spec/views/moo/bar_spec.rb
102
+ spec/views/baa/boo_spec.rb
103
+ spec/models/baz_spec.rb
104
+ spec/controllers/moo_spec.rb
105
+ spec/models
106
+ spec/controllers
107
+ spec/views/baa
108
+ ))
109
+ File.stub!(:exists?).and_return(true)
110
+ determine('foo').should == %w(
111
+ spec/foo_spec.rb
112
+ spec/views/moo/bar_spec.rb
113
+ spec/models
114
+ spec/controllers
115
+ spec/views/baa
116
+ )
117
+ end
118
+ end
81
119
  end
@@ -3,16 +3,12 @@ require 'rspactor/runner'
3
3
  describe RSpactor::Runner do
4
4
 
5
5
  described_class.class_eval do
6
- def self.run_command(cmd)
6
+ def run_command(cmd)
7
7
  # never shell out in tests
8
8
  cmd
9
9
  end
10
10
  end
11
11
 
12
- def runner
13
- described_class
14
- end
15
-
16
12
  def with_env(name, value)
17
13
  old_value = ENV[name]
18
14
  ENV[name] = value
@@ -23,81 +19,139 @@ describe RSpactor::Runner do
23
19
  end
24
20
  end
25
21
 
26
- describe "setup" do
22
+ def capture_stderr(io = StringIO.new)
23
+ @old_stderr, $stderr = $stderr, io
24
+ begin; yield ensure; restore_stderr; end if block_given?
25
+ end
26
+
27
+ def restore_stderr
28
+ $stderr = @old_stderr
29
+ end
30
+
31
+ def capture_stdout(io = StringIO.new)
32
+ @old_stdout, $stdout = $stdout, io
33
+ begin; yield ensure; restore_stdout; end if block_given?
34
+ end
35
+
36
+ def restore_stdout
37
+ $stdout = @old_stdout
38
+ end
39
+
40
+ describe "start" do
27
41
  before(:each) do
28
- Dir.stub!(:pwd).and_return('/my/path')
29
- File.stub!(:exists?).and_return(false)
30
- runner.stub!(:puts)
31
- RSpactor::Inspector.stub!(:new)
32
- RSpactor::Interactor.stub!(:new).and_return(mock('Interactor').as_null_object)
33
- RSpactor::Listener.stub!(:new).and_return(mock('Listener').as_null_object)
42
+ @runner = described_class.new('/my/path')
43
+ capture_stdout
34
44
  end
35
45
 
36
- def setup
37
- runner.load
46
+ after(:each) do
47
+ restore_stdout
38
48
  end
39
49
 
40
- it "should initialize Inspector" do
41
- RSpactor::Inspector.should_receive(:new).with('/my/path')
42
- setup
50
+ def setup
51
+ @runner.start
43
52
  end
53
+
54
+ context "Interactor" do
55
+ before(:each) do
56
+ @runner.stub!(:load_dotfile)
57
+ @runner.stub!(:start_listener)
58
+ @interactor = mock('Interactor')
59
+ @interactor.should_receive(:start_termination_handler)
60
+ RSpactor::Interactor.should_receive(:new).and_return(@interactor)
61
+ end
62
+
63
+ it "should start Interactor" do
64
+ @interactor.should_receive(:wait_for_enter_key).with(instance_of(String), 3)
65
+ setup
66
+ end
44
67
 
45
- it "should start Interactor" do
46
- interactor = mock('Interactor')
47
- interactor.should_receive(:wait_for_enter_key).with(instance_of(String), 3)
48
- interactor.should_receive(:start_termination_handler)
49
- RSpactor::Interactor.should_receive(:new).and_return(interactor)
50
- setup
51
- end
68
+ it "should run all specs if Interactor isn't interrupted" do
69
+ @interactor.should_receive(:wait_for_enter_key).and_return(nil)
70
+ @runner.should_receive(:run_spec_command).with('spec')
71
+ setup
72
+ end
52
73
 
53
- it "should run all specs if Interactor isn't interrupted" do
54
- interactor = mock('Interactor', :start_termination_handler => nil)
55
- interactor.should_receive(:wait_for_enter_key).and_return(nil)
56
- RSpactor::Interactor.should_receive(:new).and_return(interactor)
57
- runner.should_receive(:run_spec_command).with('spec')
58
- setup
74
+ it "should skip running all specs if Interactor is interrupted" do
75
+ @interactor.should_receive(:wait_for_enter_key).and_return(true)
76
+ @runner.should_not_receive(:run_spec_command)
77
+ setup
78
+ end
59
79
  end
60
-
61
- it "should skip running all specs if Interactor is interrupted" do
62
- interactor = mock('Interactor', :start_termination_handler => nil)
63
- interactor.should_receive(:wait_for_enter_key).and_return(true)
64
- RSpactor::Interactor.should_receive(:new).and_return(interactor)
65
- runner.should_not_receive(:run_spec_command)
80
+
81
+ it "should initialize Inspector" do
82
+ @runner.stub!(:load_dotfile)
83
+ @runner.stub!(:start_interactor)
84
+ RSpactor::Inspector.should_receive(:new).with('/my/path')
85
+ RSpactor::Listener.stub!(:new).and_return(mock('Listener').as_null_object)
66
86
  setup
67
87
  end
68
-
69
- it "should run Listener" do
70
- listener = mock('Listener')
71
- listener.should_receive(:run).with('/my/path')
72
- RSpactor::Listener.should_receive(:new).with(instance_of(Array)).and_return(listener)
73
- setup
88
+
89
+ context "Listener" do
90
+ before(:each) do
91
+ @runner.stub!(:load_dotfile)
92
+ @runner.stub!(:start_interactor)
93
+ @inspector = mock("Inspector")
94
+ RSpactor::Inspector.stub!(:new).and_return(@inspector)
95
+ @listener = mock('Listener')
96
+ end
97
+
98
+ it "should run Listener" do
99
+ @listener.should_receive(:run).with('/my/path')
100
+ RSpactor::Listener.should_receive(:new).with(instance_of(Array)).and_return(@listener)
101
+ setup
102
+ end
74
103
  end
75
104
 
76
105
  it "should output 'watching' message on start" do
77
- runner.should_receive(:puts).with("** RSpactor is now watching at '/my/path'")
106
+ @runner.stub!(:load_dotfile)
107
+ @runner.stub!(:start_interactor)
108
+ @runner.stub!(:start_listener)
78
109
  setup
110
+ $stdout.string.chomp.should == "** RSpactor is now watching at '/my/path'"
79
111
  end
80
112
 
81
- it "should load dotfile if found" do
82
- with_env('HOME', '/home/moo') do
83
- File.should_receive(:exists?).with('/home/moo/.rspactor').and_return(true)
84
- Kernel.should_receive(:load).with('/home/moo/.rspactor')
85
- setup
113
+ context "dotfile" do
114
+ before(:each) do
115
+ @runner.stub!(:start_interactor)
116
+ @runner.stub!(:start_listener)
117
+ end
118
+
119
+ it "should load dotfile if found" do
120
+ with_env('HOME', '/home/moo') do
121
+ File.should_receive(:exists?).with('/home/moo/.rspactor').and_return(true)
122
+ Kernel.should_receive(:load).with('/home/moo/.rspactor')
123
+ setup
124
+ end
125
+ end
126
+
127
+ it "should continue even if the dotfile raised errors" do
128
+ with_env('HOME', '/home/moo') do
129
+ File.should_receive(:exists?).and_return(true)
130
+ Kernel.should_receive(:load).with('/home/moo/.rspactor').and_raise(ArgumentError)
131
+ capture_stderr do
132
+ lambda { setup }.should_not raise_error
133
+ $stderr.string.split("\n").should include('Error while loading /home/moo/.rspactor: ArgumentError')
134
+ end
135
+ end
86
136
  end
87
137
  end
88
138
  end
89
139
 
90
140
  describe "#run_spec_command" do
141
+ before(:each) do
142
+ @runner = described_class.new('/my/path')
143
+ end
144
+
91
145
  def with_rubyopt(string, &block)
92
146
  with_env('RUBYOPT', string, &block)
93
147
  end
94
148
 
95
149
  def run(paths)
96
- runner.run_spec_command(paths)
150
+ @runner.run_spec_command(paths)
97
151
  end
98
152
 
99
153
  it "should exit if the paths argument is empty" do
100
- runner.should_not_receive(:run_command)
154
+ @runner.should_not_receive(:run_command)
101
155
  run([])
102
156
  end
103
157
 
@@ -130,14 +184,61 @@ describe RSpactor::Runner do
130
184
  end
131
185
 
132
186
  it "should not include 'progress' formatter if there already are 2 or more formatters" do
133
- runner.should_receive(:formatter_opts).and_return('-f foo --format bar')
187
+ @runner.should_receive(:formatter_opts).and_return('-f foo --format bar')
134
188
  run('foo').should_not include('-f progress')
135
189
  end
190
+
191
+ it "should save status of last run" do
192
+ @runner.should_receive(:run_command).twice.and_return(true, false)
193
+ run('foo')
194
+ @runner.last_run_failed?.should be_false
195
+ run('bar')
196
+ @runner.last_run_failed?.should be_true
197
+ run([])
198
+ @runner.last_run_failed?.should be_false
199
+ end
200
+ end
201
+
202
+ describe "#spec_changed_files" do
203
+ before(:each) do
204
+ @runner = described_class.new('.')
205
+ @runner.stub!(:inspector).and_return(mock("Inspector"))
206
+ end
207
+
208
+ def set_inspector_expectation(file, ret)
209
+ @runner.inspector.should_receive(:determine_spec_files).with(file).and_return(ret)
210
+ end
211
+
212
+ it "should find and run spec files" do
213
+ set_inspector_expectation('moo.rb', ['spec/moo_spec.rb'])
214
+ set_inspector_expectation('views/baz.haml', [])
215
+ set_inspector_expectation('config/bar.yml', ['spec/bar_spec.rb', 'spec/bar_stuff_spec.rb'])
216
+
217
+ expected = %w(spec/moo_spec.rb spec/bar_spec.rb spec/bar_stuff_spec.rb)
218
+ @runner.should_receive(:run_spec_command).with(expected)
219
+
220
+ capture_stdout do
221
+ @runner.send(:spec_changed_files, %w(moo.rb views/baz.haml config/bar.yml))
222
+ $stdout.string.split("\n").should == expected
223
+ end
224
+ end
225
+
226
+ it "should run the full suite after a run succeded when the previous one failed" do
227
+ @runner.inspector.stub!(:determine_spec_files).and_return(['spec/foo_spec.rb'], ['spec/bar_spec.rb'])
228
+ @runner.stub!(:options).and_return({ :retry_failed => true })
229
+
230
+ capture_stdout do
231
+ @runner.stub!(:run_spec_command)
232
+ @runner.should_receive(:last_run_failed?).and_return(true, false)
233
+ @runner.should_receive(:run_all_specs)
234
+ @runner.send(:spec_changed_files, %w(moo.rb))
235
+ end
236
+ end
136
237
  end
137
238
 
138
239
  it "should have Runner in global namespace for backwards compatibility" do
139
240
  defined?(::Runner).should be_true
140
- ::Runner.should == runner
241
+ ::Runner.should == described_class
141
242
  end
142
243
 
143
244
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mislav-rspactor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.2
4
+ version: 0.3.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Mislav Marohni\xC4\x87"
@@ -11,7 +11,7 @@ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
13
 
14
- date: 2009-03-17 00:00:00 -07:00
14
+ date: 2009-03-27 00:00:00 -07:00
15
15
  default_executable: rspactor
16
16
  dependencies: []
17
17