├── .gitignore
├── .rspec
├── .travis.yml
├── Gemfile
├── Guardfile
├── LICENSE
├── README.md
├── Rakefile
├── guard-spork.gemspec
├── lib
└── guard
│ ├── spork.rb
│ └── spork
│ ├── rinda_ring_finger_patch.rb
│ ├── runner.rb
│ ├── spork_instance.rb
│ ├── spork_windows_instance.rb
│ ├── templates
│ └── Guardfile
│ └── version.rb
└── spec
├── guard
├── spork
│ ├── runner_spec.rb
│ ├── spork_instance_spec.rb
│ └── spork_windows_instance_spec.rb
└── spork_spec.rb
└── spec_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | pkg/*
2 | *.gem
3 | .bundle
4 | Gemfile.lock
5 | .rvmrc
6 | .rbx
7 |
8 | ## MAC OS
9 | .DS_Store
10 | .Trashes
11 | .com.apple.timemachine.supported
12 | .fseventsd
13 | Desktop DB
14 | Desktop DF
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 | --require spec_helper
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.9.3
3 | - 2.0.0
4 | - 2.1.2
5 | - jruby
6 | - rbx
7 | matrix:
8 | fast_finish: true
9 | allow_failures:
10 | - rvm: rbx
11 | - rvm: jruby
12 | notifications:
13 | recipients:
14 | - xrkhill@gmail.com
15 | irc: "irc.freenode.org#guard"
16 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "https://rubygems.org"
2 |
3 | gemspec
4 |
5 | gem 'rake'
6 | gem 'ruby_gntp'
7 | platforms :ruby do
8 | gem 'rb-readline'
9 | end
10 |
--------------------------------------------------------------------------------
/Guardfile:
--------------------------------------------------------------------------------
1 | guard :rspec, cmd: "bundle exec rspec" do
2 | require "ostruct"
3 |
4 | rspec = OpenStruct.new
5 | rspec.spec_dir = "spec"
6 | rspec.spec = ->(m) { "#{rspec.spec_dir}/#{m}_spec.rb" }
7 | rspec.spec_helper = "#{rspec.spec_dir}/spec_helper.rb"
8 |
9 | # matchers
10 | rspec.spec_files = %r{^#{rspec.spec_dir}/.+_spec\.rb$}
11 |
12 | # Ruby apps
13 | ruby = OpenStruct.new
14 | ruby.lib_files = %r{^(lib/.+)\.rb$}
15 |
16 | watch(rspec.spec_files)
17 | watch(rspec.spec_helper) { rspec.spec_dir }
18 | watch(ruby.lib_files) { |m| rspec.spec.(m[1]) }
19 | end
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2010-12 Thibaud Guillaume-Gentil
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Guard::Spork [](http://travis-ci.org/guard/guard-spork) [](https://gemnasium.com/guard/guard-spork) [](http://badge.fury.io/rb/guard-spork)
2 |
3 |
4 | Guard::Spork allows to automatically & intelligently start/reload your RSpec/Cucumber/Test::Unit [Spork](https://github.com/sporkrb/spork) server(s).
5 |
6 | * Compatible with Spork 0.8.4 & 0.9.0.rcX.
7 | * Tested against Ruby 1.9.3, 2.0.0, 2.1.2, and JRuby.
8 |
9 | ## Install
10 |
11 | Please be sure to have [Guard](https://github.com/guard/guard) installed before continuing.
12 |
13 | Install the gem:
14 |
15 | $ gem install guard-spork
16 |
17 | Add it to your Gemfile (inside development group):
18 |
19 | ```ruby
20 | group :development do
21 | gem 'guard-spork'
22 | end
23 | ```
24 |
25 | Add guard definition to your Guardfile with:
26 |
27 | $ guard init spork
28 |
29 | ## Usage
30 |
31 | Please read the [Guard usage documentation](https://github.com/guard/guard#readme).
32 |
33 | ## Guardfile
34 |
35 | Please read [Guard doc](https://github.com/guard/guard#readme) for more info about the Guardfile DSL.
36 |
37 | **IMPORTANT: place Spork guard before RSpec/Cucumber/Test::Unit guards!**
38 |
39 | ### Rails app
40 |
41 | ``` ruby
42 | guard 'spork' do
43 | watch('config/application.rb')
44 | watch('config/environment.rb')
45 | watch(%r{^config/environments/.*\.rb$})
46 | watch(%r{^config/initializers/.*\.rb$})
47 | watch('Gemfile.lock')
48 | watch('spec/spec_helper.rb') { :rspec }
49 | watch('test/test_helper.rb') { :test_unit }
50 | watch(%r{features/support/}) { :cucumber }
51 | end
52 | ```
53 |
54 | ### Running specs over Spork
55 |
56 | Pass the `:cmd => "rspec --drb"` option to [Guard::RSpec](https://github.com/guard/guard-rspec) and/or [Guard::Cucumber](https://github.com/guard/guard-cucumber) to run them over the Spork DRb server:
57 |
58 | ``` ruby
59 | guard 'rspec', :cmd => "rspec --drb" do
60 | # ...
61 | end
62 |
63 | guard 'cucumber', :cmd => "rspec --drb" do
64 | # ...
65 | end
66 | ```
67 |
68 | For MiniTest Guard you should pass the `:drb => true` option:
69 |
70 | ``` ruby
71 | guard 'minitest', :drb => true do
72 | # ...
73 | end
74 | ```
75 |
76 | ## Options
77 |
78 | Guard::Spork automatically detect RSpec/Cucumber/Test::Unit/Bundler presence but you can disable any of them with the corresponding options:
79 |
80 | ``` ruby
81 | guard 'spork', :rspec => false, :cucumber => false, :test_unit => false, :bundler => false do
82 | # ...
83 | end
84 | ```
85 |
86 | You can provide additional environment variables for RSpec, Cucumber, and Test::Unit with the :rspec_env, :cucumber_env, and :test_unit_env options:
87 |
88 | ``` ruby
89 | guard 'spork', :cucumber_env => { 'RAILS_ENV' => 'cucumber' }, :rspec_env => { 'RAILS_ENV' => 'test' }, :test_unit_env => { 'RAILS_ENV' => 'test' } do
90 | # ...
91 | end
92 | ```
93 |
94 | If your application runs on [Heroku](http://www.heroku.com/) or otherwise uses [Foreman](https://github.com/ddollar/foreman), you can provide the `:foreman => true` option to have environment variables present in the `.env` file passed on to the Spork server.
95 |
96 | Available options:
97 |
98 | ``` ruby
99 | :wait => 60 # Seconds to wait for the server to start, default: 30. Setting it to nil will cause it to wait indefinitely.
100 | :retry_delay => 60 # Seconds to wait before retrying booting the server, default: 30. Setting it to nil will cause it to wait indefinitely.
101 | :cucumber => false
102 | :rspec => false
103 | :test_unit => false
104 | :minitest => false
105 | :bundler => false # Don't use "bundle exec"
106 | :test_unit_port => 1233 # Default: 8988
107 | :rspec_port => 1234 # Default: 8989
108 | :cucumber_port => 4321 # Default: 8990
109 | :test_unit_env => { 'RAILS_ENV' => 'baz' } # Default: nil
110 | :rspec_env => { 'RAILS_ENV' => 'foo' } # Default: nil
111 | :cucumber_env => { 'RAILS_ENV' => 'bar' } # Default: nil
112 | :aggressive_kill => false # Default: true, will search Spork pids from `ps aux` and kill them all on start.
113 | :notify_on_start => true # Default: false, will notify as soon as starting begins.
114 | :foreman => true # Default: false, will start Spork through `foreman run` to pick up environment variables used by Foreman. Pass an env file {:env => ".env.test"}
115 | :quiet => true # Default: false, will silence some of the debugging output which can get repetitive (only work with Spork edge at the moment).
116 | ```
117 |
118 | ## Common troubleshooting
119 |
120 | If you can start Spork manually but get the following error message when using Guard::Spork:
121 |
122 | Starting Spork for RSpec ERROR: Could not start Spork for RSpec/Cucumber. Make sure you can use it manually first.
123 |
124 | Try to increase the value of the `:wait => 60` option before any further investigation.
125 | It's possible that this error is the result of an unnecessary /test directory in the root of your application. Removing the /test directory entirely may resolve this error.
126 |
127 | ## Development
128 |
129 | * Source hosted at [GitHub](https://github.com/guard/guard-spork).
130 | * Report issues and feature requests to [GitHub Issues](https://github.com/guard/guard-spork/issues).
131 |
132 | Pull requests are very welcome! Please try to follow these simple "rules", though:
133 |
134 | * Please create a topic branch for every separate change you make.
135 | * Make sure your patches are well tested.
136 | * Update the README (if applicable).
137 | * Please **do not change** the version number.
138 |
139 | For questions please join us on our [Google group](http://groups.google.com/group/guard-dev) or on `#guard` (irc.freenode.net).
140 |
141 | ## Author
142 |
143 | [Thibaud Guillaume-Gentil](https://github.com/thibaudgg)
144 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 | Bundler::GemHelper.install_tasks
3 |
4 | require 'rspec/core/rake_task'
5 | RSpec::Core::RakeTask.new(:spec) do |t|
6 | t.verbose = ENV['CI'] == 'true'
7 | end
8 | task :default => :spec
9 |
10 | namespace :spec do
11 |
12 | desc "Run all specs on multiple ruby versions (requires rvm and bundler)"
13 | task :portability do
14 | %w[1.8.7 1.9.2 ree rbx jruby].each do |version|
15 | system <<-BASH
16 | bash -c 'source ~/.rvm/scripts/rvm;
17 | rvm #{version};
18 | echo "--------- version #{version} ----------\n";
19 | bundle install 1> /dev/null;
20 | rake spec'
21 | BASH
22 | end
23 | end
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/guard-spork.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path('../lib', __FILE__)
3 | require 'guard/spork/version'
4 |
5 | Gem::Specification.new do |s|
6 | s.name = 'guard-spork'
7 | s.version = Guard::SporkVersion::VERSION
8 | s.platform = Gem::Platform::RUBY
9 | s.authors = ['Thibaud Guillaume-Gentil']
10 | s.email = ['thibaud@thibaud.me']
11 | s.homepage = 'http://rubygems.org/gems/guard-spork'
12 | s.summary = 'Guard gem for Spork'
13 | s.description = 'Guard::Spork automatically manage Spork DRb servers.'
14 | s.license = 'MIT'
15 |
16 | s.required_ruby_version = '>= 1.9.3'
17 | s.required_rubygems_version = '>= 1.3.6'
18 | s.rubyforge_project = 'guard-spork'
19 |
20 | s.add_dependency 'guard', '~> 2.0'
21 | s.add_dependency 'guard-compat', '~> 1.0'
22 | s.add_dependency 'spork', '>= 0.8.4'
23 | s.add_dependency 'childprocess', '>= 0.2.3'
24 |
25 | s.add_development_dependency 'bundler', '~> 1.0'
26 | s.add_development_dependency 'rspec', '~> 3.1'
27 | s.add_development_dependency 'transpec'
28 | s.add_development_dependency 'guard-rspec', '~> 4.4'
29 | s.add_development_dependency 'pry', '~> 0.9.12.6'
30 |
31 | s.files = Dir.glob('{lib}/**/*') + %w[LICENSE README.md]
32 | s.require_path = 'lib'
33 | end
34 |
--------------------------------------------------------------------------------
/lib/guard/spork.rb:
--------------------------------------------------------------------------------
1 | require 'guard/compat/plugin'
2 | require 'childprocess'
3 |
4 | module Guard
5 | class Spork < Plugin
6 |
7 | autoload :Runner, 'guard/spork/runner'
8 | autoload :SporkInstance, 'guard/spork/spork_instance'
9 | autoload :SporkWindowsInstance, 'guard/spork/spork_windows_instance'
10 | attr_accessor :runner
11 |
12 | def initialize(options={})
13 | super
14 | @runner = Runner.new(options)
15 | end
16 |
17 | def start
18 | runner.kill_global_sporks
19 | runner.launch_sporks("start")
20 | end
21 |
22 | def reload
23 | relaunch_sporks
24 | end
25 |
26 | def run_on_additions(paths)
27 | relaunch_sporks
28 | end
29 |
30 | def run_on_modifications(paths)
31 | relaunch_sporks
32 | end
33 |
34 | def stop
35 | runner.kill_sporks
36 | end
37 |
38 | private
39 |
40 | def relaunch_sporks
41 | runner.kill_sporks
42 | runner.launch_sporks("reload")
43 | end
44 |
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/guard/spork/rinda_ring_finger_patch.rb:
--------------------------------------------------------------------------------
1 | # Patch for Rinda::RingFinger.primary hanging forever on Ruby 1.9.2 & 1.9.3
2 | # from http://www.ruby-forum.com/topic/4229908
3 | require 'rinda/ring'
4 |
5 | module Rinda
6 | class RingFinger
7 | def lookup_ring_any(timeout=5)
8 | queue = Queue.new
9 |
10 | Thread.new do
11 | self.lookup_ring(timeout) do |ts|
12 | queue.push(ts)
13 | end
14 | queue.push(nil)
15 | end
16 |
17 | @primary = queue.pop
18 | raise('RingNotFound') if @primary.nil?
19 | while it = queue.pop
20 | @rings.push(it)
21 | end
22 |
23 | @primary
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/guard/spork/runner.rb:
--------------------------------------------------------------------------------
1 | require 'socket'
2 |
3 | module Guard
4 | class Spork
5 | class Runner
6 | attr_accessor :options
7 | attr_reader :spork_instances
8 |
9 | def initialize(options={})
10 | options[:wait] ||= 30 # seconds
11 | options[:retry_delay] ||= 2 * options[:wait] # seconds
12 | options[:test_unit_port] ||= 8988
13 | options[:cucumber_port] ||= 8990
14 | options[:rspec_port] ||= 8989
15 | options[:minitest_port] ||= 8988
16 | options[:rspec_env] ||= {}
17 | options[:test_unit_env] ||= {}
18 | options[:cucumber_env] ||= {}
19 | options[:minitest_env] ||= {}
20 | options[:minitest] ||= false
21 | options[:aggressive_kill] = true unless options[:aggressive_kill] == false
22 | options[:foreman] ||= false
23 | options[:quiet] ||= false
24 | @options = options
25 | initialize_spork_instances
26 | end
27 |
28 | def launch_sporks(action, type = nil)
29 | instances = find_instances(type)
30 | Compat::UI.info "#{action.capitalize}ing Spork for #{instances.join(', ')}", :reset => true
31 | if options[:notify_on_start]
32 | Notifier.notify "#{action.capitalize}ing #{instances.join(', ')}", :title => "Spork", :image => :success
33 | end
34 | instances.each(&:start)
35 | verify_launches(action, instances)
36 | end
37 |
38 | def kill_sporks(type = nil)
39 | alive = find_instances(type).select(&:alive?)
40 | Compat::UI.debug "Killing Spork servers with PID: #{alive.map(&:pid).join(', ')}"
41 | alive.each(&:stop)
42 | end
43 |
44 | def kill_global_sporks
45 | if options[:aggressive_kill]
46 | kill_pids self.class.spork_instance_class.spork_pids
47 | end
48 | end
49 |
50 | def self.windows?
51 | ENV['OS'] == 'Windows_NT'
52 | end
53 |
54 | private
55 |
56 | def initialize_spork_instances
57 | @spork_instances = []
58 | [:rspec, :cucumber, :test_unit, :minitest].each do |type|
59 | port, env = options[:"#{type}_port"], options[:"#{type}_env"]
60 | spork_instances << self.class.spork_instance_class.new(type, port, env, :bundler => should_use?(:bundler), :foreman => should_use?(:foreman), :quiet => should_use?(:quiet)) if should_use?(type)
61 | end
62 | end
63 |
64 | def self.spork_instance_class
65 | windows? ? SporkWindowsInstance : SporkInstance
66 | end
67 |
68 | def kill_pids(pids)
69 | Compat::UI.debug "Killing Spork servers with PID: #{pids.join(', ')}"
70 | pids.each { |pid| ::Process.kill("KILL", pid) rescue nil }
71 | end
72 |
73 | def find_instances(type = nil)
74 | if type.nil?
75 | spork_instances
76 | else
77 | spork_instances.select { |instance| instance.type == type }
78 | end
79 | end
80 |
81 | def verify_launches(action, instances)
82 | start_time = Time.now
83 | names = instances.join(', ')
84 |
85 | if wait_for_launch(instances, options[:wait])
86 | Compat::UI.info "Spork server for #{names} successfully #{action}ed", :reset => true
87 | Compat::UI.notify "#{names} successfully #{action}ed", :title => "Spork", :image => :success
88 | else
89 | Compat::UI.error "Could not #{action} Spork server for #{names} after #{options[:wait]} seconds. I will continue waiting for a further #{options[:retry_delay]} seconds."
90 | Compat::UI.notify "#{names} NOT #{action}ed. Continuing to wait for #{options[:retry_delay]} seconds.", :title => "Spork", :image => :failed
91 | if wait_for_launch(instances, options[:retry_delay])
92 | total_time = Time.now - start_time
93 | UI.info "Spork server for #{names} eventually #{action}ed after #{total_time.to_i} seconds. Consider adjusting your :wait option beyond this time.", :reset => true
94 | Notifier.notify "#{names} eventually #{action}ed after #{total_time.to_i} seconds", :title => "Spork", :image => :success
95 | else
96 | Compat::UI.error "Could not #{action} Spork server for #{names}. Make sure you can use it manually first."
97 | Compat::UI.notify "#{names} NOT #{action}ed", :title => "Spork", :image => :failed
98 | throw :task_has_failed
99 | end
100 | end
101 | end
102 |
103 | def wait_for_launch(instances, wait)
104 | not_running = instances.dup
105 | wait_or_loop(wait) do
106 | sleep 1
107 | not_running.delete_if { |instance| instance.running? }
108 | return true if not_running.empty?
109 | end
110 | end
111 |
112 | def should_use?(what)
113 | options[what].nil? ? send("detect_#{what}") : options[what]
114 | end
115 |
116 | def wait_or_loop(wait)
117 | if wait
118 | wait.times { yield }
119 | else
120 | loop { yield }
121 | end
122 | false
123 | end
124 |
125 | def detect_bundler
126 | File.exist?("Gemfile")
127 | end
128 |
129 | def detect_test_unit
130 | File.exist?("test/test_helper.rb")
131 | end
132 |
133 | def detect_rspec
134 | File.exist?("spec") && (options[:minitest].nil? || !options[:minitest])
135 | end
136 |
137 | def detect_minitest
138 | false
139 | end
140 |
141 | def detect_cucumber
142 | File.exist?("features")
143 | end
144 |
145 | def detect_foreman
146 | File.exist?("Procfile")
147 | end
148 |
149 | end
150 | end
151 | end
152 |
--------------------------------------------------------------------------------
/lib/guard/spork/spork_instance.rb:
--------------------------------------------------------------------------------
1 | require 'socket'
2 |
3 | module Guard
4 | class Spork
5 | class SporkInstance
6 | attr_reader :type, :env, :port, :options, :pid, :process
7 |
8 | def initialize(type, port, env, options)
9 | @type = type
10 | @port = port
11 | @env = env
12 | @options = options
13 | end
14 |
15 | def to_s
16 | case type
17 | when :rspec
18 | "RSpec"
19 | when :cucumber
20 | "Cucumber"
21 | when :test_unit
22 | "Test::Unit"
23 | when :minitest
24 | "MiniTest"
25 | else
26 | type.to_s
27 | end
28 | end
29 |
30 | def start
31 | executable, *cmd = command
32 |
33 | Compat::UI.debug "guard-spork command execution: #{cmd}"
34 |
35 | @process = ChildProcess.build(executable, *cmd)
36 | @process.environment.merge!(env) unless env.empty?
37 | @process.io.inherit!
38 | @process.start
39 | @pid = @process.pid
40 | end
41 |
42 | def stop
43 | process.stop
44 | end
45 |
46 | def alive?
47 | pid && process.alive?
48 | end
49 |
50 | def running?
51 | return false unless alive?
52 | TCPSocket.new('127.0.0.1', port).close
53 | true
54 | rescue Errno::ECONNREFUSED
55 | false
56 | end
57 |
58 | def command
59 | parts = []
60 | if use_bundler?
61 | parts << "bundle"
62 | parts << "exec"
63 | end
64 | if use_foreman?
65 | parts << "foreman"
66 | parts << "run"
67 | end
68 | parts << "spork"
69 |
70 | if type == :test_unit
71 | parts << "testunit"
72 | elsif type == :cucumber
73 | parts << "cu"
74 | elsif type == :minitest
75 | parts << "minitest"
76 | end
77 |
78 | parts << "-p"
79 | parts << port.to_s
80 | parts << "-q" if options[:quiet]
81 |
82 | if use_foreman?
83 | parts << "-e=#{options[:foreman].fetch(:env, '.env')}" if foreman_options?
84 | end
85 |
86 | parts
87 | end
88 |
89 | def self.spork_pids
90 | `ps aux | grep -v guard | awk '/spork/&&!/awk/{print $2;}'`.split("\n").map { |pid| pid.to_i }
91 | end
92 |
93 | private
94 |
95 | def use_bundler?
96 | options[:bundler]
97 | end
98 |
99 | def use_foreman?
100 | options[:foreman]
101 | end
102 |
103 | def foreman_options?
104 | options[:foreman].is_a?(Hash)
105 | end
106 |
107 | end
108 | end
109 | end
110 |
--------------------------------------------------------------------------------
/lib/guard/spork/spork_windows_instance.rb:
--------------------------------------------------------------------------------
1 | require 'rinda/ring'
2 | require 'guard/spork/rinda_ring_finger_patch'
3 |
4 | module Guard
5 | class Spork
6 | class SporkWindowsInstance < SporkInstance
7 | def command
8 | ["cmd", "/C"] + super
9 | end
10 |
11 | def stop
12 | kill_all_spork_processes
13 | end
14 |
15 | def running?
16 | super && drb_ready?
17 | end
18 |
19 | def self.spork_pids
20 | spork_processes.map { |process| process[:pid] }
21 | end
22 |
23 | private
24 |
25 | def drb_ready?
26 | DRb.start_service
27 | # make sure that ringfinger is not taken from cache, because it won't
28 | # work after guard-spork has been restarted
29 | Rinda::RingFinger.class_variable_set :@@finger, nil
30 | ts = Rinda::RingFinger.primary
31 | ts.read_all([:name, :MagazineSlave, nil, nil]).size > 0
32 | rescue
33 | false
34 | end
35 |
36 | def kill_all_spork_processes
37 | all_pids_for(pid, self.class.spork_processes).each do |pid|
38 | Process.kill 9, pid rescue nil
39 | end
40 | end
41 |
42 | def all_pids_for(parent_pid, processes)
43 | processes.inject([parent_pid]) do |memo, process|
44 | memo += all_pids_for(process[:pid], processes) if process[:ppid] == parent_pid
45 | memo
46 | end
47 | end
48 |
49 | def self.spork_processes
50 | require "win32ole"
51 | WIN32OLE.connect("winmgmts://.").InstancesOf("win32_process").
52 | each.
53 | select do |p|
54 | p.commandline =~ /spork|ring_server|magazine_slave_provider/ &&
55 | File.basename(p.executablepath, File.extname(p.executablepath)) =~ /^(cmd|ruby)$/i
56 | end.
57 | map { |p| {:pid => p.processid, :ppid => p.parentprocessid} }
58 | end
59 |
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/lib/guard/spork/templates/Guardfile:
--------------------------------------------------------------------------------
1 | guard :spork, :cucumber_env => { 'RAILS_ENV' => 'test' }, :rspec_env => { 'RAILS_ENV' => 'test' } do
2 | watch('config/application.rb')
3 | watch('config/environment.rb')
4 | watch('config/environments/test.rb')
5 | watch(%r{^config/initializers/.+\.rb$})
6 | watch('Gemfile.lock')
7 | watch('spec/spec_helper.rb') { :rspec }
8 | watch('test/test_helper.rb') { :test_unit }
9 | watch(%r{features/support/}) { :cucumber }
10 | end
11 |
--------------------------------------------------------------------------------
/lib/guard/spork/version.rb:
--------------------------------------------------------------------------------
1 | module Guard
2 | module SporkVersion
3 | VERSION = "2.1.0"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/guard/spork/runner_spec.rb:
--------------------------------------------------------------------------------
1 | require 'timeout'
2 |
3 | require "guard/compat/test/helper"
4 | require "guard/spork"
5 |
6 | RSpec.describe Guard::Spork::Runner do
7 | let(:runner) { Guard::Spork::Runner.new }
8 |
9 | describe "default options" do
10 | subject { Guard::Spork::Runner.new.options }
11 |
12 | it { is_expected.to include(:wait => 30) }
13 | it { is_expected.to include(:cucumber_port => 8990) }
14 | it { is_expected.to include(:rspec_port => 8989) }
15 | it { is_expected.to include(:test_unit_port => 8988) }
16 | it { is_expected.to include(:test_unit_env => {}) }
17 | it { is_expected.to include(:rspec_env => {}) }
18 | it { is_expected.to include(:minitest => false) }
19 | it { is_expected.to include(:cucumber_env => {}) }
20 | it { is_expected.to include(:aggressive_kill => true) }
21 | it { is_expected.to include(:foreman => false) }
22 | it { is_expected.to include(:quiet => false) }
23 | end
24 |
25 | before(:each) do
26 | allow_any_instance_of(Guard::Spork::SporkInstance).to receive(:start)
27 | allow(Guard::Compat::UI).to receive(:info)
28 | allow(Guard::Compat::UI).to receive(:error)
29 | allow(Guard::Compat::UI).to receive(:notify)
30 | allow(Guard::Compat::UI).to receive(:debug)
31 | end
32 |
33 | describe "(spork detection)" do
34 | def file_existance(files)
35 | allow(File).to receive(:exist?).and_raise { |name| "Unexpected file passed: #{name}" }
36 | files.each_pair do |file, existance|
37 | allow(File).to receive(:exist?).with(file).and_return(existance)
38 | end
39 | end
40 |
41 | def instance(type, runner = runner)
42 | runner.spork_instances.find { |instance| instance.type == type }
43 | end
44 |
45 | def spork_instance_class
46 | Guard::Spork::Runner.send :spork_instance_class
47 | end
48 |
49 | it "has a spork instance for :rspec when configured" do
50 | runner = Guard::Spork::Runner.new({
51 | :rspec => true,
52 | :rspec_port => 2,
53 | :rspec_env => {'spec' => 'yes'},
54 | })
55 |
56 | instance(:rspec, runner).tap do |instance|
57 | expect(instance.port).to eq(2)
58 | expect(instance.env).to eq({'spec' => 'yes'})
59 | end
60 | end
61 |
62 | it "has a spork instance for :cucumber when configured" do
63 | runner = Guard::Spork::Runner.new({
64 | :cucumber => true,
65 | :cucumber_port => 2,
66 | :cucumber_env => {'cuke' => 'yes'},
67 | })
68 |
69 | instance(:cucumber, runner).tap do |instance|
70 | expect(instance.port).to eq(2)
71 | expect(instance.env).to eq({'cuke' => 'yes'})
72 | end
73 | end
74 |
75 | it "has a spork instance for :test_unit when configured" do
76 | runner = Guard::Spork::Runner.new({
77 | :test_unit => true,
78 | :test_unit_port => 2,
79 | :test_unit_env => {'unit' => 'yes'},
80 | })
81 |
82 | instance(:test_unit, runner).tap do |instance|
83 | expect(instance.port).to eq(2)
84 | expect(instance.env).to eq({'unit' => 'yes'})
85 | end
86 | end
87 |
88 | it "has a spork instance for :minitest when configured" do
89 | runner = Guard::Spork::Runner.new({
90 | :minitest => true,
91 | :minitest_port => 2,
92 | :minitest_env => {'minitest' => 'yes'},
93 | })
94 |
95 | instance(:minitest, runner).tap do |instance|
96 | expect(instance.port).to eq(2)
97 | expect(instance.env).to eq({'minitest' => 'yes'})
98 | end
99 | end
100 |
101 |
102 | context "with Test::Unit only" do
103 | before(:each) do
104 | file_existance({
105 | 'test/test_helper.rb' => true,
106 | 'spec' => false,
107 | 'features' => false,
108 | 'Gemfile' => false,
109 | })
110 | end
111 |
112 | it "has a spork instance for :test_unit" do
113 | expect(instance(:test_unit)).to be_instance_of(spork_instance_class)
114 | end
115 |
116 | it "does not have bundler enabled for the test_unit instance" do
117 | expect(instance(:test_unit).options).to include(:bundler => false)
118 | end
119 |
120 | it "does not have a spork instance for :rspec" do
121 | expect(instance(:rspec)).to be_nil
122 | end
123 |
124 | it "does not have a spork instance for :cucumber" do
125 | expect(instance(:cucumber)).to be_nil
126 | end
127 | end
128 |
129 | context "with MiniTest only" do
130 | before(:each) do
131 | file_existance({
132 | 'test/test_helper.rb' => true,
133 | 'spec' => true,
134 | 'features' => false,
135 | 'Gemfile' => false,
136 | })
137 |
138 | @runner = Guard::Spork::Runner.new({
139 | :minitest => true,
140 | :minitest_port => 2,
141 | :minitest_env => {'minitest' => 'yes'},
142 | })
143 |
144 | end
145 |
146 | it "has a spork instance for :test_unit" do
147 | expect(instance(:minitest, @runner)).to be_instance_of(spork_instance_class)
148 | end
149 |
150 | it "does not have bundler enabled for the test_unit instance" do
151 | expect(instance(:minitest, @runner).options).to include(:bundler => false)
152 | end
153 |
154 | it "does not have a spork instance for :rspec" do
155 | expect(instance(:rspec, @runner)).to be_nil
156 | end
157 |
158 | it "does not have a spork instance for :cucumber" do
159 | expect(instance(:cucumber, @runner)).to be_nil
160 | end
161 | end
162 |
163 | context "with RSpec only" do
164 | before(:each) do
165 | file_existance({
166 | 'test/test_helper.rb' => false,
167 | 'spec' => true,
168 | 'features' => false,
169 | 'Gemfile' => false,
170 | })
171 | end
172 |
173 | it "has a spork instance for :rspec" do
174 | expect(instance(:rspec)).to be_instance_of(spork_instance_class)
175 | end
176 |
177 | it "does not have bundler enabled for the rspec instance" do
178 | expect(instance(:rspec).options).to include(:bundler => false)
179 | end
180 |
181 | it "does not have a spork instance for :test_unit" do
182 | expect(instance(:test_unit)).to be_nil
183 | end
184 |
185 | it "does not have a spork instance for :cucumber" do
186 | expect(instance(:cucumber)).to be_nil
187 | end
188 | end
189 |
190 | context "with Cucumber only" do
191 | before(:each) do
192 | file_existance({
193 | 'test/test_helper.rb' => false,
194 | 'spec' => false,
195 | 'features' => true,
196 | 'Gemfile' => false,
197 | })
198 | end
199 |
200 | it "has a spork instance for :cucumber" do
201 | expect(instance(:cucumber)).to be_instance_of(spork_instance_class)
202 | end
203 |
204 | it "does not have bundler enabled for the cucumber instance" do
205 | expect(instance(:cucumber).options).to include(:bundler => false)
206 | end
207 |
208 | it "does not have a spork instance for :test_unit" do
209 | expect(instance(:test_unit)).to be_nil
210 | end
211 |
212 | it "does not have a spork instance for :rspec" do
213 | expect(instance(:rspec)).to be_nil
214 | end
215 |
216 | it "does not have a spork instance for :minitest" do
217 | expect(instance(:minitest)).to be_nil
218 | end
219 |
220 | end
221 |
222 | context "with RSpec, Cucumber and Bundler" do
223 | before(:each) do
224 | file_existance({
225 | 'test/test_helper.rb' => false,
226 | 'spec' => true,
227 | 'features' => true,
228 | 'Gemfile' => true,
229 | })
230 | end
231 |
232 | it "has a spork instance for :rspec" do
233 | expect(instance(:rspec)).to be_instance_of(spork_instance_class)
234 | end
235 |
236 | it "has a spork instance for :cucumber" do
237 | expect(instance(:cucumber)).to be_instance_of(spork_instance_class)
238 | end
239 |
240 | it "has bundler enabled for the rspec instance" do
241 | expect(instance(:rspec).options).to include(:bundler => true)
242 | end
243 |
244 | it "has bundler enabled for the cucumber instance" do
245 | expect(instance(:cucumber).options).to include(:bundler => true)
246 | end
247 |
248 | it "does not have a spork instance for :test_unit" do
249 | expect(instance(:test_unit)).to be_nil
250 | end
251 | end
252 |
253 | context ".windows?" do
254 | describe Guard::Spork::SporkInstance do
255 | before { allow(runner.class).to receive_messages(:windows? => false) }
256 |
257 | it "is created when not Windows OS is used" do
258 | expect(instance(:rspec, Guard::Spork::Runner.new)).to be_instance_of(Guard::Spork::SporkInstance)
259 | end
260 | end
261 |
262 | describe Guard::Spork::SporkWindowsInstance do
263 | before { allow(runner.class).to receive_messages(:windows? => true) }
264 |
265 | it "is created when Windows OS is used" do
266 | expect(instance(:rspec, Guard::Spork::Runner.new)).to be_instance_of(Guard::Spork::SporkWindowsInstance)
267 | end
268 | end
269 | end
270 | end
271 |
272 | describe "#launch_sporks(action, type)" do
273 | let(:rspec_instance) { fake_instance(:rspec) }
274 | let(:cucumber_instance) { fake_instance(:cucumber) }
275 |
276 | def fake_instance(type)
277 | fake = Object.new
278 | fake.instance_eval do
279 | def start() nil end
280 | def running?() true end
281 | def type() @type end
282 | def to_s() type.to_s end
283 | def inspect() to_s end
284 | end
285 | fake.instance_variable_set('@type', type)
286 | fake
287 | end
288 |
289 | around(:each) do |example|
290 | Timeout.timeout(2) { example.run }
291 | end
292 |
293 | before(:each) do
294 | allow(runner).to receive_messages(:spork_instances => [rspec_instance, cucumber_instance])
295 | allow(runner).to receive(:sleep)
296 | end
297 |
298 | context "with no type specified" do
299 | it "outputs an info message" do
300 | allow(runner).to receive_messages(:spork_instances => [
301 | fake_instance("one"),
302 | fake_instance("two"),
303 | fake_instance("three"),
304 | ])
305 | expect(Guard::Compat::UI).to receive(:info).with("Kissing Spork for one, two, three", :reset => true)
306 | runner.launch_sporks("kiss")
307 | end
308 |
309 | it "starts all spork instances" do
310 | expect(rspec_instance).to receive(:start)
311 | expect(cucumber_instance).to receive(:start)
312 | runner.launch_sporks("")
313 | end
314 |
315 | it "waits for the spork instances to start" do
316 | expect(rspec_instance).to receive(:running?).and_return(false, false, false, true)
317 | allow(cucumber_instance).to receive_messages(:running? => true)
318 | expect(runner).to receive(:sleep).with(1).exactly(4).times
319 |
320 | runner.launch_sporks("")
321 | end
322 |
323 | # This behavior is a bit weird, isn't it?
324 | it "does not wait longer than the configured wait duration + 60" do
325 | runner.options[:wait] = 7
326 | expect(runner).to receive(:sleep).with(1).exactly(67).times
327 | allow(rspec_instance).to receive_messages(:running? => false)
328 | allow(cucumber_instance).to receive_messages(:running? => true)
329 |
330 | expect {
331 | runner.launch_sporks("")
332 | }.to throw_symbol(:task_has_failed)
333 | end
334 |
335 | it "does not wait longer than the configured wait duration + retry_delay" do
336 | runner.options[:wait] = 7
337 | runner.options[:retry_delay] = 10
338 | expect(runner).to receive(:sleep).with(1).exactly(17).times
339 | allow(rspec_instance).to receive_messages(:running? => false)
340 | allow(cucumber_instance).to receive_messages(:running? => true)
341 |
342 | expect {
343 | runner.launch_sporks("")
344 | }.to throw_symbol(:task_has_failed)
345 | end
346 |
347 | context "when :wait is nil" do
348 | it "does not time out" do
349 | runner.options[:wait] = nil
350 | allow(rspec_instance).to receive_messages(:running? => false)
351 | allow(cucumber_instance).to receive_messages(:running? => true)
352 |
353 | expect {
354 | runner.launch_sporks("")
355 | }.not_to throw_symbol(:task_has_failed)
356 | end
357 | end
358 | end
359 |
360 | context "with a type specified" do
361 | it "outputs an info message" do
362 | allow(runner).to receive_messages(:spork_instances => [
363 | fake_instance("one"),
364 | fake_instance("two"),
365 | fake_instance("three"),
366 | ])
367 | expect(Guard::Compat::UI).to receive(:info).with("Kissing Spork for two", :reset => true)
368 | runner.launch_sporks("kiss", "two")
369 | end
370 |
371 | it "starts the matching spork instance" do
372 | expect(rspec_instance).to receive(:start)
373 | expect(cucumber_instance).not_to receive(:start)
374 | runner.launch_sporks("", :rspec)
375 | end
376 |
377 | it "waits for the spork instances to start" do
378 | expect(rspec_instance).to receive(:running?).and_return(false, false, false, true)
379 | expect(runner).to receive(:sleep).with(1).exactly(4).times
380 |
381 | expect(cucumber_instance).not_to receive(:running?)
382 | runner.launch_sporks("", :rspec)
383 | end
384 |
385 | # This behavior is a bit weird, isn't it?
386 | it "does not wait longer than the configured wait duration + 60" do
387 | runner.options[:wait] = 7
388 | expect(runner).to receive(:sleep).with(1).exactly(67).times
389 | allow(rspec_instance).to receive_messages(:running? => false)
390 |
391 | expect(cucumber_instance).not_to receive(:running?)
392 | expect {
393 | runner.launch_sporks("", :rspec)
394 | }.to throw_symbol(:task_has_failed)
395 | end
396 |
397 | # This behavior is a bit weird, isn't it?
398 | it "does not wait longer than the configured wait duration + retry_delay" do
399 | runner.options[:wait] = 7
400 | runner.options[:retry_delay] = 10
401 | expect(runner).to receive(:sleep).with(1).exactly(17).times
402 | allow(rspec_instance).to receive_messages(:running? => false)
403 |
404 | expect(cucumber_instance).not_to receive(:running?)
405 | expect {
406 | runner.launch_sporks("", :rspec)
407 | }.to throw_symbol(:task_has_failed)
408 | end
409 |
410 | context "when :wait is nil" do
411 | it "does not time out" do
412 | runner.options[:wait] = nil
413 | allow(rspec_instance).to receive_messages(:running? => false)
414 |
415 | expect(cucumber_instance).not_to receive(:running?)
416 | expect {
417 | runner.launch_sporks("", :rspec)
418 | }.not_to throw_symbol(:task_has_failed)
419 | end
420 | end
421 | end
422 | end
423 |
424 | describe "#kill_sporks(type)" do
425 | context "without a type" do
426 | it "kills all alive spork instances" do
427 | alive = double("alive instance", :alive? => true, :pid => 111)
428 | dead = double("dead instance", :alive? => false, :pid => 222)
429 | allow(runner).to receive_messages(:spork_instances => [alive, dead])
430 |
431 | expect(Guard::Compat::UI).to receive(:debug).with(/111/)
432 | expect(alive).to receive(:stop)
433 | expect(dead).not_to receive(:stop)
434 |
435 | runner.kill_sporks
436 | end
437 | end
438 |
439 | context "with a given type" do
440 | it "kills the matching spork instance" do
441 | matching = double("alive instance", :alive? => true, :pid => 111, :type => :matching)
442 | other = double("dead instance", :alive? => true, :pid => 222, :type => :other)
443 | allow(runner).to receive_messages(:spork_instances => [matching, other])
444 |
445 | expect(Guard::Compat::UI).to receive(:debug).with(/111/)
446 | expect(matching).to receive(:stop)
447 | expect(other).not_to receive(:stop)
448 |
449 | runner.kill_sporks(:matching)
450 | end
451 | end
452 | end
453 |
454 | describe "#kill_global_sporks" do
455 | context "when configured to do aggressive killing" do
456 | before(:each) { runner.options[:aggressive_kill] = true }
457 |
458 | it "calls #kill_pids" do
459 | expect(runner).to receive(:kill_pids)
460 | runner.kill_global_sporks
461 | end
462 |
463 | it "calls a KILL command for each Spork server running on the system" do
464 | expect(runner.class.spork_instance_class).to receive(:spork_pids).and_return([666, 999])
465 |
466 | expect(Guard::Compat::UI).to receive(:debug).with('Killing Spork servers with PID: 666, 999')
467 | expect(Process).to receive(:kill).with('KILL', 666)
468 | expect(Process).to receive(:kill).with('KILL', 999)
469 |
470 | runner.kill_global_sporks
471 | end
472 | end
473 |
474 | context "when configured to not do aggressive killing" do
475 | before(:each) { runner.options[:aggressive_kill] = false }
476 |
477 | it "does not call #kill_pids" do
478 | expect(runner).not_to receive(:kill_pids)
479 | runner.kill_global_sporks
480 | end
481 | end
482 | end
483 |
484 | describe "#should_use?(what)" do
485 | subject {runner.send(:should_use?, :bundler)}
486 |
487 | context "with the detection succeeding" do
488 | before(:each) { allow(runner).to receive_messages(:detect_bundler => true) }
489 | # Not sure this is the best way of testing this, but since the behavior is the same regardless of the argument...
490 |
491 | context "with no option specified" do
492 | it {is_expected.to be_truthy}
493 | end
494 |
495 | context "with an option set to false" do
496 | before(:each) {runner.options[:bundler] = false}
497 | it {is_expected.to be_falsey}
498 | end
499 |
500 | context "with an option set to true" do
501 | before(:each) {runner.options[:bundler] = true}
502 | it {is_expected.to be_truthy}
503 | end
504 | end
505 |
506 | context "with the detection failing" do
507 | before(:each) { allow(runner).to receive_messages(:detect_minitest => false) }
508 |
509 | # Not sure this is the best way of testing this, but since the behavior is the same regardless of the argument...
510 | subject {runner.send(:should_use?, :minitest)}
511 | context "with no option specified" do
512 | it {is_expected.to be_falsey}
513 | end
514 |
515 | context "with an option set to false" do
516 | before(:each) {runner.options[:minitest] = false}
517 | it {is_expected.to be_falsey}
518 | end
519 |
520 | context "with an option set to true" do
521 | before(:each) {runner.options[:minitest] = true}
522 | it {is_expected.to be_truthy}
523 | end
524 | end
525 | end
526 |
527 | end
528 |
--------------------------------------------------------------------------------
/spec/guard/spork/spork_instance_spec.rb:
--------------------------------------------------------------------------------
1 | require "guard/spork"
2 |
3 | module Guard
4 | class Spork
5 | RSpec.describe SporkInstance do
6 | it "remembers instances" do
7 | SporkInstance.new('type', 0, {}, {})
8 | end
9 |
10 | describe "rspec on port 1337" do
11 | let(:options) { Hash.new }
12 | subject { SporkInstance.new(:rspec, 1337, {}, options) }
13 |
14 | describe '#command' do
15 | subject { super().command }
16 | it { is_expected.to eq(%w{spork -p 1337}) }
17 | end
18 |
19 | describe '#port' do
20 | subject { super().port }
21 | it { is_expected.to eq(1337) }
22 | end
23 |
24 | describe '#type' do
25 | subject { super().type }
26 | it { is_expected.to eq(:rspec) }
27 | end
28 |
29 | describe '#to_s' do
30 | subject { super().to_s }
31 | it { is_expected.to eq("RSpec") }
32 | end
33 |
34 | context "with bundler enabled" do
35 | let(:options) { {:bundler => true} }
36 |
37 | describe '#command' do
38 | subject { super().command }
39 | it { is_expected.to eq(%w{bundle exec spork -p 1337}) }
40 | end
41 | end
42 |
43 | context "with foreman enabled" do
44 | let(:options) { { :foreman => true, :bundler => true } }
45 |
46 | describe '#command' do
47 | subject { super().command }
48 | it { is_expected.to eq(%w{bundle exec foreman run spork -p 1337}) }
49 | end
50 | end
51 |
52 | context "with quiet enabled" do
53 | let(:options) { { :quiet => true } }
54 |
55 | describe '#command' do
56 | subject { super().command }
57 | it { is_expected.to eq(%w{spork -p 1337 -q}) }
58 | end
59 | end
60 | end
61 |
62 | describe "cucumber on port 1337" do
63 | let(:options) { Hash.new }
64 | subject { SporkInstance.new(:cucumber, 1337, {}, options) }
65 |
66 | describe '#command' do
67 | subject { super().command }
68 | it { is_expected.to eq(%w{spork cu -p 1337}) }
69 | end
70 |
71 | describe '#port' do
72 | subject { super().port }
73 | it { is_expected.to eq(1337) }
74 | end
75 |
76 | describe '#type' do
77 | subject { super().type }
78 | it { is_expected.to eq(:cucumber) }
79 | end
80 |
81 | describe '#to_s' do
82 | subject { super().to_s }
83 | it { is_expected.to eq("Cucumber") }
84 | end
85 |
86 | context "with bundler enabled" do
87 | let(:options) { {:bundler => true} }
88 |
89 | describe '#command' do
90 | subject { super().command }
91 | it { is_expected.to eq(%w{bundle exec spork cu -p 1337}) }
92 | end
93 | end
94 |
95 | context "with foreman enabled" do
96 | let(:options) { { :foreman => true, :bundler => true } }
97 |
98 | describe '#command' do
99 | subject { super().command }
100 | it { is_expected.to eq(%w{bundle exec foreman run spork cu -p 1337}) }
101 | end
102 | end
103 |
104 | context "with foreman enabled and env name option" do
105 | let(:options) { { :foreman => { :env => ".env.test" }, :bundler => true } }
106 |
107 | describe '#command' do
108 | subject { super().command }
109 | it { is_expected.to eq(%w{bundle exec foreman run spork cu -p 1337 -e=.env.test})}
110 | end
111 | end
112 | end
113 |
114 | describe "test_unit on port 1337" do
115 | let(:options) { Hash.new }
116 | subject { SporkInstance.new(:test_unit, 1337, {}, options) }
117 |
118 | describe '#command' do
119 | subject { super().command }
120 | it { is_expected.to eq(%w{spork testunit -p 1337}) }
121 | end
122 |
123 | describe '#port' do
124 | subject { super().port }
125 | it { is_expected.to eq(1337) }
126 | end
127 |
128 | describe '#type' do
129 | subject { super().type }
130 | it { is_expected.to eq(:test_unit) }
131 | end
132 |
133 | describe '#to_s' do
134 | subject { super().to_s }
135 | it { is_expected.to eq("Test::Unit") }
136 | end
137 |
138 | context "with bundler enabled" do
139 | let(:options) { {:bundler => true} }
140 |
141 | describe '#command' do
142 | subject { super().command }
143 | it { is_expected.to eq(%w{bundle exec spork testunit -p 1337}) }
144 | end
145 | end
146 |
147 | context "with foreman enabled" do
148 | let(:options) { { :foreman => true, :bundler => true } }
149 |
150 | describe '#command' do
151 | subject { super().command }
152 | it { is_expected.to eq(%w{bundle exec foreman run spork testunit -p 1337}) }
153 | end
154 | end
155 |
156 | context "with foreman enabled and env name option" do
157 | let(:options) { { :foreman => { :env => ".env.test" }, :bundler => true } }
158 |
159 | describe '#command' do
160 | subject { super().command }
161 | it { is_expected.to eq(%w{bundle exec foreman run spork testunit -p 1337 -e=.env.test})}
162 | end
163 | end
164 | end
165 |
166 | describe "minitest on port 1338" do
167 | let(:options) { Hash.new }
168 | subject { SporkInstance.new(:minitest, 1338, {}, options) }
169 |
170 | describe '#command' do
171 | subject { super().command }
172 | it { is_expected.to eq(%w{spork minitest -p 1338}) }
173 | end
174 |
175 | describe '#port' do
176 | subject { super().port }
177 | it { is_expected.to eq(1338) }
178 | end
179 |
180 | describe '#type' do
181 | subject { super().type }
182 | it { is_expected.to eq(:minitest) }
183 | end
184 |
185 | describe '#to_s' do
186 | subject { super().to_s }
187 | it { is_expected.to eq("MiniTest") }
188 | end
189 |
190 | context "with bundler enabled" do
191 | let(:options) { {:bundler => true} }
192 |
193 | describe '#command' do
194 | subject { super().command }
195 | it { is_expected.to eq(%w{bundle exec spork minitest -p 1338}) }
196 | end
197 | end
198 |
199 | context "with foreman enabled" do
200 | let(:options) { { :foreman => true, :bundler => true } }
201 |
202 | describe '#command' do
203 | subject { super().command }
204 | it { is_expected.to eq(%w{bundle exec foreman run spork minitest -p 1338})}
205 | end
206 | end
207 |
208 | context "with foreman enabled and env name option" do
209 | let(:options) { { :foreman => { :env => ".env.test" }, :bundler => true } }
210 |
211 | describe '#command' do
212 | subject { super().command }
213 | it { is_expected.to eq(%w{bundle exec foreman run spork minitest -p 1338 -e=.env.test})}
214 | end
215 | end
216 | end
217 |
218 | end
219 |
220 | RSpec.describe SporkInstance, "spawning" do
221 | let(:instance) { SporkInstance.new(:test, 1, {}, {}) }
222 | before(:each) do
223 | allow(instance).to receive_messages(:command => "")
224 | allow(Guard::Compat::UI).to receive(:debug)
225 | end
226 |
227 | describe "#start" do
228 | after(:each) { ENV.delete('SPORK_PIDS') }
229 |
230 | it "uses ChildProcess and stores the pid" do
231 | process = double("process").as_null_object
232 | expect(ChildProcess).to receive(:build).and_return(process)
233 | allow(process).to receive_messages(:pid => "a pid")
234 | expect {
235 | instance.start
236 | }.to change(instance, :pid).from(nil).to("a pid")
237 | end
238 |
239 | it "passes environment to the ChildProcess" do
240 | allow(instance).to receive_messages(:command => "command", :env => {:environment => true})
241 | process = double("process").as_null_object
242 | expect(ChildProcess).to receive(:build).and_return(process)
243 | process_env = {}
244 | expect(process).to receive(:environment).and_return(process_env)
245 | expect(process_env).to receive(:merge!).with(:environment => true)
246 | instance.start
247 | end
248 | end
249 |
250 | describe "#stop" do
251 | it "delegates to ChildProcess#stop" do
252 | process = double("a process")
253 | allow(instance).to receive(:process).and_return(process)
254 | expect(process).to receive(:stop)
255 | instance.stop
256 | end
257 | end
258 |
259 | describe "(alive)" do
260 | subject { instance }
261 | before(:each) do
262 | allow(instance).to receive_messages(:pid => nil)
263 | end
264 |
265 | context "when no pid is set" do
266 | it { is_expected.not_to be_alive }
267 | end
268 |
269 | context "when the pid is a running process" do
270 | before(:each) do
271 | allow(instance).to receive_messages(:pid => 42)
272 | process = double("a process")
273 | allow(instance).to receive_messages(:process => process)
274 | allow(process).to receive_messages(:alive? => true)
275 | end
276 |
277 | it { is_expected.to be_alive }
278 | end
279 |
280 | context "when the pid is a stopped process" do
281 | subject { instance }
282 | before(:each) do
283 | allow(instance).to receive_messages(:pid => 42)
284 | process = double("a process")
285 | allow(instance).to receive_messages(:process => process)
286 | allow(process).to receive_messages(:alive? => false)
287 | end
288 |
289 | it { is_expected.not_to be_alive }
290 | end
291 | end
292 |
293 | describe "(running)" do
294 | let(:socket) { double(:close => nil) }
295 | subject { instance }
296 |
297 | before(:each) do
298 | allow(instance).to receive_messages(:pid => 42, :port => 1337)
299 | allow(TCPSocket).to receive_messages(:new => socket)
300 | end
301 |
302 | context "when no pid is specified" do
303 | before(:each) { allow(instance).to receive_messages(:pid => nil) }
304 | it { is_expected.not_to be_running }
305 | end
306 |
307 | context "when process is not alive" do
308 | before(:each) { allow(instance).to receive_messages(:alive? => false)}
309 | it { is_expected.not_to be_running }
310 | end
311 |
312 | context "when spork does not respond" do
313 | before(:each) do
314 | expect(TCPSocket).to receive(:new).with('127.0.0.1', 1337).and_raise(Errno::ECONNREFUSED)
315 | allow(instance).to receive_messages(:alive? => true)
316 | end
317 |
318 | it { is_expected.not_to be_running }
319 | end
320 |
321 | context "when spork accepts the connection" do
322 | before(:each) do
323 | expect(TCPSocket).to receive(:new).with('127.0.0.1', 1337).and_return(socket)
324 | allow(instance).to receive_messages(:alive? => true)
325 | end
326 |
327 | it { is_expected.to be_running }
328 | end
329 | end
330 |
331 | describe ".spork_pids" do
332 | it "returns all the pids belonging to spork" do
333 | allow(instance.class).to receive(:`) { |command| raise "Unexpected command: #{command}" }
334 | expect(instance.class).to receive(:`).
335 | with(%q[ps aux | grep -v guard | awk '/spork/&&!/awk/{print $2;}']).
336 | and_return("666\n999")
337 |
338 | expect(instance.class.spork_pids).to eq([666, 999])
339 | end
340 | end
341 | end
342 | end
343 | end
344 |
--------------------------------------------------------------------------------
/spec/guard/spork/spork_windows_instance_spec.rb:
--------------------------------------------------------------------------------
1 | module Guard
2 | class Spork
3 | RSpec.describe SporkWindowsInstance do
4 | describe "#command adds 'cmd /C' as command prefix" do
5 | let(:options) { Hash.new }
6 | subject { SporkWindowsInstance.new(:rspec, 1337, {}, options) }
7 |
8 | describe '#command' do
9 | subject { super().command }
10 | it { is_expected.to eq(%w{cmd /C spork -p 1337}) }
11 | end
12 | end
13 | end
14 |
15 | RSpec.describe SporkWindowsInstance, "spawning" do
16 | let(:instance) { SporkWindowsInstance.new(:test, 1, {}, {}) }
17 |
18 | describe "#stop" do
19 | it "kills all child processes manually on Windows" do
20 | expect(instance).to receive(:pid).and_return("a pid")
21 | processes = [{:pid => 22, :ppid => "a pid"}, {:pid => 66, :ppid => 99}, {:pid => 33, :ppid => 22}, {:pid => 44, :ppid => 33}]
22 | allow(instance.class).to receive_messages(:spork_processes => processes)
23 | expect(Process).to receive(:kill).with(9, "a pid")
24 | expect(Process).to receive(:kill).with(9, 22)
25 | expect(Process).to receive(:kill).with(9, 33)
26 | expect(Process).to receive(:kill).with(9, 44)
27 | expect(Process).not_to receive(:kill).with(9, 66)
28 |
29 | instance.stop
30 | end
31 | end
32 |
33 | describe "(running)" do
34 | let(:socket) { double(:close => nil) }
35 | subject { instance }
36 |
37 | before(:each) do
38 | allow(instance).to receive_messages(:pid => 42, :port => 1337)
39 | allow(TCPSocket).to receive_messages(:new => socket)
40 | end
41 |
42 | context "when spork accepts the connection and DRb is not ready" do
43 | before(:each) do
44 | expect(TCPSocket).to receive(:new).with('127.0.0.1', 1337).and_return(socket)
45 | allow(instance).to receive_messages(:alive? => true)
46 | allow(instance).to receive_messages(:drb_ready? => false)
47 | end
48 |
49 | it { is_expected.not_to be_running }
50 | end
51 |
52 | context "when spork accepts the connection and DRb is ready" do
53 | before(:each) do
54 | expect(TCPSocket).to receive(:new).with('127.0.0.1', 1337).and_return(socket)
55 | allow(instance).to receive_messages(:alive? => true)
56 | allow(instance).to receive_messages(:drb_ready? => true)
57 | end
58 |
59 | it { is_expected.to be_running }
60 | end
61 | end
62 |
63 | describe ".spork_pids" do
64 | it "returns all the pids belonging to sporks", :if => Guard::Spork::Runner.windows? do
65 | require "win32ole"
66 |
67 | instances = double('instances')
68 | expect(WIN32OLE).to receive(:connect).
69 | with("winmgmts://.").and_return(instances)
70 |
71 | MockProcess = Struct.new :processid, :parentprocessid, :executablepath, :commandline
72 | spork = MockProcess.new 1, 10, "c:\\foo\\bar\\ruby.exe", "ruby.exe bin\\spork"
73 | spork_cmd = MockProcess.new 2, 1, "c:\\windows\\cmd.exe", "cmd.exe spork.bat"
74 | ring_server = MockProcess.new 3, 2, "c:\\foo\\bar\\ruby.exe", "ruby.exe bar\\ring_server.rb"
75 | slave_provider = MockProcess.new 4, 1, "c:\\foo\\bar\\ruby.exe", "ruby.exe bar\\magazine_slave_provider.rb"
76 | foo = MockProcess.new 5, 1, "c:\\foo\\bar\\foobar.exe", "foobar.exe ignored"
77 | expect(instances).to receive(:InstancesOf).with("win32_process").
78 | and_return([spork, spork_cmd, ring_server, slave_provider, foo])
79 |
80 | expect(instance.class.spork_pids).to eq([1, 2, 3, 4])
81 | end
82 | end
83 | end
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/spec/guard/spork_spec.rb:
--------------------------------------------------------------------------------
1 | require "guard/spork"
2 |
3 | RSpec.describe Guard::Spork do
4 | subject { Guard::Spork.new }
5 | let(:runner) { subject.runner }
6 |
7 | describe '#initialize' do
8 | let(:runner) { double('runner instance', :reevaluate => nil) }
9 | before(:each) { allow(Guard::Spork::Runner).to receive_messages(:new => runner) }
10 |
11 | it "instantiates Runner with the given options" do
12 | expect(Guard::Spork::Runner).to receive(:new).with(:bundler => false).and_return(runner)
13 | Guard::Spork.new :bundler => false
14 | end
15 | end
16 |
17 | describe "#start" do
18 | it "calls Runner#kill_global_sporks and Runner#launch_sporks with 'start'" do
19 | expect(runner).to receive(:kill_global_sporks)
20 | expect(runner).to receive(:launch_sporks).with("start")
21 | subject.start
22 | end
23 | end
24 |
25 | describe "#reload" do
26 | it "calls Runner#kill_sporks and Runner#launch_sporks with 'reload'" do
27 | expect(runner).to receive(:kill_sporks)
28 | expect(runner).to receive(:launch_sporks).with("reload")
29 | subject.reload
30 | end
31 | end
32 |
33 | describe "#run_on_modifications" do
34 | it "calls Runner#kill_sporks and Runner#launch_sporks with 'reload'" do
35 | expect(runner).to receive(:kill_sporks)
36 | expect(runner).to receive(:launch_sporks).with("reload")
37 | subject.run_on_modifications(["spec/spec_helper.rb"])
38 | end
39 | end
40 |
41 | describe "#run_on_additions" do
42 | it "calls Runner#kill_sporks and Runner#launch_sporks with 'reload'" do
43 | expect(runner).to receive(:kill_sporks)
44 | expect(runner).to receive(:launch_sporks).with("reload")
45 | subject.run_on_additions(["spec/spec_helper.rb"])
46 | end
47 | end
48 |
49 | describe "#stop" do
50 | it "calls Runner#kill_sporks" do
51 | expect(runner).to receive(:kill_sporks)
52 | subject.stop
53 | end
54 | end
55 | end
56 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | RSpec.configure do |config|
2 | def ci?
3 | ENV['CI'] == 'true'
4 | end
5 |
6 | config.expect_with :rspec do |expectations|
7 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true
8 | end
9 |
10 | config.mock_with :rspec do |mocks|
11 | mocks.verify_partial_doubles = true
12 | end
13 |
14 | config.filter_run focus: !ci?
15 | config.run_all_when_everything_filtered = true
16 |
17 | config.disable_monkey_patching!
18 |
19 | config.warnings = true
20 |
21 | if config.files_to_run.one?
22 | config.default_formatter = 'doc'
23 | end
24 |
25 | # config.profile_examples = 10
26 |
27 | config.order = :random
28 |
29 | Kernel.srand config.seed
30 | end
31 |
--------------------------------------------------------------------------------