summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorSteven Murawski <steven.murawski@gmail.com>2016-02-18 14:58:32 -0600
committerSteven Murawski <steven.murawski@gmail.com>2016-05-23 13:32:49 -0500
commite24b8e2cd67aef572fde184e8b07d48333184b29 (patch)
tree2b1f6507104149182791436be30561be3be1aef2 /spec
parente4bca443a3ddca6381275a1a252de04f5e4883c4 (diff)
downloadchef-e24b8e2cd67aef572fde184e8b07d48333184b29.tar.gz
* define exit codes
* exit code functional specs * audit exit codes * reboot now/reboot failed/reboot pending exit codes * Deal with forked and unforked process and get the right exit code * Reboot Now should really be reboot scheduled * pass exception rather than exit code * updated with sigint and sigterm * support legacy fatal!("", 2) behavior * fixup all fatal! and exit! calls
Diffstat (limited to 'spec')
-rw-r--r--spec/integration/client/exit_code_spec.rb245
-rw-r--r--spec/unit/application/apply_spec.rb6
-rw-r--r--spec/unit/application/exit_code_spec.rb231
-rw-r--r--spec/unit/config_fetcher_spec.rb4
4 files changed, 482 insertions, 4 deletions
diff --git a/spec/integration/client/exit_code_spec.rb b/spec/integration/client/exit_code_spec.rb
new file mode 100644
index 0000000000..30020f6a3f
--- /dev/null
+++ b/spec/integration/client/exit_code_spec.rb
@@ -0,0 +1,245 @@
+
+require "support/shared/integration/integration_helper"
+require "chef/mixin/shell_out"
+require "tiny_server"
+require "tmpdir"
+require "chef/platform"
+
+describe "chef-client" do
+
+ include IntegrationSupport
+ include Chef::Mixin::ShellOut
+
+ let(:chef_dir) { File.join(File.dirname(__FILE__), "..", "..", "..", "bin") }
+
+ # Invoke `chef-client` as `ruby PATH/TO/chef-client`. This ensures the
+ # following constraints are satisfied:
+ # * Windows: windows can only run batch scripts as bare executables. Rubygems
+ # creates batch wrappers for installed gems, but we don't have batch wrappers
+ # in the source tree.
+ # * Other `chef-client` in PATH: A common case is running the tests on a
+ # machine that has omnibus chef installed. In that case we need to ensure
+ # we're running `chef-client` from the source tree and not the external one.
+ # cf. CHEF-4914
+ let(:chef_client) { "ruby '#{chef_dir}/chef-client' --no-fork --minimal-ohai" }
+
+ let(:critical_env_vars) { %w{PATH RUBYOPT BUNDLE_GEMFILE GEM_PATH}.map { |o| "#{o}=#{ENV[o]}" } .join(" ") }
+
+ when_the_repository "does not have exit_status configured" do
+
+ def setup_client_rb
+ file "config/client.rb", <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+EOM
+ end
+
+ def setup_client_rb_with_audit_mode
+ file "config/client.rb", <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+audit_mode :audit_only
+EOM
+ end
+
+ def run_chef_client_and_expect_exit_code(exit_code)
+ shell_out!(
+ "#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'",
+ :cwd => chef_dir,
+ :returns => [exit_code])
+ end
+
+ context "has a cookbook" do
+ context "with a library" do
+ context "which cannot be loaded" do
+ before do
+ file "cookbooks/x/recipes/default.rb", ""
+ file "cookbooks/x/libraries/error.rb", "require 'does/not/exist'"
+ end
+
+ it "exits with GENERIC_FAILURE, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+ end
+
+ context "with an audit recipe" do
+ context "which fails" do
+ before do
+ file "cookbooks/x/recipes/default.rb", <<-RECIPE
+control_group "control group without top level control" do
+ it "should fail" do
+ expect(2 - 2).to eq(1)
+ end
+end
+RECIPE
+ end
+
+ it "exits with GENERIC_FAILURE, 1" do
+ setup_client_rb_with_audit_mode
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+ end
+
+ context "with a recipe" do
+ context "which throws an error" do
+ before { file "cookbooks/x/recipes/default.rb", "raise 'BOOM'" }
+
+ it "exits with GENERIC_FAILURE, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+
+ context "with a recipe which calls Chef::Application.fatal with a non-RFC exit code" do
+ before { file "cookbooks/x/recipes/default.rb", "Chef::Application.fatal!('BOOM', 123)" }
+
+ it "exits with the specified exit code" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 123
+ end
+ end
+
+ context "with a recipe which calls Chef::Application.exit with a non-RFC exit code" do
+ before { file "cookbooks/x/recipes/default.rb", "Chef::Application.exit!('BOOM', 231)" }
+
+ it "exits with the specified exit code" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 231
+ end
+ end
+ end
+
+ context "when an attempt to reboot fails (like from the reboot resource)" do
+ before do
+ file "cookbooks/x/recipes/default.rb", <<EOM
+raise Chef::Exceptions::RebootFailed.new
+EOM
+ end
+
+ it "exits with GENERIC_FAILURE, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+ end
+ end
+
+ when_the_repository "does has exit_status enabled" do
+
+ def setup_client_rb
+ file "config/client.rb", <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+exit_status :enabled
+EOM
+ end
+
+ def setup_client_rb_with_audit_mode
+ file "config/client.rb", <<EOM
+local_mode true
+cookbook_path "#{path_to('cookbooks')}"
+exit_status :enabled
+audit_mode :audit_only
+EOM
+ end
+
+ def run_chef_client_and_expect_exit_code(exit_code)
+ shell_out!("#{chef_client} -c \"#{path_to('config/client.rb')}\" -o 'x::default'",
+ :cwd => chef_dir,
+ :returns => [exit_code])
+ end
+
+ context "has a cookbook" do
+ context "with a library" do
+ context "which cannot be loaded" do
+ before do
+ file "cookbooks/x/recipes/default.rb", ""
+ file "cookbooks/x/libraries/error.rb", "require 'does/not/exist'"
+ end
+
+ it "exits with GENERIC_FAILURE, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+ end
+
+ context "with an audit recipe" do
+ context "which fails" do
+ before do
+ file "cookbooks/x/recipes/default.rb", <<-RECIPE
+control_group "control group without top level control" do
+ it "should fail" do
+ expect(4 - 4).to eq(1)
+ end
+end
+RECIPE
+ end
+
+ it "exits with AUDIT_MODE_FAILURE, 42" do
+ setup_client_rb_with_audit_mode
+ run_chef_client_and_expect_exit_code 42
+ end
+ end
+ end
+
+ context "with a recipe" do
+ context "which throws an error" do
+ before { file "cookbooks/x/recipes/default.rb", "raise 'BOOM'" }
+
+ it "exits with GENERIC_FAILURE, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+
+ context "with a recipe which calls Chef::Application.fatal with a non-RFC exit code" do
+ before { file "cookbooks/x/recipes/default.rb", "Chef::Application.fatal!('BOOM', 123)" }
+
+ it "exits with the GENERIC_FAILURE exit code, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+
+ context "with a recipe which calls Chef::Application.exit with a non-RFC exit code" do
+ before { file "cookbooks/x/recipes/default.rb", "Chef::Application.exit!('BOOM', 231)" }
+
+ it "exits with the GENERIC_FAILURE exit code, 1" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 1
+ end
+ end
+
+ context "when a reboot exception is raised (like from the reboot resource)" do
+ before do
+ file "cookbooks/x/recipes/default.rb", <<EOM
+raise Chef::Exceptions::Reboot.new
+EOM
+ end
+
+ it "exits with REBOOT_SCHEDULED, 35" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 35
+ end
+ end
+
+ context "when an attempt to reboot fails (like from the reboot resource)" do
+ before do
+ file "cookbooks/x/recipes/default.rb", <<EOM
+raise Chef::Exceptions::RebootFailed.new
+EOM
+ end
+
+ it "exits with REBOOT_FAILED, 41" do
+ setup_client_rb
+ run_chef_client_and_expect_exit_code 41
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb
index 6473666fbf..0af3916134 100644
--- a/spec/unit/application/apply_spec.rb
+++ b/spec/unit/application/apply_spec.rb
@@ -52,7 +52,8 @@ describe Chef::Application::Apply do
describe "when recipe is nil" do
it "should raise a fatal with the missing filename message" do
- expect(Chef::Application).to receive(:fatal!).with("No recipe file was provided", 1)
+ expect(Chef::Application).to receive(:fatal!).with("No recipe file was provided",
+ Chef::Exceptions::RecipeNotFound.new)
@app.read_recipe_file(nil)
end
end
@@ -61,7 +62,8 @@ describe Chef::Application::Apply do
allow(File).to receive(:exist?).with(@recipe_path).and_return(false)
end
it "should raise a fatal with the file doesn't exist message" do
- expect(Chef::Application).to receive(:fatal!).with(/^No file exists at/, 1)
+ expect(Chef::Application).to receive(:fatal!).with(/^No file exists at/,
+ Chef::Exceptions::RecipeNotFound.new)
@app.read_recipe_file(@recipe_file_name)
end
end
diff --git a/spec/unit/application/exit_code_spec.rb b/spec/unit/application/exit_code_spec.rb
new file mode 100644
index 0000000000..73a113e554
--- /dev/null
+++ b/spec/unit/application/exit_code_spec.rb
@@ -0,0 +1,231 @@
+#
+# Author:: Steven Murawski (<smurawski@chef.io>)
+# Copyright:: Copyright 2016, Chef Software, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "chef"
+require "spec_helper"
+
+require "chef/application/exit_code"
+
+describe Chef::Application::ExitCode do
+
+ let(:exit_codes) { Chef::Application::ExitCode }
+
+ let(:valid_rfc_exit_codes) { Chef::Application::ExitCode::VALID_RFC_062_EXIT_CODES.values }
+
+ context "Validates the return codes from RFC 062" do
+
+ before do
+ allow(Chef::Config).to receive(:[]).with(:exit_status).and_return(:enabled)
+ end
+
+ it "validates a SUCCESS return code of 0" do
+ expect(valid_rfc_exit_codes.include?(0)).to eq(true)
+ end
+
+ it "validates a GENERIC_FAILURE return code of 1" do
+ expect(valid_rfc_exit_codes.include?(1)).to eq(true)
+ end
+
+ it "validates a SIGINT_RECEIVED return code of 2" do
+ expect(valid_rfc_exit_codes.include?(2)).to eq(true)
+ end
+
+ it "validates a SIGTERM_RECEIVED return code of 3" do
+ expect(valid_rfc_exit_codes.include?(3)).to eq(true)
+ end
+
+ it "validates a AUDIT_MODE_FAILURE return code of 42" do
+ expect(valid_rfc_exit_codes.include?(42)).to eq(true)
+ end
+
+ it "validates a REBOOT_SCHEDULED return code of 35" do
+ expect(valid_rfc_exit_codes.include?(35)).to eq(true)
+ end
+
+ it "validates a REBOOT_NEEDED return code of 37" do
+ expect(valid_rfc_exit_codes.include?(37)).to eq(true)
+ end
+
+ it "validates a REBOOT_FAILED return code of 41" do
+ expect(valid_rfc_exit_codes.include?(41)).to eq(true)
+ end
+ end
+
+ context "when Chef::Config :exit_status is not configured" do
+ before do
+ allow(Chef::Config).to receive(:[]).with(:exit_status).and_return(nil)
+ allow(Chef::Config).to receive(:[]).with(:treat_deprecation_warnings_as_errors).and_return(false)
+ end
+
+ it "writes a deprecation warning" do
+ warn = "Chef RFC 062 (https://github.com/chef/chef-rfc/master/rfc062-exit-status.md) defines the" \
+ " exit codes that should be used with Chef. Chef::Application::ExitCode defines valid exit codes" \
+ " In a future release, non-standard exit codes will be redefined as" \
+ " GENERIC_FAILURE unless `exit_status` is set to `:disabled` in your client.rb."
+ expect(Chef).to receive(:log_deprecation).with(warn)
+ expect(exit_codes.normalize_exit_code(151)).to eq(151)
+ end
+
+ it "does not modify non-RFC exit codes" do
+ expect(exit_codes.normalize_exit_code(151)).to eq(151)
+ end
+
+ it "returns DEPRECATED_FAILURE when no exit code is specified" do
+ expect(exit_codes.normalize_exit_code()).to eq(-1)
+ end
+
+ it "returns SIGINT_RECEIVED when a SIGINT is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigInt.new("BOOM"))).to eq(2)
+ end
+
+ it "returns SIGTERM_RECEIVED when a SIGTERM is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigTerm.new("BOOM"))).to eq(3)
+ end
+
+ it "returns SIGINT_RECEIVED when a deprecated exit code error is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::DeprecatedExitCode.new("BOOM"))).to eq(2)
+ end
+
+ it "returns GENERIC_FAILURE when an exception is specified" do
+ expect(exit_codes.normalize_exit_code(Exception.new("BOOM"))).to eq(1)
+ end
+
+ end
+
+ context "when Chef::Config :exit_status is configured to not validate exit codes" do
+ before do
+ allow(Chef::Config).to receive(:[]).with(:exit_status).and_return(:disabled)
+ allow(Chef::Config).to receive(:[]).with(:treat_deprecation_warnings_as_errors).and_return(false)
+ end
+
+ it "does not write a deprecation warning" do
+ warn = "Chef RFC 062 (https://github.com/chef/chef-rfc/master/rfc062-exit-status.md) defines the" \
+ " exit codes that should be used with Chef. Chef::Application::ExitCode defines valid exit codes" \
+ " In a future release, non-standard exit codes will be redefined as" \
+ " GENERIC_FAILURE unless `exit_status` is set to `:disabled` in your client.rb."
+ expect(Chef).not_to receive(:log_deprecation).with(warn)
+ expect(exit_codes.normalize_exit_code(151)).to eq(151)
+ end
+
+ it "does not modify non-RFC exit codes" do
+ expect(exit_codes.normalize_exit_code(151)).to eq(151)
+ end
+
+ it "returns DEPRECATED_FAILURE when no exit code is specified" do
+ expect(exit_codes.normalize_exit_code()).to eq(-1)
+ end
+
+ it "returns GENERIC_FAILURE when an exception is specified" do
+ expect(exit_codes.normalize_exit_code(Exception.new("BOOM"))).to eq(1)
+ end
+
+ it "returns SUCCESS when a reboot is pending" do
+ allow(Chef::DSL::RebootPending).to receive(:reboot_pending?).and_return(true)
+ expect(exit_codes.normalize_exit_code(0)).to eq(0)
+ end
+
+ it "returns SIGINT_RECEIVED when a SIGINT is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigInt.new("BOOM"))).to eq(2)
+ end
+
+ it "returns SIGTERM_RECEIVED when a SIGTERM is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigTerm.new("BOOM"))).to eq(3)
+ end
+
+ it "returns SIGINT_RECEIVED when a deprecated exit code error is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::DeprecatedExitCode.new("BOOM"))).to eq(2)
+ end
+ end
+
+ context "when Chef::Config :exit_status is configured to validate exit codes" do
+ before do
+ allow(Chef::Config).to receive(:[]).with(:exit_status).and_return(:enabled)
+ allow(Chef::Config).to receive(:[]).with(:treat_deprecation_warnings_as_errors).and_return(false)
+ end
+
+ it "does write a deprecation warning" do
+ warn = "Chef RFC 062 (https://github.com/chef/chef-rfc/master/rfc062-exit-status.md) defines the" \
+ " exit codes that should be used with Chef. Chef::Application::ExitCode defines valid exit codes" \
+ " In a future release, non-standard exit codes will be redefined as" \
+ " GENERIC_FAILURE unless `exit_status` is set to `:disabled` in your client.rb."
+ expect(Chef).to receive(:log_deprecation).with(warn)
+ expect(exit_codes.normalize_exit_code(151)).to eq(1)
+ end
+
+ it "returns a GENERIC_FAILURE for non-RFC exit codes" do
+ expect(exit_codes.normalize_exit_code(151)).to eq(1)
+ end
+
+ it "returns GENERIC_FAILURE when no exit code is specified" do
+ expect(exit_codes.normalize_exit_code()).to eq(1)
+ end
+
+ it "returns SIGINT_RECEIVED when a SIGINT is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigInt.new("BOOM"))).to eq(2)
+ end
+
+ it "returns SIGTERM_RECEIVED when a SIGTERM is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::SigTerm.new("BOOM"))).to eq(3)
+ end
+
+ it "returns GENERIC_FAILURE when a deprecated exit code error is received" do
+ expect(exit_codes.normalize_exit_code(Chef::Exceptions::DeprecatedExitCode.new("BOOM"))).to eq(1)
+ end
+
+ it "returns GENERIC_FAILURE when an exception is specified" do
+ expect(exit_codes.normalize_exit_code(Exception.new("BOOM"))).to eq(1)
+ end
+
+ it "returns AUDIT_MODE_FAILURE when there is an audit error" do
+ audit_error = Chef::Exceptions::AuditError.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(audit_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(42)
+ end
+
+ it "returns REBOOT_SCHEDULED when there is an reboot requested" do
+ reboot_error = Chef::Exceptions::Reboot.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(reboot_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(35)
+ end
+
+ it "returns REBOOT_FAILED when the reboot command fails" do
+ reboot_error = Chef::Exceptions::RebootFailed.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(reboot_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(41)
+ end
+
+ it "returns REBOOT_NEEDED when a reboot is pending" do
+ reboot_error = Chef::Exceptions::RebootPending.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(reboot_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(37)
+ end
+
+ it "returns SIGINT_RECEIVED when a SIGINT is received." do
+ sigint_error = Chef::Exceptions::SigInt.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(sigint_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(2)
+ end
+
+ it "returns SIGTERM_RECEIVED when a SIGTERM is received." do
+ sigterm_error = Chef::Exceptions::SigTerm.new("BOOM")
+ runtime_error = Chef::Exceptions::RunFailedWrappingError.new(sigterm_error)
+ expect(exit_codes.normalize_exit_code(runtime_error)).to eq(3)
+ end
+ end
+
+end
diff --git a/spec/unit/config_fetcher_spec.rb b/spec/unit/config_fetcher_spec.rb
index 35cf27f2af..6847ee5fd3 100644
--- a/spec/unit/config_fetcher_spec.rb
+++ b/spec/unit/config_fetcher_spec.rb
@@ -58,7 +58,7 @@ describe Chef::ConfigFetcher do
and_return(invalid_json)
expect(Chef::Application).to receive(:fatal!).
- with(invalid_json_error_regex, 2)
+ with(invalid_json_error_regex, Chef::Exceptions::DeprecatedExitCode.new)
fetcher.fetch_json
end
end
@@ -104,7 +104,7 @@ describe Chef::ConfigFetcher do
with("").and_return(invalid_json)
expect(Chef::Application).to receive(:fatal!).
- with(invalid_json_error_regex, 2)
+ with(invalid_json_error_regex, Chef::Exceptions::DeprecatedExitCode.new)
fetcher.fetch_json
end
end