summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md5
-rw-r--r--Gemfile5
-rw-r--r--README.md2
-rw-r--r--Rakefile7
-rw-r--r--appveyor.yml31
-rw-r--r--lib/mixlib/shellout/version.rb2
-rw-r--r--lib/mixlib/shellout/windows.rb8
-rw-r--r--mixlib-shellout-windows.gemspec8
-rw-r--r--mixlib-shellout-x86-mingw32.gemspec9
-rw-r--r--spec/mixlib/shellout_spec.rb225
10 files changed, 177 insertions, 125 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b64b9ea..ca1a924 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,11 @@
## Unreleased
+## Release: 2.0.1
+
+* add buffering to the child process status pipe to fix chef-client deadlocks
+* fix timeouts on Windows
+
## Release: 2.0.0
* remove LC_ALL=C default setting, consumers should now set this if they
diff --git a/Gemfile b/Gemfile
index 86160d0..d080412 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,9 +3,10 @@ source 'https://rubygems.org'
gemspec :name => "mixlib-shellout"
group(:test) do
-
gem "rspec_junit_formatter"
gem 'rake'
-
end
+group(:development) do
+ gem 'pry'
+end
diff --git a/README.md b/README.md
index d470975..2cfe5b0 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ Invoke crontab to edit user cron:
## Windows Impersonation Example
Invoke "whoami.exe" to demonstrate running a command as another user:
- whomai = Mixlib::ShellOut.new("whoami.exe", :user => "username", :domain => "DOMAIN", :password => "password")
+ whoami = Mixlib::ShellOut.new("whoami.exe", :user => "username", :domain => "DOMAIN", :password => "password")
whoami.run_command
## Platform Support
diff --git a/Rakefile b/Rakefile
index 533b341..754ba4d 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,20 +1,19 @@
require 'rspec/core/rake_task'
require 'rubygems/package_task'
+require 'mixlib/shellout/version'
Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
gemspec = eval(IO.read(gemspec_path))
Gem::PackageTask.new(gemspec).define
end
-require 'mixlib/shellout/version'
-
desc "Run all specs in spec directory"
RSpec::Core::RakeTask.new(:spec) do |t|
t.pattern = FileList['spec/**/*_spec.rb']
end
desc "Build it and ship it"
-task :ship => [:clean, :gem] do
+task ship: [:clobber_package, :gem] do
sh("git tag #{Mixlib::ShellOut::VERSION}")
sh("git push opscode --tags")
Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
@@ -22,4 +21,4 @@ task :ship => [:clean, :gem] do
end
end
-task :default => :spec
+task default: :spec
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..de4779c
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,31 @@
+version: "master-{build}"
+
+os: Windows Server 2012
+platform:
+ - x64
+
+environment:
+ matrix:
+ - ruby_version: "200"
+ - ruby_version: "193"
+
+clone_folder: c:\projects\mixlib-shellout
+clone_depth: 1
+skip_tags: true
+branches:
+ only:
+ - master
+
+install:
+ - SET PATH=C:\Ruby%ruby_version%\bin;%PATH%
+ - echo %PATH%
+ - ruby --version
+ - gem --version
+ - gem install bundler --quiet --no-ri --no-rdoc
+ - bundler --version
+
+build_script:
+ - bundle install
+
+test_script:
+ - bundle exec rspec
diff --git a/lib/mixlib/shellout/version.rb b/lib/mixlib/shellout/version.rb
index e741dfb..2301857 100644
--- a/lib/mixlib/shellout/version.rb
+++ b/lib/mixlib/shellout/version.rb
@@ -1,5 +1,5 @@
module Mixlib
class ShellOut
- VERSION = "2.2.0.dev.0"
+ VERSION = "2.0.1"
end
end
diff --git a/lib/mixlib/shellout/windows.rb b/lib/mixlib/shellout/windows.rb
index 137aaa9..1612b09 100644
--- a/lib/mixlib/shellout/windows.rb
+++ b/lib/mixlib/shellout/windows.rb
@@ -111,6 +111,12 @@ module Mixlib
when WAIT_TIMEOUT
# Kill the process
if (Time.now - start_wait) > timeout
+ begin
+ Process.kill(:KILL, process.process_id)
+ rescue Errno::EIO
+ logger.warn("Failed to kill timed out process #{process.process_id}") if logger
+ end
+
raise Mixlib::ShellOut::CommandTimeout, "command timed out:\n#{format_for_exception}"
end
@@ -156,7 +162,7 @@ module Mixlib
begin
next_chunk = stdout_read.readpartial(READ_SIZE)
@stdout << next_chunk
- @live_stream << next_chunk if @live_stream
+ @live_stdout << next_chunk if @live_stdout
rescue EOFError
stdout_read.close
open_streams.delete(stdout_read)
diff --git a/mixlib-shellout-windows.gemspec b/mixlib-shellout-windows.gemspec
new file mode 100644
index 0000000..20c8164
--- /dev/null
+++ b/mixlib-shellout-windows.gemspec
@@ -0,0 +1,8 @@
+gemspec = eval(File.read(File.expand_path("../mixlib-shellout.gemspec", __FILE__)))
+
+gemspec.platform = Gem::Platform.new(["universal", "mingw32"])
+
+gemspec.add_dependency "win32-process", "~> 0.7.5"
+gemspec.add_dependency "windows-pr", "~> 1.2.4"
+
+gemspec
diff --git a/mixlib-shellout-x86-mingw32.gemspec b/mixlib-shellout-x86-mingw32.gemspec
deleted file mode 100644
index 2997a35..0000000
--- a/mixlib-shellout-x86-mingw32.gemspec
+++ /dev/null
@@ -1,9 +0,0 @@
-# x86-mingw32 Gemspec #
-gemspec = eval(IO.read(File.expand_path("../mixlib-shellout.gemspec", __FILE__)))
-
-gemspec.platform = "x86-mingw32"
-
-gemspec.add_dependency "win32-process", "~> 0.7.1"
-gemspec.add_dependency "windows-pr", "~> 1.2.2"
-
-gemspec
diff --git a/spec/mixlib/shellout_spec.rb b/spec/mixlib/shellout_spec.rb
index 3fda419..1765026 100644
--- a/spec/mixlib/shellout_spec.rb
+++ b/spec/mixlib/shellout_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
require 'logger'
+require 'timeout'
describe Mixlib::ShellOut do
let(:shell_cmd) { options ? shell_cmd_with_options : shell_cmd_without_options }
@@ -538,19 +539,14 @@ describe Mixlib::ShellOut do
let(:locale) { nil }
context 'when running under Unix', :unix_only do
- let(:parent_locale) { ENV['LC_ALL'].to_s.strip }
-
- it "should unset the parent process's locale" do
+ it "should unset the process's locale" do
should eql("")
end
end
context 'when running under Windows', :windows_only do
- # On windows, if an environmental variable is not set, it returns the key
- let(:parent_locale) { (ENV['LC_ALL'] || '%LC_ALL%').to_s.strip }
-
- it "should use the parent process's locale" do
- should eql(parent_locale)
+ it "should unset process's locale" do
+ should eql('%LC_ALL%')
end
end
end
@@ -1032,142 +1028,157 @@ describe Mixlib::ShellOut do
end
- context 'with subprocess that takes longer than timeout', :unix_only do
- def ruby_wo_shell(code)
- parts = %w[ruby]
- parts << "--disable-gems" if ruby_19?
- parts << "-e"
- parts << code
- end
-
- let(:cmd) do
- ruby_wo_shell(<<-CODE)
- STDOUT.sync = true
- trap(:TERM) { puts "got term"; exit!(123) }
- sleep 10
- CODE
- end
+ context 'with subprocess that takes longer than timeout' do
let(:options) { { :timeout => 1 } }
- it "should raise CommandTimeout" do
- lambda { executed_cmd }.should raise_error(Mixlib::ShellOut::CommandTimeout)
- end
+ context 'on windows', :windows_only do
+ let(:cmd) do
+ 'powershell -c "sleep 10"'
+ end
- it "should ask the process nicely to exit" do
- # note: let blocks don't correctly memoize if an exception is raised,
- # so can't use executed_cmd
- lambda { shell_cmd.run_command }.should raise_error(Mixlib::ShellOut::CommandTimeout)
- shell_cmd.stdout.should include("got term")
- shell_cmd.exitstatus.should == 123
+ it "should raise CommandTimeout" do
+ Timeout::timeout(5) do
+ lambda { executed_cmd }.should raise_error(Mixlib::ShellOut::CommandTimeout)
+ end
+ end
end
- context "and the child is unresponsive" do
+ context 'on unix', :unix_only do
+ def ruby_wo_shell(code)
+ parts = %w[ruby]
+ parts << "--disable-gems" if ruby_19?
+ parts << "-e"
+ parts << code
+ end
+
let(:cmd) do
ruby_wo_shell(<<-CODE)
STDOUT.sync = true
- trap(:TERM) { puts "nanana cant hear you" }
+ trap(:TERM) { puts "got term"; exit!(123) }
sleep 10
CODE
end
- it "should KILL the wayward child" do
+ it "should raise CommandTimeout" do
+ lambda { executed_cmd }.should raise_error(Mixlib::ShellOut::CommandTimeout)
+ end
+
+ it "should ask the process nicely to exit" do
# note: let blocks don't correctly memoize if an exception is raised,
# so can't use executed_cmd
- lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
- shell_cmd.stdout.should include("nanana cant hear you")
- shell_cmd.status.termsig.should == 9
+ lambda { shell_cmd.run_command }.should raise_error(Mixlib::ShellOut::CommandTimeout)
+ shell_cmd.stdout.should include("got term")
+ shell_cmd.exitstatus.should == 123
end
- context "and a logger is configured" do
- let(:log_output) { StringIO.new }
- let(:logger) { Logger.new(log_output) }
- let(:options) { {:timeout => 1, :logger => logger} }
+ context "and the child is unresponsive" do
+ let(:cmd) do
+ ruby_wo_shell(<<-CODE)
+ STDOUT.sync = true
+ trap(:TERM) { puts "nanana cant hear you" }
+ sleep 10
+ CODE
+ end
- it "should log messages about killing the child process" do
+ it "should KILL the wayward child" do
# note: let blocks don't correctly memoize if an exception is raised,
# so can't use executed_cmd
lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
shell_cmd.stdout.should include("nanana cant hear you")
shell_cmd.status.termsig.should == 9
-
- log_output.string.should include("Command exceeded allowed execution time, sending TERM")
- log_output.string.should include("Command exceeded allowed execution time, sending KILL")
end
- end
- end
+ context "and a logger is configured" do
+ let(:log_output) { StringIO.new }
+ let(:logger) { Logger.new(log_output) }
+ let(:options) { {:timeout => 1, :logger => logger} }
- context "and the child process forks grandchildren" do
- let(:cmd) do
- ruby_wo_shell(<<-CODE)
- STDOUT.sync = true
- trap(:TERM) { print "got term in child\n"; exit!(123) }
- fork do
- trap(:TERM) { print "got term in grandchild\n"; exit!(142) }
- sleep 10
+ it "should log messages about killing the child process" do
+ # note: let blocks don't correctly memoize if an exception is raised,
+ # so can't use executed_cmd
+ lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
+ shell_cmd.stdout.should include("nanana cant hear you")
+ shell_cmd.status.termsig.should == 9
+
+ log_output.string.should include("Command exceeded allowed execution time, sending TERM")
+ log_output.string.should include("Command exceeded allowed execution time, sending KILL")
end
- sleep 10
- CODE
- end
- it "should TERM the wayward child and grandchild" do
- # note: let blocks don't correctly memoize if an exception is raised,
- # so can't use executed_cmd
- lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
- shell_cmd.stdout.should include("got term in child")
- shell_cmd.stdout.should include("got term in grandchild")
+ end
end
- end
- context "and the child process forks grandchildren that don't respond to TERM" do
- let(:cmd) do
- ruby_wo_shell(<<-CODE)
- STDOUT.sync = true
-
- trap(:TERM) { print "got term in child\n"; exit!(123) }
- fork do
- trap(:TERM) { print "got term in grandchild\n" }
+ context "and the child process forks grandchildren" do
+ let(:cmd) do
+ ruby_wo_shell(<<-CODE)
+ STDOUT.sync = true
+ trap(:TERM) { print "got term in child\n"; exit!(123) }
+ fork do
+ trap(:TERM) { print "got term in grandchild\n"; exit!(142) }
+ sleep 10
+ end
sleep 10
- end
- sleep 10
- CODE
- end
-
- it "should TERM the wayward child and grandchild, then KILL whoever is left" do
- # note: let blocks don't correctly memoize if an exception is raised,
- # so can't use executed_cmd
- lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
-
- begin
-
- # A little janky. We get the process group id out of the command
- # object, then try to kill a process in it to make sure none
- # exists. Trusting the system under test like this isn't great but
- # it's difficult to test otherwise.
- child_pgid = shell_cmd.send(:child_pgid)
- initial_process_listing = `ps -j`
+ CODE
+ end
+ it "should TERM the wayward child and grandchild" do
+ # note: let blocks don't correctly memoize if an exception is raised,
+ # so can't use executed_cmd
+ lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
shell_cmd.stdout.should include("got term in child")
shell_cmd.stdout.should include("got term in grandchild")
+ end
- kill_return_val = Process.kill(:INT, child_pgid) # should raise ESRCH
- # AIX - kill returns code > 0 for error, where as other platforms return -1. Ruby code signal.c treats < 0 as error and raises exception and hence fails on AIX. So we check the return code for assertions since ruby wont raise an error here.
-
- if(kill_return_val == 0)
- # Debug the failure:
- puts "child pgid=#{child_pgid.inspect}"
- Process.wait
- puts "collected process: #{$?.inspect}"
- puts "initial process listing:\n#{initial_process_listing}"
- puts "current process listing:"
- puts `ps -j`
- raise "Failed to kill all expected processes"
+ end
+ context "and the child process forks grandchildren that don't respond to TERM" do
+ let(:cmd) do
+ ruby_wo_shell(<<-CODE)
+ STDOUT.sync = true
+
+ trap(:TERM) { print "got term in child\n"; exit!(123) }
+ fork do
+ trap(:TERM) { print "got term in grandchild\n" }
+ sleep 10
+ end
+ sleep 10
+ CODE
+ end
+
+ it "should TERM the wayward child and grandchild, then KILL whoever is left" do
+ # note: let blocks don't correctly memoize if an exception is raised,
+ # so can't use executed_cmd
+ lambda { shell_cmd.run_command}.should raise_error(Mixlib::ShellOut::CommandTimeout)
+
+ begin
+
+ # A little janky. We get the process group id out of the command
+ # object, then try to kill a process in it to make sure none
+ # exists. Trusting the system under test like this isn't great but
+ # it's difficult to test otherwise.
+ child_pgid = shell_cmd.send(:child_pgid)
+ initial_process_listing = `ps -j`
+
+ shell_cmd.stdout.should include("got term in child")
+ shell_cmd.stdout.should include("got term in grandchild")
+
+ kill_return_val = Process.kill(:INT, child_pgid) # should raise ESRCH
+ # AIX - kill returns code > 0 for error, where as other platforms return -1. Ruby code signal.c treats < 0 as error and raises exception and hence fails on AIX. So we check the return code for assertions since ruby wont raise an error here.
+
+ if(kill_return_val == 0)
+ # Debug the failure:
+ puts "child pgid=#{child_pgid.inspect}"
+ Process.wait
+ puts "collected process: #{$?.inspect}"
+ puts "initial process listing:\n#{initial_process_listing}"
+ puts "current process listing:"
+ puts `ps -j`
+ raise "Failed to kill all expected processes"
+ end
+ rescue Errno::ESRCH
+ # this is what we want
end
- rescue Errno::ESRCH
- # this is what we want
end
- end
+ end
end
end