[Pkg-puppet-devel] [facter] 268/352: (FACT-327) Extract windows/posix execution classes

Stig Sandbeck Mathisen ssm at debian.org
Sun Apr 6 22:21:52 UTC 2014


This is an automated email from the git hooks/post-receive script.

ssm pushed a commit to branch master
in repository facter.

commit 74840eb1291a71cc6df4c8d7c41ddb26a3c17005
Author: Adrien Thebo <git at somethingsinistral.net>
Date:   Wed Feb 12 19:58:58 2014 -0800

    (FACT-327) Extract windows/posix execution classes
---
 lib/facter/core/execution.rb             |  11 +-
 lib/facter/core/execution/base.rb        | 109 +++++++----------
 lib/facter/core/execution/posix.rb       |  50 ++++++++
 lib/facter/core/execution/ruby18.rb      |  46 -------
 lib/facter/core/execution/windows.rb     |  57 +++++++++
 spec/unit/core/execution/base_spec.rb    | 204 +++++--------------------------
 spec/unit/core/execution/posix_spec.rb   |  86 +++++++++++++
 spec/unit/core/execution/ruby18_spec.rb  |  59 ---------
 spec/unit/core/execution/windows_spec.rb | 106 ++++++++++++++++
 9 files changed, 383 insertions(+), 345 deletions(-)

diff --git a/lib/facter/core/execution.rb b/lib/facter/core/execution.rb
index cff46fe..353d0fa 100644
--- a/lib/facter/core/execution.rb
+++ b/lib/facter/core/execution.rb
@@ -5,9 +5,14 @@ module Facter
     module Execution
 
       require 'facter/core/execution/base'
-      require 'facter/core/execution/ruby18'
+      require 'facter/core/execution/windows'
+      require 'facter/core/execution/posix'
 
-      @@impl = Facter::Core::Execution::Ruby18.new
+      @@impl = if Facter::Util::Config.is_windows?
+                 Facter::Core::Execution::Windows.new
+               else
+                 Facter::Core::Execution::Posix.new
+               end
 
       def self.impl
         @@impl
@@ -52,7 +57,7 @@ module Facter
 
       # Given a command line, this returns the command line with the
       # executable written as an absolute path. If the executable contains
-      # spaces, it has be but in double quotes to be properly recognized.
+      # spaces, it has be put in double quotes to be properly recognized.
       #
       # @param command [String] the command line
       #
diff --git a/lib/facter/core/execution/base.rb b/lib/facter/core/execution/base.rb
index 88165fe..cdeda13 100644
--- a/lib/facter/core/execution/base.rb
+++ b/lib/facter/core/execution/base.rb
@@ -1,71 +1,5 @@
 class Facter::Core::Execution::Base
 
-  def search_paths
-    if Facter::Util::Config.is_windows?
-      ENV['PATH'].split(File::PATH_SEPARATOR)
-    else
-      # Make sure facter is usable even for non-root users. Most commands
-      # in /sbin (like ifconfig) can be run as non priviledged users as
-      # long as they do not modify anything - which we do not do with facter
-      ENV['PATH'].split(File::PATH_SEPARATOR) + [ '/sbin', '/usr/sbin' ]
-    end
-  end
-
-  def which(bin)
-    if absolute_path?(bin)
-      return bin if File.executable?(bin)
-    else
-      search_paths.each do |dir|
-        dest = File.join(dir, bin)
-        if Facter::Util::Config.is_windows?
-          dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR)
-          if File.extname(dest).empty?
-            exts = ENV['PATHEXT']
-            exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD]
-            exts.each do |ext|
-              destext = dest + ext
-              return destext if File.executable?(destext)
-            end
-          end
-        end
-        return dest if File.executable?(dest)
-      end
-    end
-    nil
-  end
-
-  def absolute_path?(path, platform=nil)
-    # Escape once for the string literal, and once for the regex.
-    slash = '[\\\\/]'
-    name = '[^\\\\/]+'
-    regexes = {
-      :windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i,
-      :posix   => %r!^/!,
-    }
-    platform ||= Facter::Util::Config.is_windows? ? :windows : :posix
-
-    !! (path =~ regexes[platform])
-  end
-
-  def expand_command(command)
-    if match = /^"(.+?)"(?:\s+(.*))?/.match(command)
-      exe, arguments = match.captures
-      exe = which(exe) and [ "\"#{exe}\"", arguments ].compact.join(" ")
-    elsif match = /^'(.+?)'(?:\s+(.*))?/.match(command) and not Facter::Util::Config.is_windows?
-      exe, arguments = match.captures
-      exe = which(exe) and [ "'#{exe}'", arguments ].compact.join(" ")
-    else
-      exe, arguments = command.split(/ /,2)
-      if exe = which(exe)
-        # the binary was not quoted which means it contains no spaces. But the
-        # full path to the binary may do so.
-        exe = "\"#{exe}\"" if exe =~ /\s/ and Facter::Util::Config.is_windows?
-        exe = "'#{exe}'" if exe =~ /\s/ and not Facter::Util::Config.is_windows?
-        [ exe, arguments ].compact.join(" ")
-      end
-    end
-  end
-
   def with_env(values)
     old = {}
     values.each do |var, value|
@@ -93,4 +27,47 @@ class Facter::Core::Execution::Base
     rv
   end
 
+  def exec(code)
+
+    ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the
+    ## output of the command can expect it to be in a consistent / predictable format / locale
+    with_env "LANG" => "C" do
+
+      if expanded_code = expand_command(code)
+        # if we can find the binary, we'll run the command with the expanded path to the binary
+        code = expanded_code
+      else
+        return ''
+      end
+
+      out = ''
+
+      begin
+        wait_for_child = true
+        out = %x{#{code}}.chomp
+        wait_for_child = false
+      rescue => detail
+        Facter.warn(detail.message)
+        return ''
+      ensure
+        if wait_for_child
+          # We need to ensure that if this code exits early then any spawned
+          # children will be reaped. Process execution is frequently
+          # terminated using Timeout.timeout but since the timeout isn't in
+          # this scope we can't rescue the raised exception. The best that
+          # we can do is determine if the child has exited, and if it hasn't
+          # then we need to spawn a thread to wait for the child.
+          #
+          # Due to the limitations of Ruby 1.8 there aren't good ways to
+          # asynchronously run a command and grab the PID of that command
+          # using the standard library. The best we can do is blindly wait
+          # on all processes and hope for the best. This issue is described
+          # at https://tickets.puppetlabs.com/browse/FACT-150
+          Thread.new { Process.waitall }
+        end
+      end
+
+      out
+    end
+  end
 end
diff --git a/lib/facter/core/execution/posix.rb b/lib/facter/core/execution/posix.rb
new file mode 100644
index 0000000..f710ff9
--- /dev/null
+++ b/lib/facter/core/execution/posix.rb
@@ -0,0 +1,50 @@
+class Facter::Core::Execution::Posix < Facter::Core::Execution::Base
+
+  DEFAULT_SEARCH_PATHS = ['/sbin', '/usr/sbin']
+
+  def search_paths
+    # Make sure facter is usable even for non-root users. Most commands
+    # in /sbin (like ifconfig) can be run as non privileged users as
+    # long as they do not modify anything - which we do not do with facter
+    ENV['PATH'].split(File::PATH_SEPARATOR) + DEFAULT_SEARCH_PATHS
+  end
+
+  def which(bin)
+    if absolute_path?(bin)
+      return bin if File.executable?(bin)
+    else
+      search_paths.each do |dir|
+        dest = File.join(dir, bin)
+        return dest if File.executable?(dest)
+      end
+    end
+    nil
+  end
+
+  ABSOLUTE_PATH_REGEX = %r{^/}
+
+  def absolute_path?(path)
+    !! (path =~ ABSOLUTE_PATH_REGEX)
+  end
+
+  DOUBLE_QUOTED_COMMAND = /^"(.+?)"(?:\s+(.*))?/
+  SINGLE_QUOTED_COMMAND = /^'(.+?)'(?:\s+(.*))?/
+
+  def expand_command(command)
+    exe = nil
+    args = nil
+
+    if (match = (command.match(DOUBLE_QUOTED_COMMAND) || command.match(SINGLE_QUOTED_COMMAND)))
+      exe, args = match.captures
+    else
+      exe, args = command.split(/ /,2)
+    end
+
+    if exe and (expanded = which(exe))
+      expanded = "'#{expanded}'" if expanded.match(/\s/)
+      expanded << " #{args}" if args
+
+      return expanded
+    end
+  end
+end
diff --git a/lib/facter/core/execution/ruby18.rb b/lib/facter/core/execution/ruby18.rb
deleted file mode 100644
index 55251a7..0000000
--- a/lib/facter/core/execution/ruby18.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-class Facter::Core::Execution::Ruby18 < Facter::Core::Execution::Base
-
-  def exec(code)
-
-    ## Set LANG to force i18n to C for the duration of this exec; this ensures that any code that parses the
-    ## output of the command can expect it to be in a consistent / predictable format / locale
-    with_env "LANG" => "C" do
-
-      if expanded_code = expand_command(code)
-        # if we can find the binary, we'll run the command with the expanded path to the binary
-        code = expanded_code
-      else
-        return ''
-      end
-
-      out = ''
-
-      begin
-        wait_for_child = true
-        out = %x{#{code}}.chomp
-        wait_for_child = false
-      rescue => detail
-        Facter.warn(detail.message)
-        return ''
-      ensure
-        if wait_for_child
-          # We need to ensure that if this code exits early then any spawned
-          # children will be reaped. Process execution is frequently
-          # terminated using Timeout.timeout but since the timeout isn't in
-          # this scope we can't rescue the raised exception. The best that
-          # we can do is determine if the child has exited, and if it hasn't
-          # then we need to spawn a thread to wait for the child.
-          #
-          # Due to the limitations of Ruby 1.8 there aren't good ways to
-          # asynchronously run a command and grab the PID of that command
-          # using the standard library. The best we can do is blindly wait
-          # on all processes and hope for the best. This issue is described
-          # at https://tickets.puppetlabs.com/browse/FACT-150
-          Thread.new { Process.waitall }
-        end
-      end
-
-      out
-    end
-  end
-end
diff --git a/lib/facter/core/execution/windows.rb b/lib/facter/core/execution/windows.rb
new file mode 100644
index 0000000..1fa3ba7
--- /dev/null
+++ b/lib/facter/core/execution/windows.rb
@@ -0,0 +1,57 @@
+class Facter::Core::Execution::Windows < Facter::Core::Execution::Base
+
+  def search_paths
+    ENV['PATH'].split(File::PATH_SEPARATOR)
+  end
+
+  DEFAULT_COMMAND_EXTENSIONS = %w[.COM .EXE .BAT .CMD]
+
+  def which(bin)
+    if absolute_path?(bin)
+      return bin if File.executable?(bin)
+    else
+      search_paths.each do |dir|
+        dest = File.join(dir, bin)
+        dest.gsub!(File::SEPARATOR, File::ALT_SEPARATOR)
+        if File.extname(dest).empty?
+          exts = ENV['PATHEXT']
+          exts = exts ? exts.split(File::PATH_SEPARATOR) : DEFAULT_COMMAND_EXTENSIONS
+          exts.each do |ext|
+            destext = dest + ext
+            return destext if File.executable?(destext)
+          end
+        end
+        return dest if File.executable?(dest)
+      end
+    end
+    nil
+  end
+
+  slash = '[\\\\/]'
+  name = '[^\\\\/]+'
+  ABSOLUTE_PATH_REGEX = %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i
+
+  def absolute_path?(path)
+    !! (path =~ ABSOLUTE_PATH_REGEX)
+  end
+
+  DOUBLE_QUOTED_COMMAND = /^"(.+?)"(?:\s+(.*))?/
+
+  def expand_command(command)
+    exe = nil
+    args = nil
+
+    if (match = command.match(DOUBLE_QUOTED_COMMAND))
+      exe, args = match.captures
+    else
+      exe, args = command.split(/ /,2)
+    end
+
+    if exe and (expanded = which(exe))
+      expanded = "\"#{expanded}\"" if expanded.match(/\s+/)
+      expanded << " #{args}" if args
+
+      return expanded
+    end
+  end
+end
diff --git a/spec/unit/core/execution/base_spec.rb b/spec/unit/core/execution/base_spec.rb
index d7e22fa..652f392 100644
--- a/spec/unit/core/execution/base_spec.rb
+++ b/spec/unit/core/execution/base_spec.rb
@@ -62,196 +62,58 @@ describe Facter::Core::Execution::Base do
     end
   end
 
-  describe "#search_paths" do
-    context "on windows", :as_platform => :windows do
-      it "should use the PATH environment variable to determine locations" do
-        ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32'
-        subject.search_paths.should == %w{C:\Windows C:\Windows\System32}
-      end
-    end
+  describe "#exec" do
 
-    context "on posix", :as_platform => :posix do
-      it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do
-        ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin"
-        subject.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin}
-      end
+    it "switches LANG to C when executing the command" do
+      subject.expects(:with_env).with('LANG' => 'C')
+      subject.exec('foo')
     end
-  end
-
-  describe "#which" do
-    context "when run on posix", :as_platform => :posix  do
-      before :each do
-        subject.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin']
-      end
-
-      context "and provided with an absolute path" do
-        it "should return the binary if executable" do
-          File.expects(:executable?).with('/opt/foo').returns true
-          subject.which('/opt/foo').should == '/opt/foo'
-        end
 
-        it "should return nil if the binary is not executable" do
-          File.expects(:executable?).with('/opt/foo').returns false
-          subject.which('/opt/foo').should be_nil
-        end
-      end
+    it "switches LC_ALL to C when executing the command"
 
-      context "and not provided with an absolute path" do
-        it "should return the absolute path if found" do
-          File.expects(:executable?).with('/bin/foo').returns false
-          File.expects(:executable?).with('/sbin/foo').returns true
-          File.expects(:executable?).with('/usr/sbin/foo').never
-          subject.which('foo').should == '/sbin/foo'
-        end
-
-        it "should return nil if not found" do
-          File.expects(:executable?).with('/bin/foo').returns false
-          File.expects(:executable?).with('/sbin/foo').returns false
-          File.expects(:executable?).with('/usr/sbin/foo').returns false
-          subject.which('foo').should be_nil
-        end
-      end
+    it "expands the command before running it" do
+      subject.stubs(:`).returns ''
+      subject.expects(:expand_command).with('foo').returns '/bin/foo'
+      subject.exec('foo')
     end
 
-    context "when run on windows", :as_platform => :windows do
-      before :each do
-        subject.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ]
-        ENV.stubs(:[]).with('PATHEXT').returns nil
-      end
-
-      context "and provided with an absolute path" do
-        it "should return the binary if executable" do
-          File.expects(:executable?).with('C:\Tools\foo.exe').returns true
-          File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true
-          subject.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe'
-          subject.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe'
-        end
-
-        it "should return nil if the binary is not executable" do
-          File.expects(:executable?).with('C:\Tools\foo.exe').returns false
-          File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false
-          subject.which('C:\Tools\foo.exe').should be_nil
-          subject.which('\\\\remote\dir\foo.exe').should be_nil
-        end
-      end
-
-      context "and not provided with an absolute path" do
-        it "should return the absolute path if found" do
-          File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false
-          File.expects(:executable?).with('C:\Windows\foo.exe').returns true
-          File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never
-          subject.which('foo.exe').should == 'C:\Windows\foo.exe'
-        end
-
-        it "should return the absolute path with file extension if found" do
-          ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext|
-            File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false
-            File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false
-          end
-          ['.COM', '.BAT', '.CMD', '' ].each do |ext|
-            File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false
-          end
-          File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true
-
-          subject.which('foo').should == 'C:\Windows\foo.EXE'
-        end
-
-        it "should return nil if not found" do
-          File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false
-          File.expects(:executable?).with('C:\Windows\foo.exe').returns false
-          File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false
-          subject.which('foo.exe').should be_nil
-        end
-      end
+    it "returns an empty string when the command could not be expanded" do
+      subject.expects(:expand_command).with('foo').returns nil
+      expect(subject.exec('foo')).to be_empty
     end
 
-    describe "#expand_command" do
-      context "on windows", :as_platform => :windows do
-        it "should expand binary" do
-          subject.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd'
-          subject.expand_command(
-            'cmd /c echo foo > C:\bar'
-          ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar'
-        end
-
-        it "should expand double quoted binary" do
-          subject.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe'
-          subject.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b'
-        end
-
-        it "should not expand single quoted binary" do
-          subject.expects(:which).with('\'C:\My').returns nil
-          subject.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil
-        end
-
-        it "should quote expanded binary if found in path with spaces" do
-          subject.expects(:which).with('foo').returns 'C:\My Tools\foo.exe'
-          subject.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b'
-        end
+    it "logs a warning and returns an empty string when the command execution fails" do
+      subject.expects(:`).with("/bin/foo").raises "kaboom!"
+      Facter.expects(:warn).with("kaboom!")
 
-        it "should return nil if not found" do
-          subject.expects(:which).with('foo').returns nil
-          subject.expand_command('foo /a | stuff >> /dev/null').should be_nil
-        end
-      end
+      subject.expects(:expand_command).with('foo').returns '/bin/foo'
 
-      context "on unix", :as_platform => :posix do
-        it "should expand binary" do
-          subject.expects(:which).with('foo').returns '/bin/foo'
-          subject.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null'
-        end
+      expect(subject.exec("foo")).to be_empty
+    end
 
-        it "should expand double quoted binary" do
-          subject.expects(:which).with('/tmp/my foo').returns '/tmp/my foo'
-          subject.expand_command(%q{"/tmp/my foo" bar}).should == %q{"/tmp/my foo" bar}
-        end
+    it "launches a thread to wait on children if the command was interrupted" do
+      subject.expects(:`).with("/bin/foo").raises "kaboom!"
+      subject.expects(:expand_command).with('foo').returns '/bin/foo'
 
-        it "should expand single quoted binary" do
-          subject.expects(:which).with('my foo').returns '/home/bob/my path/my foo'
-          subject.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a}
-        end
+      Facter.stubs(:warn)
+      Thread.expects(:new).yields
+      Process.expects(:waitall).once
 
-        it "should quote expanded binary if found in path with spaces" do
-          subject.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh'
-          subject.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b}
-        end
-
-        it "should return nil if not found" do
-          subject.expects(:which).with('foo').returns nil
-          subject.expand_command('foo -a | stuff >> /dev/null').should be_nil
-        end
-      end
+      subject.exec("foo")
     end
 
-  end
-
-  describe "#absolute_path?" do
-    context "when run on unix", :as_platform => :posix do
-      %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path|
-        it "should return true for #{path}" do
-          subject.should be_absolute_path(path)
-        end
-      end
+    it "returns the output of the command" do
+      subject.expects(:`).with("/bin/foo").returns 'hi'
+      subject.expects(:expand_command).with('foo').returns '/bin/foo'
 
-      %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path|
-        it "should return false for #{path}" do
-          subject.should_not be_absolute_path(path)
-        end
-      end
+      expect(subject.exec("foo")).to eq 'hi'
     end
 
-    context "when run on windows", :as_platform => :windows  do
-      %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path|
-        it "should return true for #{path}" do
-          subject.should be_absolute_path(path)
-        end
-      end
+    it "strips off trailing newlines" do
+      subject.expects(:`).with("/bin/foo").returns "hi\n"
+      subject.expects(:expand_command).with('foo').returns '/bin/foo'
 
-      %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path|
-        it "should return false for #{path}" do
-          subject.should_not be_absolute_path(path)
-        end
-      end
+      expect(subject.exec("foo")).to eq 'hi'
     end
   end
 end
diff --git a/spec/unit/core/execution/posix_spec.rb b/spec/unit/core/execution/posix_spec.rb
new file mode 100644
index 0000000..1acd8e4
--- /dev/null
+++ b/spec/unit/core/execution/posix_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe Facter::Core::Execution::Posix, :as_plaform => :posix do
+
+  describe "#search_paths" do
+    it "should use the PATH environment variable plus /sbin and /usr/sbin on unix" do
+      ENV.expects(:[]).with('PATH').returns "/bin:/usr/bin"
+      subject.search_paths.should == %w{/bin /usr/bin /sbin /usr/sbin}
+    end
+  end
+
+  describe "#which" do
+    before :each do
+      subject.stubs(:search_paths).returns [ '/bin', '/sbin', '/usr/sbin']
+    end
+
+    context "and provided with an absolute path" do
+      it "should return the binary if executable" do
+        File.expects(:executable?).with('/opt/foo').returns true
+        subject.which('/opt/foo').should == '/opt/foo'
+      end
+
+      it "should return nil if the binary is not executable" do
+        File.expects(:executable?).with('/opt/foo').returns false
+        subject.which('/opt/foo').should be_nil
+      end
+    end
+
+    context "and not provided with an absolute path" do
+      it "should return the absolute path if found" do
+        File.expects(:executable?).with('/bin/foo').returns false
+        File.expects(:executable?).with('/sbin/foo').returns true
+        File.expects(:executable?).with('/usr/sbin/foo').never
+        subject.which('foo').should == '/sbin/foo'
+      end
+
+      it "should return nil if not found" do
+        File.expects(:executable?).with('/bin/foo').returns false
+        File.expects(:executable?).with('/sbin/foo').returns false
+        File.expects(:executable?).with('/usr/sbin/foo').returns false
+        subject.which('foo').should be_nil
+      end
+    end
+  end
+
+  describe "#expand_command" do
+    it "should expand binary" do
+      subject.expects(:which).with('foo').returns '/bin/foo'
+      subject.expand_command('foo -a | stuff >> /dev/null').should == '/bin/foo -a | stuff >> /dev/null'
+    end
+
+    it "should expand double quoted binary" do
+      subject.expects(:which).with('/tmp/my foo').returns '/tmp/my foo'
+      subject.expand_command(%q{"/tmp/my foo" bar}).should == %q{'/tmp/my foo' bar}
+    end
+
+    it "should expand single quoted binary" do
+      subject.expects(:which).with('my foo').returns '/home/bob/my path/my foo'
+      subject.expand_command(%q{'my foo' -a}).should == %q{'/home/bob/my path/my foo' -a}
+    end
+
+    it "should quote expanded binary if found in path with spaces" do
+      subject.expects(:which).with('foo.sh').returns '/home/bob/my tools/foo.sh'
+      subject.expand_command('foo.sh /a /b').should == %q{'/home/bob/my tools/foo.sh' /a /b}
+    end
+
+    it "should return nil if not found" do
+      subject.expects(:which).with('foo').returns nil
+      subject.expand_command('foo -a | stuff >> /dev/null').should be_nil
+    end
+  end
+
+  describe "#absolute_path?" do
+    %w[/ /foo /foo/../bar //foo //Server/Foo/Bar //?/C:/foo/bar /\Server/Foo /foo//bar/baz].each do |path|
+      it "should return true for #{path}" do
+        subject.should be_absolute_path(path)
+      end
+    end
+
+    %w[. ./foo \foo C:/foo \\Server\Foo\Bar \\?\C:\foo\bar \/?/foo\bar \/Server/foo foo//bar/baz].each do |path|
+      it "should return false for #{path}" do
+        subject.should_not be_absolute_path(path)
+      end
+    end
+  end
+end
diff --git a/spec/unit/core/execution/ruby18_spec.rb b/spec/unit/core/execution/ruby18_spec.rb
deleted file mode 100644
index 10c0c1a..0000000
--- a/spec/unit/core/execution/ruby18_spec.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-require 'spec_helper'
-
-describe Facter::Core::Execution::Ruby18 do
-
-  describe "#exec" do
-
-    it "switches LANG to C when executing the command" do
-      subject.expects(:with_env).with('LANG' => 'C')
-      subject.exec('foo')
-    end
-
-    it "switches LC_ALL to C when executing the command"
-
-    it "expands the command before running it" do
-      subject.stubs(:`).returns ''
-      subject.expects(:expand_command).with('foo').returns '/bin/foo'
-      subject.exec('foo')
-    end
-
-    it "returns an empty string when the command could not be expanded" do
-      subject.expects(:expand_command).with('foo').returns nil
-      expect(subject.exec('foo')).to be_empty
-    end
-
-    it "logs a warning and returns an empty string when the command execution fails" do
-      subject.expects(:`).with("/bin/foo").raises "kaboom!"
-      Facter.expects(:warn).with("kaboom!")
-
-      subject.expects(:expand_command).with('foo').returns '/bin/foo'
-
-      expect(subject.exec("foo")).to be_empty
-    end
-
-    it "launches a thread to wait on children if the command was interrupted" do
-      subject.expects(:`).with("/bin/foo").raises "kaboom!"
-      subject.expects(:expand_command).with('foo').returns '/bin/foo'
-
-      Facter.stubs(:warn)
-      Thread.expects(:new).yields
-      Process.expects(:waitall).once
-
-      subject.exec("foo")
-    end
-
-    it "returns the output of the command" do
-      subject.expects(:`).with("/bin/foo").returns 'hi'
-      subject.expects(:expand_command).with('foo').returns '/bin/foo'
-
-      expect(subject.exec("foo")).to eq 'hi'
-    end
-
-    it "strips off trailing newlines" do
-      subject.expects(:`).with("/bin/foo").returns "hi\n"
-      subject.expects(:expand_command).with('foo').returns '/bin/foo'
-
-      expect(subject.exec("foo")).to eq 'hi'
-    end
-  end
-end
diff --git a/spec/unit/core/execution/windows_spec.rb b/spec/unit/core/execution/windows_spec.rb
new file mode 100644
index 0000000..39ff1d1
--- /dev/null
+++ b/spec/unit/core/execution/windows_spec.rb
@@ -0,0 +1,106 @@
+require 'spec_helper'
+
+describe Facter::Core::Execution::Windows, :as_platform => :windows do
+
+  describe "#search_paths" do
+    it "should use the PATH environment variable to determine locations" do
+      ENV.expects(:[]).with('PATH').returns 'C:\Windows;C:\Windows\System32'
+      subject.search_paths.should == %w{C:\Windows C:\Windows\System32}
+    end
+  end
+
+  describe "#which" do
+    before :each do
+      subject.stubs(:search_paths).returns ['C:\Windows\system32', 'C:\Windows', 'C:\Windows\System32\Wbem' ]
+      ENV.stubs(:[]).with('PATHEXT').returns nil
+    end
+
+    context "and provided with an absolute path" do
+      it "should return the binary if executable" do
+        File.expects(:executable?).with('C:\Tools\foo.exe').returns true
+        File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns true
+        subject.which('C:\Tools\foo.exe').should == 'C:\Tools\foo.exe'
+        subject.which('\\\\remote\dir\foo.exe').should == '\\\\remote\dir\foo.exe'
+      end
+
+      it "should return nil if the binary is not executable" do
+        File.expects(:executable?).with('C:\Tools\foo.exe').returns false
+        File.expects(:executable?).with('\\\\remote\dir\foo.exe').returns false
+        subject.which('C:\Tools\foo.exe').should be_nil
+        subject.which('\\\\remote\dir\foo.exe').should be_nil
+      end
+    end
+
+    context "and not provided with an absolute path" do
+      it "should return the absolute path if found" do
+        File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false
+        File.expects(:executable?).with('C:\Windows\foo.exe').returns true
+        File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').never
+        subject.which('foo.exe').should == 'C:\Windows\foo.exe'
+      end
+
+      it "should return the absolute path with file extension if found" do
+        ['.COM', '.EXE', '.BAT', '.CMD', '' ].each do |ext|
+          File.stubs(:executable?).with('C:\Windows\system32\foo'+ext).returns false
+          File.stubs(:executable?).with('C:\Windows\System32\Wbem\foo'+ext).returns false
+        end
+        ['.COM', '.BAT', '.CMD', '' ].each do |ext|
+          File.stubs(:executable?).with('C:\Windows\foo'+ext).returns false
+        end
+        File.stubs(:executable?).with('C:\Windows\foo.EXE').returns true
+
+        subject.which('foo').should == 'C:\Windows\foo.EXE'
+      end
+
+      it "should return nil if not found" do
+        File.expects(:executable?).with('C:\Windows\system32\foo.exe').returns false
+        File.expects(:executable?).with('C:\Windows\foo.exe').returns false
+        File.expects(:executable?).with('C:\Windows\System32\Wbem\foo.exe').returns false
+        subject.which('foo.exe').should be_nil
+      end
+    end
+  end
+
+  describe "#expand_command" do
+    it "should expand binary" do
+      subject.expects(:which).with('cmd').returns 'C:\Windows\System32\cmd'
+      subject.expand_command(
+        'cmd /c echo foo > C:\bar'
+      ).should == 'C:\Windows\System32\cmd /c echo foo > C:\bar'
+    end
+
+    it "should expand double quoted binary" do
+      subject.expects(:which).with('my foo').returns 'C:\My Tools\my foo.exe'
+      subject.expand_command('"my foo" /a /b').should == '"C:\My Tools\my foo.exe" /a /b'
+    end
+
+    it "should not expand single quoted binary" do
+      subject.expects(:which).with('\'C:\My').returns nil
+      subject.expand_command('\'C:\My Tools\foo.exe\' /a /b').should be_nil
+    end
+
+    it "should quote expanded binary if found in path with spaces" do
+      subject.expects(:which).with('foo').returns 'C:\My Tools\foo.exe'
+      subject.expand_command('foo /a /b').should == '"C:\My Tools\foo.exe" /a /b'
+    end
+
+    it "should return nil if not found" do
+      subject.expects(:which).with('foo').returns nil
+      subject.expand_command('foo /a | stuff >> NUL').should be_nil
+    end
+  end
+
+  describe "#absolute_path?" do
+    %w[C:/foo C:\foo \\\\Server\Foo\Bar \\\\?\C:\foo\bar //Server/Foo/Bar //?/C:/foo/bar /\?\C:/foo\bar \/Server\Foo/Bar c:/foo//bar//baz].each do |path|
+      it "should return true for #{path}" do
+        subject.should be_absolute_path(path)
+      end
+    end
+
+    %w[/ . ./foo \foo /foo /foo/../bar //foo C:foo/bar foo//bar/baz].each do |path|
+      it "should return false for #{path}" do
+        subject.should_not be_absolute_path(path)
+      end
+    end
+  end
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-puppet/facter.git



More information about the Pkg-puppet-devel mailing list