mislav-rspactor 0.3.2 → 0.3.3

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.
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