rspactor 0.2.0 → 0.5.4
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/LICENSE +20 -0
- data/Rakefile +58 -0
- data/bin/rspactor +9 -6
- data/images/failed.png +0 -0
- data/images/pending.png +0 -0
- data/images/success.png +0 -0
- data/lib/cucumber_growler.rb +61 -0
- data/lib/rspactor/celerity.rb +29 -0
- data/lib/rspactor/growl.rb +14 -0
- data/lib/rspactor/inspector.rb +110 -0
- data/lib/rspactor/interactor.rb +85 -0
- data/lib/rspactor/listener.rb +88 -0
- data/lib/rspactor/runner.rb +193 -0
- data/lib/rspactor/spork.rb +25 -0
- data/lib/rspactor.rb +11 -0
- data/lib/rspec_growler.rb +24 -0
- data/spec/inspector_spec.rb +131 -0
- data/spec/listener_spec.rb +39 -0
- data/spec/runner_spec.rb +259 -0
- metadata +33 -17
- data/asset/rails_fail.png +0 -0
- data/asset/rails_ok.png +0 -0
- data/lib/inspector.rb +0 -92
- data/lib/interactor.rb +0 -63
- data/lib/listener.rb +0 -55
- data/lib/resulting.rb +0 -47
- data/lib/runner.rb +0 -81
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Andreas Wolff
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
task :default => :spec
|
2
|
+
|
3
|
+
desc "starts RSpactor"
|
4
|
+
task :spec do
|
5
|
+
system "ruby -Ilib bin/rspactor"
|
6
|
+
end
|
7
|
+
|
8
|
+
desc "generates .gemspec file"
|
9
|
+
task :gemspec => "version:read" do
|
10
|
+
spec = Gem::Specification.new do |gem|
|
11
|
+
gem.name = "rspactor"
|
12
|
+
gem.summary = "RSpactor is a command line tool to automatically run your changed specs & cucumber features (much like autotest)."
|
13
|
+
gem.description = "read summary!"
|
14
|
+
gem.email = "guillaumegentil@gmail.com"
|
15
|
+
gem.homepage = "http://github.com/guillaumegentil/rspactor"
|
16
|
+
gem.authors = ["Mislav Marohnić", "Andreas Wolff", "Pelle Braendgaard", "Thibaud Guillaume-Gentil"]
|
17
|
+
gem.has_rdoc = false
|
18
|
+
|
19
|
+
gem.version = GEM_VERSION
|
20
|
+
gem.files = FileList['Rakefile', '{bin,lib,images,spec}/**/*', 'README*', 'LICENSE*']
|
21
|
+
gem.executables = Dir['bin/*'].map { |f| File.basename(f) }
|
22
|
+
end
|
23
|
+
|
24
|
+
spec_string = spec.to_ruby
|
25
|
+
|
26
|
+
begin
|
27
|
+
Thread.new { eval("$SAFE = 3\n#{spec_string}", binding) }.join
|
28
|
+
rescue
|
29
|
+
abort "unsafe gemspec: #{$!}"
|
30
|
+
else
|
31
|
+
File.open("#{spec.name}.gemspec", 'w') { |file| file.write spec_string }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
task :bump => ["version:bump", :gemspec]
|
36
|
+
|
37
|
+
namespace :version do
|
38
|
+
task :read do
|
39
|
+
unless defined? GEM_VERSION
|
40
|
+
GEM_VERSION = File.read("VERSION")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
task :bump => :read do
|
45
|
+
if ENV['VERSION']
|
46
|
+
GEM_VERSION.replace ENV['VERSION']
|
47
|
+
else
|
48
|
+
GEM_VERSION.sub!(/\d+$/) { |num| num.to_i + 1 }
|
49
|
+
end
|
50
|
+
|
51
|
+
File.open("VERSION", 'w') { |v| v.write GEM_VERSION }
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
task :release => :bump do
|
56
|
+
system %(git commit VERSION *.gemspec -m "release v#{GEM_VERSION}")
|
57
|
+
system %(git tag -am "release v#{GEM_VERSION}" v#{GEM_VERSION})
|
58
|
+
end
|
data/bin/rspactor
CHANGED
@@ -1,8 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib/rspactor'))
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
4
|
+
RSpactor::Runner.start({
|
5
|
+
:coral => ARGV.delete('--coral'),
|
6
|
+
:celerity => ARGV.delete('--celerity'),
|
7
|
+
:spork => ARGV.delete('--drb'),
|
8
|
+
:view => ARGV.delete('--view'), # by default, rspactor didn't catch specs view
|
9
|
+
:clear => ARGV.delete('--clear'),
|
10
|
+
:run_in => ARGV.last
|
11
|
+
})
|
data/images/failed.png
ADDED
Binary file
|
data/images/pending.png
ADDED
Binary file
|
data/images/success.png
ADDED
Binary file
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'cucumber'
|
2
|
+
require 'cucumber/formatter/console'
|
3
|
+
require File.dirname(__FILE__) + '/rspactor/growl'
|
4
|
+
|
5
|
+
module CucumberGrowler
|
6
|
+
include RSpactor::Growl
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
alias original_print_stats print_stats
|
11
|
+
include InstanceMethods
|
12
|
+
|
13
|
+
def print_stats(features)
|
14
|
+
title, icon, messages = '', '', []
|
15
|
+
[:failed, :skipped, :undefined, :pending, :passed].reverse.each do |status|
|
16
|
+
if step_mother.steps(status).any?
|
17
|
+
icon = icon_for(status)
|
18
|
+
# title = title_for(status)
|
19
|
+
messages << dump_count(step_mother.steps(status).length, "step", status.to_s)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
notify "Cucumber Results", messages.reverse.join(", "), icon
|
24
|
+
original_print_stats(features)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
module InstanceMethods
|
30
|
+
def icon_for(status)
|
31
|
+
case status
|
32
|
+
when :passed
|
33
|
+
'success'
|
34
|
+
when :pending, :undefined, :skipped
|
35
|
+
'pending'
|
36
|
+
when :failed
|
37
|
+
'failed'
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def title_for(status)
|
42
|
+
case status
|
43
|
+
when :passed
|
44
|
+
'Features passed!'
|
45
|
+
when :pending
|
46
|
+
'Some steps are pending...'
|
47
|
+
when :undefined
|
48
|
+
'Some undefined steps...'
|
49
|
+
when :skipped
|
50
|
+
'Some steps skipped...'
|
51
|
+
when :failed
|
52
|
+
'Failures occurred!'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
module Cucumber::Formatter::Console
|
60
|
+
include CucumberGrowler
|
61
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rspactor'
|
2
|
+
|
3
|
+
module RSpactor
|
4
|
+
class Celerity
|
5
|
+
|
6
|
+
def self.start(dir)
|
7
|
+
pid_path = "#{dir}/tmp/pids/mongrel_celerity.pid"
|
8
|
+
if File.exist?(pid_path)
|
9
|
+
system("kill $(head #{pid_path}) >/dev/null 2>&1")
|
10
|
+
system("rm #{pid_path} >/dev/null 2>&1")
|
11
|
+
end
|
12
|
+
# kill other mongrels
|
13
|
+
system("kill $(ps aux | grep 'mongrel_rails' | grep -v grep | awk '//{print $2;}') >/dev/null 2>&1")
|
14
|
+
system("rake celerity_server:start >/dev/null 2>&1 &")
|
15
|
+
Interactor.ticker_msg "** Starting celerity server"
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.restart
|
19
|
+
system("rake celerity_server:stop >/dev/null 2>&1 && rake celerity_server:start >/dev/null 2>&1 &")
|
20
|
+
Interactor.ticker_msg "** Restarting celerity server"
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.kill_jruby
|
24
|
+
system("kill $(ps aux | grep jruby | grep -v grep | awk '//{print $2;}') >/dev/null 2>&1")
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module RSpactor
|
2
|
+
module Growl
|
3
|
+
extend self
|
4
|
+
|
5
|
+
def notify(title, msg, icon, pri = 0)
|
6
|
+
system("growlnotify -w -n rspactor --image #{image_path(icon)} -p #{pri} -m #{msg.inspect} #{title} &")
|
7
|
+
end
|
8
|
+
|
9
|
+
# failed | pending | success
|
10
|
+
def image_path(icon)
|
11
|
+
File.expand_path File.dirname(__FILE__) + "/../../images/#{icon}.png"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'rspactor'
|
2
|
+
|
3
|
+
module RSpactor
|
4
|
+
# Maps the changed filenames to list of specs to run in the next go.
|
5
|
+
# Assumes Rails-like directory structure
|
6
|
+
class Inspector
|
7
|
+
EXTENSIONS = %w(rb erb builder haml rhtml rxml yml conf opts feature)
|
8
|
+
|
9
|
+
attr_reader :runner, :root
|
10
|
+
|
11
|
+
def initialize(runner)
|
12
|
+
@runner = runner
|
13
|
+
@root = runner.dir
|
14
|
+
end
|
15
|
+
|
16
|
+
def determine_files(file)
|
17
|
+
candidates = translate(file)
|
18
|
+
cucumberable = candidates.delete('cucumber')
|
19
|
+
candidates.reject { |candidate| candidate.index('.') }.each do |dir|
|
20
|
+
candidates.reject! { |candidate| candidate.index("#{dir}/") == 0 }
|
21
|
+
end
|
22
|
+
files = candidates.select { |candidate| File.exists? candidate }
|
23
|
+
|
24
|
+
if files.empty? && !candidates.empty? && !cucumberable
|
25
|
+
$stderr.puts "doesn't exist: #{candidates.inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
files << 'cucumber' if cucumberable
|
29
|
+
files
|
30
|
+
end
|
31
|
+
|
32
|
+
# mappings for Rails are inspired by autotest mappings in rspec-rails
|
33
|
+
def translate(file)
|
34
|
+
file = file.sub(%r:^#{Regexp.escape(root)}/:, '')
|
35
|
+
candidates = []
|
36
|
+
|
37
|
+
if spec_file?(file)
|
38
|
+
candidates << file
|
39
|
+
elsif cucumber_file?(file)
|
40
|
+
candidates << 'cucumber'
|
41
|
+
else
|
42
|
+
spec_file = append_spec_file_extension(file)
|
43
|
+
|
44
|
+
case file
|
45
|
+
when %r:^app/:
|
46
|
+
if file =~ %r:^app/controllers/application(_controller)?.rb$:
|
47
|
+
candidates << 'controllers'
|
48
|
+
elsif file == 'app/helpers/application_helper.rb'
|
49
|
+
candidates << 'helpers' << 'views'
|
50
|
+
elsif !file.include?("app/views/") || runner.options[:view]
|
51
|
+
candidates << spec_file.sub('app/', '')
|
52
|
+
|
53
|
+
if file =~ %r:^app/(views/.+\.[a-z]+)\.[a-z]+$:
|
54
|
+
candidates << append_spec_file_extension($1)
|
55
|
+
elsif file =~ %r:app/helpers/(\w+)_helper.rb:
|
56
|
+
candidates << "views/#{$1}"
|
57
|
+
elsif file =~ /_observer.rb$/
|
58
|
+
candidates << candidates.last.sub('_observer', '')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
when %r:^lib/:
|
62
|
+
candidates << spec_file
|
63
|
+
# lib/foo/bar_spec.rb -> lib/bar_spec.rb
|
64
|
+
candidates << candidates.last.sub($&, '')
|
65
|
+
# lib/bar_spec.rb -> bar_spec.rb
|
66
|
+
candidates << candidates.last.sub(%r:\w+/:, '') if candidates.last.index('/')
|
67
|
+
when 'config/routes.rb'
|
68
|
+
candidates << 'controllers' << 'helpers' << 'views' << 'routing'
|
69
|
+
when 'config/database.yml', 'db/schema.rb', 'spec/factories.rb'
|
70
|
+
candidates << 'models'
|
71
|
+
when 'config/boot.rb', 'config/environment.rb', %r:^config/environments/:, %r:^config/initializers/:, %r:^vendor/:, 'spec/spec_helper.rb'
|
72
|
+
Spork.reload if runner.options[:spork]
|
73
|
+
Celerity.restart if runner.options[:celerity]
|
74
|
+
candidates << 'spec'
|
75
|
+
when %r:^config/:
|
76
|
+
# nothing
|
77
|
+
when %r:^(spec/(spec_helper|shared/.*)|config/(boot|environment(s/test)?))\.rb$:, 'spec/spec.opts', 'spec/fakeweb.rb'
|
78
|
+
candidates << 'spec'
|
79
|
+
else
|
80
|
+
candidates << spec_file
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
candidates.map do |candidate|
|
85
|
+
if candidate == 'cucumber'
|
86
|
+
candidate
|
87
|
+
elsif candidate.index('spec') == 0
|
88
|
+
File.join(root, candidate)
|
89
|
+
else
|
90
|
+
File.join(root, 'spec', candidate)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def append_spec_file_extension(file)
|
96
|
+
if File.extname(file) == ".rb"
|
97
|
+
file.sub(/.rb$/, "_spec.rb")
|
98
|
+
else
|
99
|
+
file + "_spec.rb"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def spec_file?(file)
|
104
|
+
file =~ /^spec\/.+_spec.rb$/
|
105
|
+
end
|
106
|
+
def cucumber_file?(file)
|
107
|
+
file =~ /^features\/.+$/
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'timeout'
|
2
|
+
|
3
|
+
module RSpactor
|
4
|
+
class Interactor
|
5
|
+
|
6
|
+
attr_reader :runner
|
7
|
+
|
8
|
+
def initialize(runner)
|
9
|
+
@runner = runner
|
10
|
+
ticker
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.ticker_msg(msg, seconds_to_wait = 3)
|
14
|
+
$stdout.print msg
|
15
|
+
seconds_to_wait.times do
|
16
|
+
$stdout.print('.')
|
17
|
+
$stdout.flush
|
18
|
+
sleep 1
|
19
|
+
end
|
20
|
+
$stdout.puts "\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
def wait_for_enter_key(msg, seconds_to_wait, clear = runner.options[:clear])
|
24
|
+
begin
|
25
|
+
Timeout::timeout(seconds_to_wait) do
|
26
|
+
system("clear;") if clear
|
27
|
+
ticker(:start => true, :msg => msg)
|
28
|
+
$stdin.gets
|
29
|
+
return true
|
30
|
+
end
|
31
|
+
rescue Timeout::Error
|
32
|
+
false
|
33
|
+
ensure
|
34
|
+
ticker(:stop => true)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_termination_handler
|
39
|
+
@main_thread = Thread.current
|
40
|
+
Thread.new do
|
41
|
+
loop do
|
42
|
+
sleep 0.5
|
43
|
+
if entry = $stdin.gets
|
44
|
+
case entry
|
45
|
+
when "c\n" # Cucumber: current tagged feature
|
46
|
+
runner.run_cucumber_command
|
47
|
+
when "ca\n" # Cucumber All: ~pending tagged feature
|
48
|
+
runner.run_cucumber_command('~@wip,~@pending')
|
49
|
+
else
|
50
|
+
if wait_for_enter_key("** Running all specs... Hit <enter> again to exit RSpactor", 1)
|
51
|
+
@main_thread.exit
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
runner.run_all_specs
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def ticker(opts = {})
|
64
|
+
if opts[:stop]
|
65
|
+
$stdout.puts "\n"
|
66
|
+
@pointer_running = false
|
67
|
+
elsif opts[:start]
|
68
|
+
@pointer_running = true
|
69
|
+
write(opts[:msg]) if opts[:msg]
|
70
|
+
else
|
71
|
+
Thread.new do
|
72
|
+
loop do
|
73
|
+
write('.') if @pointer_running == true
|
74
|
+
sleep 1.0
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def write(msg)
|
81
|
+
$stdout.print(msg)
|
82
|
+
$stdout.flush
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'osx/foundation'
|
2
|
+
OSX.require_framework '/System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework'
|
3
|
+
|
4
|
+
module RSpactor
|
5
|
+
# based on http://rails.aizatto.com/2007/11/28/taming-the-autotest-beast-with-fsevents/
|
6
|
+
class Listener
|
7
|
+
attr_reader :last_check, :callback, :valid_extensions
|
8
|
+
|
9
|
+
def initialize(valid_extensions = nil)
|
10
|
+
@valid_extensions = valid_extensions
|
11
|
+
timestamp_checked
|
12
|
+
|
13
|
+
@callback = lambda do |stream, ctx, num_events, paths, marks, event_ids|
|
14
|
+
changed_files = extract_changed_files_from_paths(split_paths(paths, num_events))
|
15
|
+
timestamp_checked
|
16
|
+
yield changed_files unless changed_files.empty?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def run(directories)
|
21
|
+
dirs = Array(directories)
|
22
|
+
stream = OSX::FSEventStreamCreate(OSX::KCFAllocatorDefault, callback, nil, dirs, OSX::KFSEventStreamEventIdSinceNow, 0.5, 0)
|
23
|
+
unless stream
|
24
|
+
$stderr.puts "Failed to create stream"
|
25
|
+
exit(1)
|
26
|
+
end
|
27
|
+
|
28
|
+
OSX::FSEventStreamScheduleWithRunLoop(stream, OSX::CFRunLoopGetCurrent(), OSX::KCFRunLoopDefaultMode)
|
29
|
+
unless OSX::FSEventStreamStart(stream)
|
30
|
+
$stderr.puts "Failed to start stream"
|
31
|
+
exit(1)
|
32
|
+
end
|
33
|
+
|
34
|
+
begin
|
35
|
+
OSX::CFRunLoopRun()
|
36
|
+
rescue Interrupt
|
37
|
+
OSX::FSEventStreamStop(stream)
|
38
|
+
OSX::FSEventStreamInvalidate(stream)
|
39
|
+
OSX::FSEventStreamRelease(stream)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def timestamp_checked
|
44
|
+
@last_check = Time.now
|
45
|
+
end
|
46
|
+
|
47
|
+
def split_paths(paths, num_events)
|
48
|
+
paths.regard_as('*')
|
49
|
+
rpaths = []
|
50
|
+
num_events.times { |i| rpaths << paths[i] }
|
51
|
+
rpaths
|
52
|
+
end
|
53
|
+
|
54
|
+
def extract_changed_files_from_paths(paths)
|
55
|
+
changed_files = []
|
56
|
+
paths.each do |path|
|
57
|
+
next if ignore_path?(path)
|
58
|
+
Dir.glob(path + "*").each do |file|
|
59
|
+
next if ignore_file?(file)
|
60
|
+
changed_files << file if file_changed?(file)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
changed_files
|
64
|
+
end
|
65
|
+
|
66
|
+
def file_changed?(file)
|
67
|
+
File.stat(file).mtime > last_check
|
68
|
+
rescue Errno::ENOENT
|
69
|
+
false
|
70
|
+
end
|
71
|
+
|
72
|
+
def ignore_path?(path)
|
73
|
+
path =~ /(?:^|\/)\.(git|svn)/
|
74
|
+
end
|
75
|
+
|
76
|
+
def ignore_file?(file)
|
77
|
+
File.basename(file).index('.') == 0 or not valid_extension?(file)
|
78
|
+
end
|
79
|
+
|
80
|
+
def file_extension(file)
|
81
|
+
file =~ /\.(\w+)$/ and $1
|
82
|
+
end
|
83
|
+
|
84
|
+
def valid_extension?(file)
|
85
|
+
valid_extensions.nil? or valid_extensions.include?(file_extension(file))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'rspactor'
|
2
|
+
|
3
|
+
module RSpactor
|
4
|
+
class Runner
|
5
|
+
def self.start(options = {})
|
6
|
+
run_in = options.delete(:run_in) || Dir.pwd
|
7
|
+
new(run_in, options).start
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_reader :dir, :options, :inspector, :interactor
|
11
|
+
|
12
|
+
def initialize(dir, options = {})
|
13
|
+
@dir = dir
|
14
|
+
@options = options
|
15
|
+
read_git_head
|
16
|
+
end
|
17
|
+
|
18
|
+
def start
|
19
|
+
load_dotfile
|
20
|
+
puts "** RSpactor, now watching at '#{dir}'"
|
21
|
+
Spork.start if options[:spork]
|
22
|
+
Celerity.start(dir) if options[:celerity]
|
23
|
+
start_interactor
|
24
|
+
start_listener
|
25
|
+
end
|
26
|
+
|
27
|
+
def start_interactor
|
28
|
+
@interactor = Interactor.new(self)
|
29
|
+
aborted = @interactor.wait_for_enter_key("** Hit <enter> to skip initial spec & cucumber run", 2, false)
|
30
|
+
@interactor.start_termination_handler
|
31
|
+
unless aborted
|
32
|
+
run_all_specs
|
33
|
+
run_cucumber_command('~@wip,~@pending', false)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def start_listener
|
38
|
+
@inspector = Inspector.new(self)
|
39
|
+
|
40
|
+
Listener.new(Inspector::EXTENSIONS) do |files|
|
41
|
+
changed_files(files) unless git_head_changed?
|
42
|
+
end.run(dir)
|
43
|
+
end
|
44
|
+
|
45
|
+
def load_dotfile
|
46
|
+
dotfile = File.join(ENV['HOME'], '.rspactor')
|
47
|
+
if File.exists?(dotfile)
|
48
|
+
begin
|
49
|
+
Kernel.load dotfile
|
50
|
+
rescue => e
|
51
|
+
$stderr.puts "Error while loading #{dotfile}: #{e}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def run_all_specs
|
57
|
+
run_spec_command(File.join(dir, 'spec'))
|
58
|
+
end
|
59
|
+
|
60
|
+
def run_spec_command(paths)
|
61
|
+
paths = Array(paths)
|
62
|
+
if paths.empty?
|
63
|
+
@last_run_failed = nil
|
64
|
+
else
|
65
|
+
cmd = [ruby_opts, spec_runner, paths, spec_opts].flatten.join(' ')
|
66
|
+
@last_run_failed = run_command(cmd)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def run_cucumber_command(tags = '@wip:2', clear = @options[:clear])
|
71
|
+
return unless File.exist?(File.join(dir, 'features'))
|
72
|
+
|
73
|
+
system("clear;") if clear
|
74
|
+
puts "** Running all #{tags} tagged features..."
|
75
|
+
cmd = [ruby_opts, cucumber_runner, cucumber_opts(tags)].flatten.join(' ')
|
76
|
+
@last_run_failed = run_command(cmd)
|
77
|
+
# Workaround for killing jruby process when used with celerity and spork
|
78
|
+
Celerity.kill_jruby if options[:celerity] && options[:spork]
|
79
|
+
end
|
80
|
+
|
81
|
+
def last_run_failed?
|
82
|
+
@last_run_failed == false
|
83
|
+
end
|
84
|
+
|
85
|
+
protected
|
86
|
+
|
87
|
+
def run_command(cmd)
|
88
|
+
system(cmd)
|
89
|
+
$?.success?
|
90
|
+
end
|
91
|
+
|
92
|
+
def changed_files(files)
|
93
|
+
files = files.inject([]) do |all, file|
|
94
|
+
all.concat inspector.determine_files(file)
|
95
|
+
end
|
96
|
+
unless files.empty?
|
97
|
+
|
98
|
+
# cucumber features
|
99
|
+
if files.delete('cucumber')
|
100
|
+
run_cucumber_command
|
101
|
+
end
|
102
|
+
|
103
|
+
# specs files
|
104
|
+
unless files.empty?
|
105
|
+
system("clear;") if @options[:clear]
|
106
|
+
files.uniq!
|
107
|
+
puts files.map { |f| f.to_s.gsub(/#{dir}/, '') }.join("\n")
|
108
|
+
|
109
|
+
previous_run_failed = last_run_failed?
|
110
|
+
run_spec_command(files)
|
111
|
+
|
112
|
+
if options[:retry_failed] and previous_run_failed and not last_run_failed?
|
113
|
+
run_all_specs
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
def spec_opts
|
122
|
+
if File.exist?('spec/spec.opts')
|
123
|
+
opts = File.read('spec/spec.opts').gsub("\n", ' ')
|
124
|
+
else
|
125
|
+
opts = "--color"
|
126
|
+
end
|
127
|
+
|
128
|
+
opts << spec_formatter_opts
|
129
|
+
# only add the "progress" formatter unless no other (besides growl) is specified
|
130
|
+
opts << ' -f progress' unless opts.scan(/\s(?:-f|--format)\b/).length > 1
|
131
|
+
|
132
|
+
opts
|
133
|
+
end
|
134
|
+
|
135
|
+
def cucumber_opts(tags)
|
136
|
+
if File.exist?('features/support/cucumber.opts')
|
137
|
+
opts = File.read('features/support/cucumber.opts').gsub("\n", ' ')
|
138
|
+
else
|
139
|
+
opts = "--color --format progress --drb --no-profile"
|
140
|
+
end
|
141
|
+
|
142
|
+
opts << " --tags #{tags}"
|
143
|
+
opts << cucumber_formatter_opts
|
144
|
+
opts << " --require features" # because using require option overwrite default require
|
145
|
+
opts << " features"
|
146
|
+
opts
|
147
|
+
end
|
148
|
+
|
149
|
+
def spec_formatter_opts
|
150
|
+
" --require #{File.dirname(__FILE__)}/../rspec_growler.rb --format RSpecGrowler:STDOUT"
|
151
|
+
end
|
152
|
+
|
153
|
+
def cucumber_formatter_opts
|
154
|
+
" --require #{File.dirname(__FILE__)}/../cucumber_growler.rb"
|
155
|
+
end
|
156
|
+
|
157
|
+
def spec_runner
|
158
|
+
if File.exist?("script/spec")
|
159
|
+
"script/spec"
|
160
|
+
else
|
161
|
+
"spec"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def cucumber_runner
|
166
|
+
if File.exist?("script/cucumber")
|
167
|
+
"script/cucumber"
|
168
|
+
else
|
169
|
+
"cucumber"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def ruby_opts
|
174
|
+
other = ENV['RUBYOPT'] ? " #{ENV['RUBYOPT']}" : ''
|
175
|
+
other << ' -rcoral' if options[:coral]
|
176
|
+
%(RUBYOPT='-Ilib:spec#{other}')
|
177
|
+
end
|
178
|
+
|
179
|
+
def git_head_changed?
|
180
|
+
old_git_head = @git_head
|
181
|
+
read_git_head
|
182
|
+
@git_head and old_git_head and @git_head != old_git_head
|
183
|
+
end
|
184
|
+
|
185
|
+
def read_git_head
|
186
|
+
git_head_file = File.join(dir, '.git', 'HEAD')
|
187
|
+
@git_head = File.exists?(git_head_file) && File.read(git_head_file)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
# backward compatibility
|
193
|
+
Runner = RSpactor::Runner
|