# # Author:: Igor Afonov <afonov@gmail.com> # Copyright:: Copyright (c) 2011 Igor Afonov # 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 'spec_helper' describe Chef::Provider::Service::Macosx do describe ".gather_plist_dirs" do context "when HOME directory is set" do before do allow(ENV).to receive(:[]).with('HOME').and_return("/User/someuser") end it "includes users's LaunchAgents folder" do expect(described_class.gather_plist_dirs).to include("#{ENV['HOME']}/Library/LaunchAgents") end end context "when HOME directory is not set" do before do allow(ENV).to receive(:[]).with('HOME').and_return(nil) end it "doesn't include user's LaunchAgents folder" do expect(described_class.gather_plist_dirs).not_to include("~/Library/LaunchAgents") end end end context "when service name is given as" do let(:node) { Chef::Node.new } let(:events) {Chef::EventDispatch::Dispatcher.new} let(:run_context) { Chef::RunContext.new(node, {}, events) } let(:provider) { described_class.new(new_resource, run_context) } let(:launchctl_stdout) { StringIO.new } let(:plutil_stdout) { String.new <<-XML } <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>io.redis.redis-server</string> </dict> </plist> XML ["redis-server", "io.redis.redis-server"].each do |service_name| before do allow(Dir).to receive(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], []) allow(provider).to receive(:shell_out!). with("launchctl list", {:group => 1001, :user => 101}). and_return(double("Status", :stdout => launchctl_stdout)) allow(provider).to receive(:shell_out). with(/launchctl list /, {:group => nil, :user => nil}). and_return(double("Status", :stdout => launchctl_stdout, :exitstatus => 0)) allow(provider).to receive(:shell_out!). with(/plutil -convert xml1 -o/). and_return(double("Status", :stdout => plutil_stdout)) allow(File).to receive(:stat).and_return(double("stat", :gid => 1001, :uid => 101)) end context "#{service_name}" do let(:new_resource) { Chef::Resource::Service.new(service_name) } let!(:current_resource) { Chef::Resource::Service.new(service_name) } describe "#load_current_resource" do # CHEF-5223 "you can't glob for a file that hasn't been converged # onto the node yet." context "when the plist doesn't exist" do def run_resource_setup_for_action(action) new_resource.action(action) provider.action = action provider.load_current_resource provider.define_resource_requirements provider.process_resource_requirements end before do allow(Dir).to receive(:glob).and_return([]) allow(provider).to receive(:shell_out!). with(/plutil -convert xml1 -o/). and_raise(Mixlib::ShellOut::ShellCommandFailed) end it "works for action :nothing" do expect { run_resource_setup_for_action(:nothing) }.not_to raise_error end it "works for action :start" do expect { run_resource_setup_for_action(:start) }.not_to raise_error end it "errors if action is :enable" do expect { run_resource_setup_for_action(:enable) }.to raise_error(Chef::Exceptions::Service) end it "errors if action is :disable" do expect { run_resource_setup_for_action(:disable) }.to raise_error(Chef::Exceptions::Service) end end context "when launchctl returns pid in service list" do let(:launchctl_stdout) { StringIO.new <<-SVC_LIST } 12761 - 0x100114220.old.machinit.thing 7777 - io.redis.redis-server - - com.lol.stopped-thing SVC_LIST before do provider.load_current_resource end it "sets resource running state to true" do expect(provider.current_resource.running).to be_true end it "sets resouce enabled state to true" do expect(provider.current_resource.enabled).to be_true end end describe "running unsupported actions" do let(:launchctl_stdout) { StringIO.new <<-SVC_LIST } 12761 - 0x100114220.old.machinit.thing 7777 - io.redis.redis-server - - com.lol.stopped-thing SVC_LIST before do allow(Dir).to receive(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], []) end it "should throw an exception when reload action is attempted" do expect {provider.run_action(:reload)}.to raise_error(Chef::Exceptions::UnsupportedAction) end end context "when launchctl returns empty service pid" do let(:launchctl_stdout) { StringIO.new <<-SVC_LIST } 12761 - 0x100114220.old.machinit.thing - - io.redis.redis-server - - com.lol.stopped-thing SVC_LIST before do provider.load_current_resource end it "sets resource running state to false" do expect(provider.current_resource.running).to be_false end it "sets resouce enabled state to true" do expect(provider.current_resource.enabled).to be_true end end context "when launchctl doesn't return service entry at all" do let(:launchctl_stdout) { StringIO.new <<-SVC_LIST } 12761 - 0x100114220.old.machinit.thing - - com.lol.stopped-thing SVC_LIST it "sets service running state to false" do provider.load_current_resource expect(provider.current_resource.running).to be_false end context "and plist for service is not available" do before do allow(Dir).to receive(:glob).and_return([]) provider.load_current_resource end it "sets resouce enabled state to false" do expect(provider.current_resource.enabled).to be_false end end context "and plist for service is available" do before do allow(Dir).to receive(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist"], []) provider.load_current_resource end it "sets resouce enabled state to true" do expect(provider.current_resource.enabled).to be_true end end describe "and several plists match service name" do it "throws exception" do allow(Dir).to receive(:glob).and_return(["/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist", "/Users/wtf/something.plist"]) provider.load_current_resource provider.define_resource_requirements expect { provider.process_resource_requirements }.to raise_error(Chef::Exceptions::Service) end end end end describe "#start_service" do before do allow(Chef::Resource::Service).to receive(:new).and_return(current_resource) provider.load_current_resource allow(current_resource).to receive(:running).and_return(false) end it "calls the start command if one is specified and service is not running" do allow(new_resource).to receive(:start_command).and_return("cowsay dirty") expect(provider).to receive(:shell_out_with_systems_locale!).with("cowsay dirty") provider.start_service end it "shows warning message if service is already running" do allow(current_resource).to receive(:running).and_return(true) expect(Chef::Log).to receive(:debug).with("service[#{service_name}] already running, not starting") provider.start_service end it "starts service via launchctl if service found" do expect(provider).to receive(:shell_out_with_systems_locale!). with("launchctl load -w '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'", :group => 1001, :user => 101). and_return(0) provider.start_service end end describe "#stop_service" do before do allow(Chef::Resource::Service).to receive(:new).and_return(current_resource) provider.load_current_resource allow(current_resource).to receive(:running).and_return(true) end it "calls the stop command if one is specified and service is running" do allow(new_resource).to receive(:stop_command).and_return("kill -9 123") expect(provider).to receive(:shell_out_with_systems_locale!).with("kill -9 123") provider.stop_service end it "shows warning message if service is not running" do allow(current_resource).to receive(:running).and_return(false) expect(Chef::Log).to receive(:debug).with("service[#{service_name}] not running, not stopping") provider.stop_service end it "stops the service via launchctl if service found" do expect(provider).to receive(:shell_out_with_systems_locale!). with("launchctl unload '/Users/igor/Library/LaunchAgents/io.redis.redis-server.plist'", :group => 1001, :user => 101). and_return(0) provider.stop_service end end describe "#restart_service" do before do allow(Chef::Resource::Service).to receive(:new).and_return(current_resource) provider.load_current_resource allow(current_resource).to receive(:running).and_return(true) allow(provider).to receive(:sleep) end it "issues a command if given" do allow(new_resource).to receive(:restart_command).and_return("reload that thing") expect(provider).to receive(:shell_out_with_systems_locale!).with("reload that thing") provider.restart_service end it "stops and then starts service" do expect(provider).to receive(:stop_service) expect(provider).to receive(:start_service); provider.restart_service end end end end end end