Updated mcollective.init according to OSCI-658
[packages/precise/mcollective.git] / spec / unit / util_spec.rb
diff --git a/spec/unit/util_spec.rb b/spec/unit/util_spec.rb
new file mode 100755 (executable)
index 0000000..a5f6087
--- /dev/null
@@ -0,0 +1,494 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+
+module MCollective
+  describe Util do
+    before do
+      class MCollective::Connector::Stomp<MCollective::Connector::Base; end
+
+      PluginManager.clear
+      PluginManager << {:type => "connector_plugin", :class => MCollective::Connector::Stomp.new}
+    end
+
+    describe "#t" do
+      it "should correctly translate the message" do
+        I18n.expects(:t).with("PLMC1.pattern", {:rspec => "test"})
+        I18n.expects(:t).with("PLMC1.expanded", {:rspec => "test"})
+        Util.t(:PLMC1, :rspec => "test")
+        Util.t("PLMC1.expanded", :rspec => "test")
+      end
+    end
+
+    describe "#windows?" do
+      it "should correctly detect windows on unix platforms" do
+        RbConfig::CONFIG.expects("[]").returns("linux")
+        Util.windows?.should == false
+      end
+
+      it "should correctly detect windows on windows platforms" do
+        RbConfig::CONFIG.expects("[]").returns("win32")
+        Util.windows?.should == true
+      end
+    end
+
+    describe "#setup_windows_sleeper" do
+      it "should set up a thread on the windows platform" do
+        Thread.expects(:new)
+        Util.expects("windows?").returns(true).once
+        Util.setup_windows_sleeper
+      end
+
+      it "should not set up a thread on other platforms" do
+        Thread.expects(:new).never
+        Util.expects("windows?").returns(false).once
+        Util.setup_windows_sleeper
+      end
+    end
+
+    describe "#has_cf_class?" do
+      before do
+        logger = mock
+        logger.stubs(:log)
+        logger.stubs(:start)
+        Log.configure(logger)
+
+        config = mock
+        config.stubs(:classesfile).returns("/some/file")
+        Config.expects(:instance).returns(config)
+      end
+
+      it "should read the classes lines from the correct file" do
+        File.expects(:readlines).with("/some/file")
+
+        Util.has_cf_class?("test")
+      end
+
+      it "should support regular expression searches" do
+        File.stubs(:readlines).returns(["test_class_test"])
+        String.any_instance.expects(:match).with("^/").returns(true)
+        String.any_instance.expects(:match).with(Regexp.new("class")).returns(true)
+
+        Util.has_cf_class?("/class/").should == true
+      end
+
+      it "should support exact string matches" do
+        File.stubs(:readlines).returns(["test_class_test"])
+        String.any_instance.expects(:match).with("^/").returns(false)
+        String.any_instance.expects(:match).with(Regexp.new("test_class_test")).never
+
+        Util.has_cf_class?("test_class_test").should == true
+      end
+
+      it "should report a warning when the classes file cannot be parsed" do
+        File.stubs(:readlines).returns(nil)
+        Log.expects(:warn).with("Parsing classes file '/some/file' failed: NoMethodError: undefined method `each' for nil:NilClass")
+
+        Util.has_cf_class?("test_class_test").should == false
+      end
+    end
+
+    describe "#shellescape" do
+      it "should return '' for empty strings" do
+        Util.shellescape("").should == "''"
+      end
+
+      it "should quote newlines" do
+        Util.shellescape("\n").should == "'\n'"
+      end
+
+      it "should escape unwanted characters" do
+        Util.shellescape("foo;bar").should == 'foo\;bar'
+        Util.shellescape('foo`bar').should == 'foo\`bar'
+        Util.shellescape('foo$bar').should == 'foo\$bar'
+        Util.shellescape('foo|bar').should == 'foo\|bar'
+        Util.shellescape('foo&&bar').should == 'foo\&\&bar'
+        Util.shellescape('foo||bar').should == 'foo\|\|bar'
+        Util.shellescape('foo>bar').should == 'foo\>bar'
+        Util.shellescape('foo<bar').should == 'foo\<bar'
+        Util.shellescape('foobar').should == 'foobar'
+      end
+    end
+
+    describe "#make_subscription" do
+      it "should validate target types" do
+        expect {
+          Util.make_subscriptions("test", "test", "test")
+        }.to raise_error("Unknown target type test")
+
+        Config.instance.stubs(:collectives).returns(["test"])
+        Util.make_subscriptions("test", :broadcast, "test")
+      end
+
+      it "should return a subscription for each collective" do
+        Config.instance.stubs(:collectives).returns(["collective1", "collective2"])
+        Util.make_subscriptions("test", :broadcast).should == [{:type=>:broadcast,
+                                                                 :agent=>"test",
+                                                                 :collective=>"collective1"},
+                                                               {:type=>:broadcast,
+                                                                 :agent=>"test",
+                                                                 :collective=>"collective2"}]
+      end
+
+      it "should validate given collective" do
+        Config.instance.stubs(:collectives).returns(["collective1", "collective2"])
+
+        expect {
+          Util.make_subscriptions("test", :broadcast, "test")
+        }.to raise_error("Unknown collective 'test' known collectives are 'collective1, collective2'")
+      end
+
+      it "should return a single subscription array given a collective" do
+        Config.instance.stubs(:collectives).returns(["collective1", "collective2"])
+        Util.make_subscriptions("test", :broadcast, "collective1").should == [{:type=>:broadcast, :agent=>"test", :collective=>"collective1"}]
+      end
+    end
+
+    describe "#subscribe" do
+      it "should subscribe to multiple topics given an Array" do
+        subs1 = {:agent => "test_agent", :type => "test_type", :collective => "test_collective"}
+        subs2 = {:agent => "test_agent2", :type => "test_type2", :collective => "test_collective2"}
+
+        MCollective::Connector::Stomp.any_instance.expects(:subscribe).with("test_agent", "test_type", "test_collective").once
+        MCollective::Connector::Stomp.any_instance.expects(:subscribe).with("test_agent2", "test_type2", "test_collective2").once
+
+        Util.subscribe([subs1, subs2])
+      end
+
+      it "should subscribe to a single topic given a hash" do
+        MCollective::Connector::Stomp.any_instance.expects(:subscribe).with("test_agent", "test_type", "test_collective").once
+        Util.subscribe({:agent => "test_agent", :type => "test_type", :collective => "test_collective"})
+      end
+    end
+
+    describe "#unsubscribe" do
+      it "should unsubscribe to multiple topics given an Array" do
+        subs1 = {:agent => "test_agent", :type => "test_type", :collective => "test_collective"}
+        subs2 = {:agent => "test_agent2", :type => "test_type2", :collective => "test_collective2"}
+        MCollective::Connector::Stomp.any_instance.expects(:unsubscribe).with("test_agent", "test_type", "test_collective").once
+        MCollective::Connector::Stomp.any_instance.expects(:unsubscribe).with("test_agent2", "test_type2", "test_collective2").once
+
+        Util.unsubscribe([subs1, subs2])
+      end
+
+      it "should subscribe to a single topic given a hash" do
+        MCollective::Connector::Stomp.any_instance.expects(:unsubscribe).with("test_agent", "test_type", "test_collective").once
+        Util.unsubscribe({:agent => "test_agent", :type => "test_type", :collective => "test_collective"})
+      end
+    end
+
+    describe "#empty_filter?" do
+      it "should correctly compare empty filters" do
+        Util.empty_filter?(Util.empty_filter).should == true
+      end
+
+      it "should treat an empty hash as an empty filter" do
+        Util.empty_filter?({}).should == true
+      end
+
+      it "should detect non empty filters correctly" do
+        filter = Util.empty_filter
+        filter["cf_class"] << "meh"
+
+
+        Util.empty_filter?(filter).should == false
+      end
+    end
+
+    describe "#empty_filter" do
+      it "should create correct empty filters" do
+        Util.empty_filter.should == {"fact" => [], "cf_class" => [], "agent" => [], "identity" => [], "compound" => []}
+      end
+    end
+
+    describe "#default_options" do
+      it "should supply correct default options" do
+        Config.instance.stubs(:default_discovery_options).returns([])
+        empty_filter = Util.empty_filter
+        config_file = Util.config_file_for_user
+
+        Util.default_options.should == {:verbose => false, :disctimeout => nil, :timeout => 5, :config => config_file, :filter => empty_filter, :collective => nil, :discovery_method => nil, :discovery_options => []}
+      end
+    end
+
+    describe "#has_fact?" do
+      it "should handle missing facts correctly" do
+        MCollective::Facts.expects("[]").with("foo").returns(nil).once
+        Util.has_fact?("foo", "1", "==").should == false
+      end
+
+      it "should handle regex in a backward compatible way" do
+        MCollective::Facts.expects("[]").with("foo").returns("foo").times(6)
+        Util.has_fact?("foo", "foo", "=~").should == true
+        Util.has_fact?("foo", "/foo/", "=~").should == true
+        Util.has_fact?("foo", "foo", "=~").should == true
+        Util.has_fact?("foo", "bar", "=~").should == false
+        Util.has_fact?("foo", "/bar/", "=~").should == false
+        Util.has_fact?("foo", "bar", "=~").should == false
+      end
+
+      it "should evaluate equality" do
+        MCollective::Facts.expects("[]").with("foo").returns("foo").twice
+        Util.has_fact?("foo", "foo", "==").should == true
+        Util.has_fact?("foo", "bar", "==").should == false
+      end
+
+      it "should handle numeric comparisons correctly" do
+        MCollective::Facts.expects("[]").with("foo").returns("1").times(8)
+        Util.has_fact?("foo", "2", ">=").should == false
+        Util.has_fact?("foo", "1", ">=").should == true
+        Util.has_fact?("foo", "2", "<=").should == true
+        Util.has_fact?("foo", "1", "<=").should == true
+        Util.has_fact?("foo", "1", "<").should == false
+        Util.has_fact?("foo", "1", ">").should == false
+        Util.has_fact?("foo", "1", "!=").should == false
+        Util.has_fact?("foo", "2", "!=").should == true
+      end
+
+      it "should handle alphabetic comparisons correctly" do
+        MCollective::Facts.expects("[]").with("foo").returns("b").times(8)
+        Util.has_fact?("foo", "c", ">=").should == false
+        Util.has_fact?("foo", "a", ">=").should == true
+        Util.has_fact?("foo", "a", "<=").should == false
+        Util.has_fact?("foo", "b", "<=").should == true
+        Util.has_fact?("foo", "b", "<").should == false
+        Util.has_fact?("foo", "b", ">").should == false
+        Util.has_fact?("foo", "b", "!=").should == false
+        Util.has_fact?("foo", "a", "!=").should == true
+      end
+    end
+
+    describe "#parse_fact_string" do
+      it "should parse old style regex fact matches" do
+        Util.parse_fact_string("foo=/bar/").should == {:fact => "foo", :value => "/bar/", :operator => "=~"}
+        Util.parse_fact_string("foo = /bar/").should == {:fact => "foo", :value => "/bar/", :operator => "=~"}
+      end
+
+      it "should parse old style equality" do
+        Util.parse_fact_string("foo=bar").should == {:fact => "foo", :value => "bar", :operator => "=="}
+        Util.parse_fact_string("foo = bar").should == {:fact => "foo", :value => "bar", :operator => "=="}
+      end
+
+      it "should parse regex fact matches" do
+        Util.parse_fact_string("foo=~bar").should == {:fact => "foo", :value => "bar", :operator => "=~"}
+        Util.parse_fact_string("foo =~ bar").should == {:fact => "foo", :value => "bar", :operator => "=~"}
+      end
+
+      it "should treat => like >=" do
+        Util.parse_fact_string("foo=>bar").should == {:fact => "foo", :value => "bar", :operator => ">="}
+        Util.parse_fact_string("foo => bar").should == {:fact => "foo", :value => "bar", :operator => ">="}
+      end
+
+      it "should treat =< like <=" do
+        Util.parse_fact_string("foo=<bar").should == {:fact => "foo", :value => "bar", :operator => "<="}
+        Util.parse_fact_string("foo =< bar").should == {:fact => "foo", :value => "bar", :operator => "<="}
+      end
+
+      it "should parse less than or equal" do
+        Util.parse_fact_string("foo<=bar").should == {:fact => "foo", :value => "bar", :operator => "<="}
+        Util.parse_fact_string("foo <= bar").should == {:fact => "foo", :value => "bar", :operator => "<="}
+      end
+
+      it "should parse greater than or equal" do
+        Util.parse_fact_string("foo>=bar").should == {:fact => "foo", :value => "bar", :operator => ">="}
+        Util.parse_fact_string("foo >= bar").should == {:fact => "foo", :value => "bar", :operator => ">="}
+      end
+
+      it "should parse less than" do
+        Util.parse_fact_string("foo<bar").should == {:fact => "foo", :value => "bar", :operator => "<"}
+        Util.parse_fact_string("foo < bar").should == {:fact => "foo", :value => "bar", :operator => "<"}
+      end
+
+      it "should parse greater than" do
+        Util.parse_fact_string("foo>bar").should == {:fact => "foo", :value => "bar", :operator => ">"}
+        Util.parse_fact_string("foo > bar").should == {:fact => "foo", :value => "bar", :operator => ">"}
+      end
+
+      it "should parse greater than" do
+        Util.parse_fact_string("foo>bar").should == {:fact => "foo", :value => "bar", :operator => ">"}
+        Util.parse_fact_string("foo > bar").should == {:fact => "foo", :value => "bar", :operator => ">"}
+      end
+
+      it "should parse not equal" do
+        Util.parse_fact_string("foo!=bar").should == {:fact => "foo", :value => "bar", :operator => "!="}
+        Util.parse_fact_string("foo != bar").should == {:fact => "foo", :value => "bar", :operator => "!="}
+      end
+
+      it "should parse equal to" do
+        Util.parse_fact_string("foo==bar").should == {:fact => "foo", :value => "bar", :operator => "=="}
+        Util.parse_fact_string("foo == bar").should == {:fact => "foo", :value => "bar", :operator => "=="}
+      end
+
+      it "should fail for facts in the wrong format" do
+        expect {
+          Util.parse_fact_string("foo")
+        }.to raise_error("Could not parse fact foo it does not appear to be in a valid format")
+      end
+    end
+
+    describe "#colorize" do
+      it "should not add color codes when color is disabled" do
+        Config.instance.stubs(:color).returns(false)
+        Util.colorize(:red, "hello world").should == "hello world"
+      end
+
+      it "should add color when color is enabled" do
+        Config.instance.stubs(:color).returns(true)
+        Util.colorize(:red, "hello world").should == "\e[31mhello world\e[0m"
+      end
+    end
+
+    describe "#align_text" do
+      it "should default to 80 if the terminal dimensions are unknown" do
+        Util.stubs(:terminal_dimensions).returns([0,0])
+
+        rootdir = File.dirname(__FILE__)
+        input = File.read("#{rootdir}/../fixtures/util/4.in")
+        output = File.read("#{rootdir}/../fixtures/util/4.out")
+
+        (Util.align_text(input, nil, 3) + "\n").should == output
+      end
+
+      it "should return the origional string if console lines are 0" do
+        result = Util.align_text("test", 0)
+        result.should == "test"
+      end
+
+      it "should return the origional string if preamble is greater than console lines" do
+        result = Util.align_text("test", 5, 6)
+        result.should == "test"
+      end
+
+      it "should return a string prefixed by the preamble" do
+        result = Util.align_text("test")
+        result.should == "     test"
+      end
+
+      it "should correctly align strings" do
+        rootdir = File.dirname(__FILE__)
+        (1..2).each do |i|
+          input = File.read("#{rootdir}/../fixtures/util/#{i}.in")
+          output = File.read("#{rootdir}/../fixtures/util/#{i}.out")
+
+          (Util.align_text(input, 158 , 5) + "\n").should == output
+        end
+
+        input = File.read("#{rootdir}/../fixtures/util/3.in")
+        output = File.read("#{rootdir}/../fixtures/util/3.out")
+
+        (Util.align_text(input, 30, 0) + "\n").should == output
+      end
+    end
+
+    describe "#terminal_dimensions" do
+      it "should return 0 if there is no tty" do
+        stdout = mock()
+        stdout.expects(:tty?).returns(false)
+        result = Util.terminal_dimensions(stdout)
+        result.should == [0,0]
+      end
+
+      it "should return the default dimensions for a windows terminal" do
+        stdout = mock()
+        stdout.expects(:tty?).returns(true)
+        Util.expects(:windows?).returns(true)
+        result = Util.terminal_dimensions(stdout)
+        result.should == [80, 40]
+      end
+
+      it "should return 0 if an exception was raised" do
+        stdout = mock()
+        stdout.expects(:tty?).raises("error")
+        result = Util.terminal_dimensions(stdout)
+        result.should == [0, 0]
+      end
+
+      it "should return the correct dimensions if ENV columns and lines are set" do
+        stdout = mock()
+        stdout.expects(:tty?).returns(true)
+        environment = mock()
+        environment.expects(:[]).with("COLUMNS").returns(5).twice
+        environment.expects(:[]).with("LINES").returns(5).twice
+        result = Util.terminal_dimensions(stdout, environment)
+        result.should == [5,5]
+      end
+
+      it "should return the correct dimensions if ENV term is set and tput is present" do
+        stdout = mock()
+        stdout.expects(:tty?).returns(true)
+        environment = mock()
+        environment.expects(:[]).with("COLUMNS").returns(false)
+        environment.expects(:[]).with("TERM").returns(true)
+
+        Util.expects(:command_in_path?).with("tput").returns(true)
+        Util.stubs(:`).returns("5")
+
+        result = Util.terminal_dimensions(stdout, environment)
+        result.should == [5,5]
+      end
+
+      it "should return the correct dimensions if stty is present" do
+        stdout = mock()
+        stdout.expects(:tty?).returns(true)
+
+        environment = mock()
+        environment.expects(:[]).with("COLUMNS").returns(false)
+        environment.expects(:[]).with("TERM").returns(false)
+
+        Util.expects(:command_in_path?).with("stty").returns(true)
+        Util.stubs(:`).returns("5 5")
+
+        result = Util.terminal_dimensions(stdout, environment)
+        result.should == [5,5]
+      end
+    end
+
+    describe "#command_in_path?" do
+      it "should return true if the command is found" do
+        File.stubs(:exist?).returns(true)
+        result = Util.command_in_path?("test")
+        result.should == true
+      end
+
+      it "should return false if the command cannot be found" do
+        File.stubs(:exist?).returns(false)
+        result = Util.command_in_path?("test")
+        result.should == false
+      end
+    end
+
+    describe "#absolute_path?" do
+      it "should work correctly validate the path" do
+        Util.absolute_path?('.', '/', '\\').should == false
+        Util.absolute_path?('foo/foo', '/', '\\').should == false
+        Util.absolute_path?('foo\\bar', '/', '\\').should == false
+        Util.absolute_path?('../foo/bar', '/', '\\').should == false
+
+        Util.absolute_path?('\\foo/foo', '/', '\\').should == true
+        Util.absolute_path?('\\', '/', '\\').should == true
+        Util.absolute_path?('/foo', '/', '\\').should == true
+        Util.absolute_path?('/foo/foo', '/', '\\').should == true
+
+        Util.absolute_path?('.', '/', nil).should == false
+        Util.absolute_path?('foo/foo', '/', nil).should == false
+        Util.absolute_path?('foo\\bar', '/', nil).should == false
+        Util.absolute_path?('../foo/bar', '/', nil).should == false
+
+        Util.absolute_path?('\\foo/foo', '/', nil).should == false
+        Util.absolute_path?('\\', '/', nil).should == false
+        Util.absolute_path?('/foo', '/', nil).should == true
+        Util.absolute_path?('/foo/foo', '/', nil).should == true
+      end
+    end
+
+    describe "#versioncmp" do
+      it "should be able to sort a long set of various unordered versions" do
+        ary = %w{ 1.1.6 2.3 1.1a 3.0 1.5 1 2.4 1.1-4 2.3.1 1.2 2.3.0 1.1-3 2.4b 2.4 2.40.2 2.3a.1 3.1 0002 1.1-5 1.1.a 1.06}
+
+        newary = ary.sort {|a, b| Util.versioncmp(a,b) }
+
+        newary.should == ["0002", "1", "1.06", "1.1-3", "1.1-4", "1.1-5", "1.1.6", "1.1.a", "1.1a", "1.2", "1.5", "2.3", "2.3.0", "2.3.1", "2.3a.1", "2.4", "2.4", "2.4b", "2.40.2", "3.0", "3.1"]
+      end
+    end
+  end
+end