diff --git a/lib/train/transports/local.rb b/lib/train/transports/local.rb index 4f98b323..a7ba6ef4 100644 --- a/lib/train/transports/local.rb +++ b/lib/train/transports/local.rb @@ -10,8 +10,6 @@ module Train::Transports class Local < Train.plugin(1) name 'local' - include_options Train::Extras::CommandWrapper - class PipeError < ::StandardError; end def connection(_ = nil) @@ -22,18 +20,11 @@ class Connection < BaseConnection def initialize(options) super(options) - # While OS is being discovered, use the GenericRunner - @runner = GenericRunner.new - @runner.cmd_wrapper = CommandWrapper.load(self, options) - - if os.windows? - # Attempt to use a named pipe but fallback to ShellOut if that fails - begin - @runner = WindowsPipeRunner.new - rescue PipeError - @runner = WindowsShellRunner.new - end - end + @runner = if options[:command_runner] + force_runner(options[:command_runner]) + else + select_runner(options) + end end def local? @@ -50,8 +41,42 @@ def uri private + def select_runner(options) + if os.windows? + # Attempt to use a named pipe but fallback to ShellOut if that fails + begin + WindowsPipeRunner.new + rescue PipeError + WindowsShellRunner.new + end + else + GenericRunner.new(self, options) + end + end + + def force_runner(command_runner) + case command_runner + when :generic + GenericRunner.new(self, options) + when :windows_pipe + WindowsPipeRunner.new + when :windows_shell + WindowsShellRunner.new + else + fail "Runner type `#{command_runner}` not supported" + end + end + def run_command_via_connection(cmd) - @runner.run_command(cmd) + # Use the runner if it is available + return @runner.run_command(cmd) if defined?(@runner) + + # If we don't have a runner, such as at the beginning of setting up the + # transport and performing the first few steps of OS detection, fall + # back to shelling out. + res = Mixlib::ShellOut.new(cmd) + res.run_command + Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus) rescue Errno::ENOENT => _ CommandResult.new('', '', 1) end @@ -65,7 +90,11 @@ def file_via_connection(path) end class GenericRunner - attr_writer :cmd_wrapper + include_options Train::Extras::CommandWrapper + + def initialize(connection, options) + @cmd_wrapper = Local::CommandWrapper.load(connection, options) + end def run_command(cmd) if defined?(@cmd_wrapper) && !@cmd_wrapper.nil? diff --git a/test/unit/transports/local_test.rb b/test/unit/transports/local_test.rb index 42a7ee8d..085edc77 100644 --- a/test/unit/transports/local_test.rb +++ b/test/unit/transports/local_test.rb @@ -13,7 +13,7 @@ def initialize(user_opts = {}) plat.family_hierarchy = opts[:family_hierarchy] plat.add_platform_methods Train::Platforms::Detect.stubs(:scan).returns(plat) - @transport = Train::Transports::Local.new + @transport = Train::Transports::Local.new(user_opts) end end @@ -55,6 +55,58 @@ def initialize(user_opts = {}) methods.include?(:file_via_connection).must_equal true end + describe 'when overriding runner selection' do + it 'can select the `GenericRunner`' do + Train::Transports::Local::Connection::GenericRunner + .expects(:new) + + Train::Transports::Local::Connection::WindowsPipeRunner + .expects(:new) + .never + + Train::Transports::Local::Connection::WindowsShellRunner + .expects(:new) + .never + + Train::Transports::Local::Connection.new(command_runner: :generic) + end + + it 'can select the `WindowsPipeRunner`' do + Train::Transports::Local::Connection::GenericRunner + .expects(:new) + .never + + Train::Transports::Local::Connection::WindowsPipeRunner + .expects(:new) + + Train::Transports::Local::Connection::WindowsShellRunner + .expects(:new) + .never + + Train::Transports::Local::Connection.new(command_runner: :windows_pipe) + end + + it 'can select the `WindowsShellRunner`' do + Train::Transports::Local::Connection::GenericRunner + .expects(:new) + .never + + Train::Transports::Local::Connection::WindowsPipeRunner + .expects(:new) + .never + + Train::Transports::Local::Connection::WindowsShellRunner + .expects(:new) + + Train::Transports::Local::Connection.new(command_runner: :windows_shell) + end + + it 'throws a RuntimeError when an invalid runner type is passed' do + proc { Train::Transports::Local::Connection.new(command_runner: :nope ) } + .must_raise(RuntimeError, "Runner type `:nope` not supported") + end + end + describe 'when running a local command' do let(:cmd_runner) { Minitest::Mock.new }