Updated mcollective.init according to OSCI-658
[packages/precise/mcollective.git] / spec / unit / application_spec.rb
diff --git a/spec/unit/application_spec.rb b/spec/unit/application_spec.rb
new file mode 100755 (executable)
index 0000000..9b1c434
--- /dev/null
@@ -0,0 +1,653 @@
+#!/usr/bin/env rspec
+
+require 'spec_helper'
+
+module MCollective
+  describe Application do
+    before do
+      Application.intialize_application_options
+      @argv_backup = ARGV.clone
+    end
+
+    describe "#application_options" do
+      it "should return the application options" do
+        Application.application_options.should == {:description          => nil,
+                                                   :usage                => [],
+                                                   :cli_arguments        => [],
+                                                   :exclude_arg_sections => []}
+      end
+    end
+
+    describe "#[]=" do
+      it "should set the application option" do
+        Application["foo"] = "bar"
+        Application.application_options["foo"].should == "bar"
+      end
+    end
+
+    describe "#[]" do
+      it "should set the application option" do
+        Application[:cli_arguments].should == []
+      end
+    end
+
+    describe "#intialize_application_options" do
+      it "should initialize application options correctly" do
+        Application.intialize_application_options.should == {:description          => nil,
+                                                             :usage                => [],
+                                                             :cli_arguments        => [],
+                                                             :exclude_arg_sections => []}
+      end
+    end
+
+    describe "#description" do
+      it "should set the description correctly" do
+        Application.description "meh"
+        Application[:description].should == "meh"
+      end
+    end
+
+    describe "#usage" do
+      it "should set the usage correctly" do
+        Application.usage "meh"
+        Application.usage "foo"
+
+        Application[:usage].should == ["meh", "foo"]
+      end
+    end
+
+    describe "#exclude_argument_sections" do
+      it "should set the excluded sections correctly" do
+        Application.exclude_argument_sections "common", "rpc", "filter"
+        Application[:exclude_arg_sections].should == ["common", "rpc", "filter"]
+        Application.exclude_argument_sections ["common", "rpc", "filter"]
+        Application[:exclude_arg_sections].should == ["common", "rpc", "filter"]
+      end
+
+      it "should detect unknown sections" do
+        expect { Application.exclude_argument_sections "rspec" }.to raise_error("Unknown CLI argument section rspec")
+      end
+    end
+
+    describe "#option" do
+      it "should add an option correctly" do
+        Application.option :test,
+                           :description => "description",
+                           :arguments => "--config CONFIG",
+                           :type => Integer,
+                           :required => true
+
+        args = Application[:cli_arguments].first
+        args.delete(:validate)
+
+        args.should == {:name=>:test,
+                        :arguments=>"--config CONFIG",
+                        :required=>true,
+                        :type=>Integer,
+                        :description=>"description"}
+      end
+
+      it "should set correct defaults" do
+        Application.option :test, {}
+
+        args = Application[:cli_arguments].first
+        args.delete(:validate)
+
+        args.should == {:name=>:test,
+                        :arguments=>[],
+                        :required=>false,
+                        :type=>String,
+                        :description=>nil}
+      end
+    end
+
+    describe "#validate_option" do
+      it "should pass validations" do
+        a = Application.new
+        a.validate_option(Proc.new {|v| v == 1}, "key", 1)
+      end
+
+      it "should print an error to STDERR on error" do
+        IO.any_instance.expects(:puts).with("Validation of key failed: failed").at_least_once
+        Application.any_instance.stubs("exit").returns(true)
+
+        a = Application.new
+        a.validate_option(Proc.new {|v| "failed"}, "key", 1)
+      end
+
+      it "should exit on valdation error" do
+        IO.any_instance.expects(:puts).at_least_once
+        Application.any_instance.stubs("exit").returns(true)
+
+        a = Application.new
+        a.validate_option(Proc.new {|v| "failed"}, "key", 1)
+      end
+    end
+
+    describe "#application_parse_options" do
+      it "should pass the requested help value to the clioptions method" do
+        ARGV.clear
+
+        app = Application.new
+        app.expects(:clioptions).with(true)
+        app.application_parse_options(true)
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should support creating arrays of values" do
+        Application.any_instance.stubs("main").returns(true)
+
+        Application.option :foo,
+                           :description => "meh",
+                           :arguments => "--foo [FOO]",
+                           :type => :array
+
+        ARGV.clear
+        ARGV << "--foo=bar" << "--foo=baz"
+
+        a = Application.new
+        a.run
+        a.configuration.should == {:foo=>["bar", "baz"]}
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should support boolean options" do
+        Application.any_instance.stubs("main").returns(true)
+
+        Application.option :foo,
+                           :description => "meh",
+                           :arguments => "--foo",
+                           :type => :boolean
+
+        ARGV.clear
+        ARGV << "--foo"
+
+        a = Application.new
+        a.run
+        a.configuration.should == {:foo=>true}
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should support unsetting boolean options" do
+        Application.any_instance.stubs("main").returns(true)
+
+        Application.option :foo,
+                           :description => "meh",
+                           :arguments => "--[no-]foo",
+                           :type => :boolean
+
+        ARGV.clear
+        ARGV << "--no-foo"
+
+        a = Application.new
+        a.run
+        a.configuration.should == {:foo=>false}
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should set the application description as head" do
+        OptionParser.any_instance.stubs(:define_head).with("meh")
+
+        ARGV.clear
+
+        Application.description "meh"
+        Application.new.application_parse_options
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should set the application usage as a banner" do
+        OptionParser.any_instance.stubs(:banner).with("meh")
+
+        ARGV.clear
+
+        Application.usage "meh"
+        Application.new.application_parse_options
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should support validation" do
+        IO.any_instance.expects(:puts).with("Validation of foo failed: failed").at_least_once
+        Application.any_instance.stubs("exit").returns(true)
+        Application.any_instance.stubs("main").returns(true)
+
+        Application.option :foo,
+                           :description => "meh",
+                           :required => true,
+                           :default => "meh",
+                           :arguments => "--foo [FOO]",
+                           :validate => Proc.new {|v| "failed"}
+
+        ARGV.clear
+        ARGV << "--foo=bar"
+
+        a = Application.new
+        a.run
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should support default values" do
+        Application.any_instance.stubs("main").returns(true)
+
+        Application.option :foo,
+                           :description => "meh",
+                           :required => true,
+                           :default => "meh",
+                           :arguments => "--foo [FOO]"
+
+        a = Application.new
+        a.run
+        a.configuration.should == {:foo => "meh"}
+      end
+
+      it "should enforce required options" do
+        Application.any_instance.stubs("exit").returns(true)
+        Application.any_instance.stubs("main").returns(true)
+        OptionParser.any_instance.stubs("parse!").returns(true)
+        IO.any_instance.expects(:puts).with(anything).at_least_once
+        IO.any_instance.expects(:puts).with("The foo option is mandatory").at_least_once
+
+        ARGV.clear
+        ARGV << "--foo=bar"
+
+        Application.option :foo,
+                           :description => "meh",
+                           :required => true,
+                           :arguments => "--foo [FOO]"
+
+        Application.new.run
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should call post_option_parser" do
+        OptionParser.any_instance.stubs("parse!").returns(true)
+        Application.any_instance.stubs("post_option_parser").returns(true).at_least_once
+        Application.any_instance.stubs("main").returns(true)
+
+        ARGV.clear
+        ARGV << "--foo=bar"
+
+        Application.option :foo,
+                           :description => "meh",
+                           :arguments => "--foo [FOO]"
+
+        Application.new.run
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+
+      it "should create an application option" do
+        OptionParser.any_instance.stubs("parse!").returns(true)
+        OptionParser.any_instance.expects(:on).with(anything, anything, anything, anything).at_least_once
+        OptionParser.any_instance.expects(:on).with('--foo [FOO]', String, 'meh').at_least_once
+        Application.any_instance.stubs("main").returns(true)
+
+        ARGV.clear
+        ARGV << "--foo=bar"
+
+        Application.option :foo,
+                           :description => "meh",
+                           :arguments => "--foo [FOO]"
+
+        Application.new.run
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+    end
+
+    describe "#initialize" do
+      it "should parse the command line options at application run" do
+        Application.any_instance.expects("application_parse_options").once
+        Application.any_instance.stubs("main").returns(true)
+
+        Application.new.run
+      end
+    end
+
+    describe "#application_options" do
+      it "sshould return the application options" do
+        Application.new.application_options.should == Application.application_options
+      end
+    end
+
+    describe "#application_description" do
+      it "should provide the right description" do
+        Application.description "Foo"
+        Application.new.application_description.should == "Foo"
+      end
+    end
+
+    describe "#application_usage" do
+      it "should provide the right usage" do
+        Application.usage "Foo"
+        Application.new.application_usage.should == ["Foo"]
+      end
+    end
+
+    describe "#application_cli_arguments" do
+      it "should provide the right usage" do
+        Application.option :foo,
+                           :description => "meh",
+                           :arguments => "--foo [FOO]"
+
+        args = Application.new.application_cli_arguments.first
+
+        # need to remove this cos we cant validate procs for equality afaik
+        args.delete(:validate)
+
+        args.should == {:description=>"meh",
+                        :name=>:foo,
+                        :arguments=>"--foo [FOO]",
+                        :type=>String,
+                        :required=>false}
+      end
+    end
+
+    describe "#help" do
+      it "should generate help using the full user supplied options" do
+        app = Application.new
+        app.expects(:clioptions).with(true).once
+        app.help
+      end
+    end
+
+    describe "#main" do
+      it "should detect applications without a #main" do
+        IO.any_instance.expects(:puts).with("Applications need to supply a 'main' method")
+
+        expect {
+          Application.new.run
+        }.to raise_error(SystemExit)
+      end
+
+      it "should raise SystemExit exceptions for exit events" do
+        connector = mock
+        connector.expects(:disconnect)
+        PluginManager.expects("[]").with("connector_plugin").returns(connector)
+
+        a = Application.new
+        a.expects(:main).raises(SystemExit)
+
+        expect {
+          a.run
+        }.to raise_error(SystemExit)
+      end
+    end
+
+    describe "#configuration" do
+      it "should return the correct configuration" do
+        Application.any_instance.stubs("main").returns(true)
+
+        ARGV.clear
+        ARGV << "--foo=bar"
+
+        Application.option :foo,
+                           :description => "meh",
+                           :arguments => "--foo [FOO]"
+
+        a = Application.new
+        a.run
+
+        a.configuration.should == {:foo => "bar"}
+
+        ARGV.clear
+        @argv_backup.each{|a| ARGV << a}
+      end
+    end
+
+    describe "#halt" do
+      before do
+        @stats = {:discoverytime => 0, :discovered => 0, :failcount => 0, :responses => 0}
+      end
+
+      it "should exit with code 0 if discovery was done and all responses passed" do
+        app = Application.new
+
+        @stats[:discoverytime] = 2
+        @stats[:discovered] = 2
+        @stats[:responses] = 2
+
+        app.expects(:exit).with(0)
+
+        app.halt(@stats)
+      end
+
+      it "should exit with code 0 if no discovery were done but responses were received" do
+        app = Application.new
+
+        @stats[:responses] = 1
+
+        app.expects(:exit).with(0)
+
+        app.halt(@stats)
+      end
+
+      it "should exit with code 0 if discovery info is missing" do
+        app = Application.new
+
+        app.expects(:exit).with(0)
+
+        app.halt({})
+      end
+
+      it "should exit with code 1 if no nodes were discovered and discovery was done" do
+        app = Application.new
+
+        @stats[:discoverytime] = 2
+
+        app.expects(:exit).with(1)
+
+        app.halt(@stats)
+      end
+
+      it "should exit with code 2 if a request failed for some nodes" do
+        app = Application.new
+
+        @stats[:discovered] = 1
+        @stats[:failcount] = 1
+        @stats[:discoverytime] = 2
+        @stats[:responses] = 1
+
+        app.expects(:exit).with(2)
+
+        app.halt(@stats)
+      end
+
+      it "should exit with code 3 if no responses were received after discovery" do
+        app = Application.new
+
+        @stats[:discovered] = 1
+        @stats[:discoverytime] = 2
+
+        app.expects(:exit).with(3)
+
+        app.halt(@stats)
+      end
+
+      it "should exit with code 4 if no discovery was done and no responses were received" do
+        app = Application.new
+
+        app.expects(:exit).with(4)
+
+        app.halt(@stats)
+      end
+    end
+
+    describe "#disconnect" do
+      it "should disconnect from the connector plugin" do
+        connector = mock
+        connector.expects(:disconnect)
+        PluginManager.expects("[]").with("connector_plugin").returns(connector)
+
+        Application.new.disconnect
+      end
+    end
+
+    describe "#clioptions" do
+      it "should pass the excluded argument section" do
+        oparser = mock
+        oparser.stubs(:parse)
+
+        Application.exclude_argument_sections "rpc"
+
+        Optionparser.expects(:new).with({:verbose => false, :progress_bar => true}, "filter", ["rpc"]).returns(oparser)
+
+        Application.new.clioptions(false)
+      end
+
+      it "should add the RPC options" do
+        oparser = mock
+        oparser.stubs(:parse).yields(oparser, {})
+        oparser.stubs(:banner=)
+        oparser.stubs(:define_tail)
+
+        Optionparser.stubs(:new).with({:verbose => false, :progress_bar => true}, "filter", []).returns(oparser)
+        RPC::Helpers.expects(:add_simplerpc_options).with(oparser, {})
+
+        Application.new.clioptions(false)
+      end
+
+      it "should support bypassing the RPC options" do
+        oparser = mock
+        oparser.stubs(:parse).yields(oparser, {})
+        oparser.stubs(:banner=)
+        oparser.stubs(:define_tail)
+
+        Application.exclude_argument_sections "rpc"
+
+        Optionparser.stubs(:new).with({:verbose => false, :progress_bar => true}, "filter", ["rpc"]).returns(oparser)
+        RPC::Helpers.expects(:add_simplerpc_options).never
+
+        Application.new.clioptions(false)
+      end
+
+      it "should return the help text if requested" do
+        parser = mock
+        parser.expects(:help)
+
+        oparser = mock
+        oparser.stubs(:parse).yields(oparser, {})
+        oparser.stubs(:banner=)
+        oparser.stubs(:define_tail)
+        oparser.expects(:parser).returns(parser)
+
+        Optionparser.stubs(:new).with({:verbose => false, :progress_bar => true}, "filter", []).returns(oparser)
+        RPC::Helpers.expects(:add_simplerpc_options).with(oparser, {})
+
+        Application.new.clioptions(true)
+      end
+    end
+
+    describe "#application_failure" do
+      before do
+        @app = Application.new
+      end
+
+      it "on SystemExit it should disconnect and exit without backtraces or error messages" do
+        @app.expects(:disconnect)
+        expect { @app.application_failure(SystemExit.new) }.to raise_error(SystemExit)
+      end
+
+      it "should print a single line error message" do
+        out = StringIO.new
+        @app.stubs(:disconnect)
+        @app.stubs(:exit).with(1)
+        @app.stubs(:options).returns({})
+
+        Config.instance.stubs(:color).returns(false)
+        e = mock
+        e.stubs(:backtrace).returns([])
+        e.stubs(:to_s).returns("rspec")
+
+        out.expects(:puts).with(regexp_matches(/rspec application failed to run/))
+
+        @app.application_failure(e, out)
+      end
+
+      it "should print a backtrace if options are unset or verbose is enabled" do
+        out = StringIO.new
+        @app.stubs(:disconnect)
+        @app.stubs(:exit).with(1)
+        @app.stubs(:options).returns(nil)
+
+        Config.instance.stubs(:color).returns(false)
+        e = mock
+        e.stubs(:backtrace).returns(["rspec"])
+        e.stubs(:to_s).returns("rspec")
+
+        @app.expects(:options).returns({:verbose => true}).times(3)
+        out.expects(:puts).with(regexp_matches(/ application failed to run/))
+        out.expects(:puts).with(regexp_matches(/from rspec  <---/))
+        out.expects(:puts).with(regexp_matches(/rspec.+Mocha::Mock/))
+
+        @app.application_failure(e, out)
+      end
+    end
+
+    describe "#run" do
+      before do
+        @app = Application.new
+      end
+
+      it "should parse the application options, run main and disconnect" do
+        @app.expects(:application_parse_options)
+        @app.expects(:main)
+        @app.expects(:disconnect)
+
+        @app.run
+      end
+
+      it "should allow the application plugin to validate configuration variables" do
+        @app.expects("respond_to?").with(:validate_configuration).returns(true)
+        @app.expects(:validate_configuration).once
+
+        @app.stubs(:application_parse_options)
+        @app.stubs(:main)
+        @app.stubs(:disconnect)
+
+        @app.run
+      end
+
+      it "should start the sleeper thread on windows" do
+        Util.expects("windows?").returns(true)
+        Util.expects(:setup_windows_sleeper).once
+
+        @app.stubs(:application_parse_options)
+        @app.stubs(:main)
+        @app.stubs(:disconnect)
+
+        @app.run
+      end
+
+      it "should catch handle exit() correctly" do
+        @app.expects(:main).raises(SystemExit)
+        @app.expects(:disconnect).once
+
+        expect { @app.run }.to raise_error(SystemExit)
+      end
+
+      it "should catch all exceptions and process them correctly" do
+        @app.expects(:main).raises("rspec")
+        @app.expects(:application_failure).once
+        @app.run
+      end
+    end
+  end
+end