├── .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 [![Build Status](https://secure.travis-ci.org/guard/guard-spork.png)](http://travis-ci.org/guard/guard-spork) [![Dependency Status](https://gemnasium.com/guard/guard-spork.png)](https://gemnasium.com/guard/guard-spork) [![Gem Version](https://badge.fury.io/rb/guard-spork.png)](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 | --------------------------------------------------------------------------------