d2e64eb137e82563369535a8cf1d0f7948963950
[packages/precise/mcollective.git] / spec / unit / application_spec.rb
1 #!/usr/bin/env rspec
2
3 require 'spec_helper'
4
5 module MCollective
6   describe Application do
7     before do
8       Application.intialize_application_options
9       @argv_backup = ARGV.clone
10     end
11
12     describe "#application_options" do
13       it "should return the application options" do
14         Application.application_options.should == {:description          => nil,
15                                                    :usage                => [],
16                                                    :cli_arguments        => [],
17                                                    :exclude_arg_sections => []}
18       end
19     end
20
21     describe "#[]=" do
22       it "should set the application option" do
23         Application["foo"] = "bar"
24         Application.application_options["foo"].should == "bar"
25       end
26     end
27
28     describe "#[]" do
29       it "should set the application option" do
30         Application[:cli_arguments].should == []
31       end
32     end
33
34     describe "#intialize_application_options" do
35       it "should initialize application options correctly" do
36         Application.intialize_application_options.should == {:description          => nil,
37                                                              :usage                => [],
38                                                              :cli_arguments        => [],
39                                                              :exclude_arg_sections => []}
40       end
41     end
42
43     describe "#description" do
44       it "should set the description correctly" do
45         Application.description "meh"
46         Application[:description].should == "meh"
47       end
48     end
49
50     describe "#usage" do
51       it "should set the usage correctly" do
52         Application.usage "meh"
53         Application.usage "foo"
54
55         Application[:usage].should == ["meh", "foo"]
56       end
57     end
58
59     describe "#exclude_argument_sections" do
60       it "should set the excluded sections correctly" do
61         Application.exclude_argument_sections "common", "rpc", "filter"
62         Application[:exclude_arg_sections].should == ["common", "rpc", "filter"]
63         Application.exclude_argument_sections ["common", "rpc", "filter"]
64         Application[:exclude_arg_sections].should == ["common", "rpc", "filter"]
65       end
66
67       it "should detect unknown sections" do
68         expect { Application.exclude_argument_sections "rspec" }.to raise_error("Unknown CLI argument section rspec")
69       end
70     end
71
72     describe "#option" do
73       it "should add an option correctly" do
74         Application.option :test,
75                            :description => "description",
76                            :arguments => "--config CONFIG",
77                            :type => Integer,
78                            :required => true
79
80         args = Application[:cli_arguments].first
81         args.delete(:validate)
82
83         args.should == {:name=>:test,
84                         :arguments=>"--config CONFIG",
85                         :required=>true,
86                         :type=>Integer,
87                         :description=>"description"}
88       end
89
90       it "should set correct defaults" do
91         Application.option :test, {}
92
93         args = Application[:cli_arguments].first
94         args.delete(:validate)
95
96         args.should == {:name=>:test,
97                         :arguments=>[],
98                         :required=>false,
99                         :type=>String,
100                         :description=>nil}
101       end
102     end
103
104     describe "#validate_option" do
105       it "should pass validations" do
106         a = Application.new
107         a.validate_option(Proc.new {|v| v == 1}, "key", 1)
108       end
109
110       it "should print an error to STDERR on error" do
111         IO.any_instance.expects(:puts).with("Validation of key failed: failed").at_least_once
112         Application.any_instance.stubs("exit").returns(true)
113
114         a = Application.new
115         a.validate_option(Proc.new {|v| "failed"}, "key", 1)
116       end
117
118       it "should exit on valdation error" do
119         IO.any_instance.expects(:puts).at_least_once
120         Application.any_instance.stubs("exit").returns(true)
121
122         a = Application.new
123         a.validate_option(Proc.new {|v| "failed"}, "key", 1)
124       end
125     end
126
127     describe "#application_parse_options" do
128       it "should pass the requested help value to the clioptions method" do
129         ARGV.clear
130
131         app = Application.new
132         app.expects(:clioptions).with(true)
133         app.application_parse_options(true)
134
135         ARGV.clear
136         @argv_backup.each{|a| ARGV << a}
137       end
138
139       it "should support creating arrays of values" do
140         Application.any_instance.stubs("main").returns(true)
141
142         Application.option :foo,
143                            :description => "meh",
144                            :arguments => "--foo [FOO]",
145                            :type => :array
146
147         ARGV.clear
148         ARGV << "--foo=bar" << "--foo=baz"
149
150         a = Application.new
151         a.run
152         a.configuration.should == {:foo=>["bar", "baz"]}
153
154         ARGV.clear
155         @argv_backup.each{|a| ARGV << a}
156       end
157
158       it "should support boolean options" do
159         Application.any_instance.stubs("main").returns(true)
160
161         Application.option :foo,
162                            :description => "meh",
163                            :arguments => "--foo",
164                            :type => :boolean
165
166         ARGV.clear
167         ARGV << "--foo"
168
169         a = Application.new
170         a.run
171         a.configuration.should == {:foo=>true}
172
173         ARGV.clear
174         @argv_backup.each{|a| ARGV << a}
175       end
176
177       it "should support unsetting boolean options" do
178         Application.any_instance.stubs("main").returns(true)
179
180         Application.option :foo,
181                            :description => "meh",
182                            :arguments => "--[no-]foo",
183                            :type => :boolean
184
185         ARGV.clear
186         ARGV << "--no-foo"
187
188         a = Application.new
189         a.run
190         a.configuration.should == {:foo=>false}
191
192         ARGV.clear
193         @argv_backup.each{|a| ARGV << a}
194       end
195
196       it "should set the application description as head" do
197         OptionParser.any_instance.stubs(:define_head).with("meh")
198
199         ARGV.clear
200
201         Application.description "meh"
202         Application.new.application_parse_options
203
204         ARGV.clear
205         @argv_backup.each{|a| ARGV << a}
206       end
207
208       it "should set the application usage as a banner" do
209         OptionParser.any_instance.stubs(:banner).with("meh")
210
211         ARGV.clear
212
213         Application.usage "meh"
214         Application.new.application_parse_options
215
216         ARGV.clear
217         @argv_backup.each{|a| ARGV << a}
218       end
219
220       it "should support validation" do
221         IO.any_instance.expects(:puts).with("Validation of foo failed: failed").at_least_once
222         Application.any_instance.stubs("exit").returns(true)
223         Application.any_instance.stubs("main").returns(true)
224
225         Application.option :foo,
226                            :description => "meh",
227                            :required => true,
228                            :default => "meh",
229                            :arguments => "--foo [FOO]",
230                            :validate => Proc.new {|v| "failed"}
231
232         ARGV.clear
233         ARGV << "--foo=bar"
234
235         a = Application.new
236         a.run
237
238         ARGV.clear
239         @argv_backup.each{|a| ARGV << a}
240       end
241
242       it "should support default values" do
243         Application.any_instance.stubs("main").returns(true)
244
245         Application.option :foo,
246                            :description => "meh",
247                            :required => true,
248                            :default => "meh",
249                            :arguments => "--foo [FOO]"
250
251         a = Application.new
252         a.run
253         a.configuration.should == {:foo => "meh"}
254       end
255
256       it "should enforce required options" do
257         Application.any_instance.stubs("exit").returns(true)
258         Application.any_instance.stubs("main").returns(true)
259         OptionParser.any_instance.stubs("parse!").returns(true)
260         IO.any_instance.expects(:puts).with(anything).at_least_once
261         IO.any_instance.expects(:puts).with("The foo option is mandatory").at_least_once
262
263         ARGV.clear
264         ARGV << "--foo=bar"
265
266         Application.option :foo,
267                            :description => "meh",
268                            :required => true,
269                            :arguments => "--foo [FOO]"
270
271         Application.new.run
272
273         ARGV.clear
274         @argv_backup.each{|a| ARGV << a}
275       end
276
277       it "should call post_option_parser" do
278         OptionParser.any_instance.stubs("parse!").returns(true)
279         Application.any_instance.stubs("post_option_parser").returns(true).at_least_once
280         Application.any_instance.stubs("main").returns(true)
281
282         ARGV.clear
283         ARGV << "--foo=bar"
284
285         Application.option :foo,
286                            :description => "meh",
287                            :arguments => "--foo [FOO]"
288
289         Application.new.run
290
291         ARGV.clear
292         @argv_backup.each{|a| ARGV << a}
293       end
294
295       it "should create an application option" do
296         OptionParser.any_instance.stubs("parse!").returns(true)
297         OptionParser.any_instance.expects(:on).with(anything, anything, anything, anything).at_least_once
298         OptionParser.any_instance.expects(:on).with('--foo [FOO]', String, 'meh').at_least_once
299         Application.any_instance.stubs("main").returns(true)
300
301         ARGV.clear
302         ARGV << "--foo=bar"
303
304         Application.option :foo,
305                            :description => "meh",
306                            :arguments => "--foo [FOO]"
307
308         Application.new.run
309
310         ARGV.clear
311         @argv_backup.each{|a| ARGV << a}
312       end
313     end
314
315     describe "#initialize" do
316       it "should parse the command line options at application run" do
317         Application.any_instance.expects("application_parse_options").once
318         Application.any_instance.stubs("main").returns(true)
319
320         Application.new.run
321       end
322     end
323
324     describe "#application_options" do
325       it "sshould return the application options" do
326         Application.new.application_options.should == Application.application_options
327       end
328     end
329
330     describe "#application_description" do
331       it "should provide the right description" do
332         Application.description "Foo"
333         Application.new.application_description.should == "Foo"
334       end
335     end
336
337     describe "#application_usage" do
338       it "should provide the right usage" do
339         Application.usage "Foo"
340         Application.new.application_usage.should == ["Foo"]
341       end
342     end
343
344     describe "#application_cli_arguments" do
345       it "should provide the right usage" do
346         Application.option :foo,
347                            :description => "meh",
348                            :arguments => "--foo [FOO]"
349
350         args = Application.new.application_cli_arguments.first
351
352         # need to remove this cos we cant validate procs for equality afaik
353         args.delete(:validate)
354
355         args.should == {:description=>"meh",
356                         :name=>:foo,
357                         :arguments=>"--foo [FOO]",
358                         :type=>String,
359                         :required=>false}
360       end
361     end
362
363     describe "#help" do
364       it "should generate help using the full user supplied options" do
365         app = Application.new
366         app.expects(:clioptions).with(true).once
367         app.help
368       end
369     end
370
371     describe "#main" do
372       it "should detect applications without a #main" do
373         IO.any_instance.expects(:puts).with("Applications need to supply a 'main' method")
374
375         expect {
376           Application.new.run
377         }.to raise_error(SystemExit)
378       end
379
380       it "should raise SystemExit exceptions for exit events" do
381         connector = mock
382         connector.expects(:disconnect)
383         PluginManager.expects("[]").with("connector_plugin").returns(connector)
384
385         a = Application.new
386         a.expects(:main).raises(SystemExit)
387
388         expect {
389           a.run
390         }.to raise_error(SystemExit)
391       end
392     end
393
394     describe "#configuration" do
395       it "should return the correct configuration" do
396         Application.any_instance.stubs("main").returns(true)
397
398         ARGV.clear
399         ARGV << "--foo=bar"
400
401         Application.option :foo,
402                            :description => "meh",
403                            :arguments => "--foo [FOO]"
404
405         a = Application.new
406         a.run
407
408         a.configuration.should == {:foo => "bar"}
409
410         ARGV.clear
411         @argv_backup.each{|a| ARGV << a}
412       end
413     end
414
415     describe "#halt" do
416       before do
417         @stats = {:discoverytime => 0, :discovered => 0, :failcount => 0, :responses => 0, :okcount => 0}
418       end
419
420       it "should exit with code 0 if discovery was done and all responses passed" do
421         app = Application.new
422
423         @stats[:discoverytime] = 2
424         @stats[:discovered] = 2
425         @stats[:responses] = 2
426         @stats[:okcount] = 2
427
428         app.halt_code(@stats).should == 0
429       end
430
431       it "should exit with code 0 if no discovery were done but responses were received" do
432         app = Application.new
433
434         @stats[:responses] = 1
435         @stats[:okcount] = 1
436         @stats[:discovered] = 1
437
438         app.halt_code(@stats).should == 0
439       end
440
441       it "should exit with code 1 if discovery info is missing" do
442         app = Application.new
443
444         app.halt_code({}).should == 1
445       end
446
447       it "should exit with code 1 if no nodes were discovered and discovery was done" do
448         app = Application.new
449
450         @stats[:discoverytime] = 2
451
452         app.halt_code(@stats).should == 1
453       end
454
455       it "should exit with code 2 if a request failed for some nodes" do
456         app = Application.new
457
458         @stats[:discovered] = 2
459         @stats[:failcount] = 1
460         @stats[:discoverytime] = 2
461         @stats[:responses] = 2
462
463         app.halt_code(@stats).should == 2
464       end
465
466       it "should exit with code 2 when no discovery were done and there were failure results" do
467         app = Application.new
468
469         @stats[:discovered] = 1
470         @stats[:failcount] = 1
471         @stats[:discoverytime] = 0
472         @stats[:responses] = 1
473
474         app.halt_code(@stats).should == 2
475       end
476
477       it "should exit with code 3 if no responses were received after discovery" do
478         app = Application.new
479
480         @stats[:discovered] = 1
481         @stats[:discoverytime] = 2
482
483         app.halt_code(@stats).should == 3
484       end
485
486       it "should exit with code 4 if no discovery was done and no responses were received" do
487         app = Application.new
488
489         app.halt_code(@stats).should == 4
490       end
491     end
492
493     describe "#disconnect" do
494       it "should disconnect from the connector plugin" do
495         connector = mock
496         connector.expects(:disconnect)
497         PluginManager.expects("[]").with("connector_plugin").returns(connector)
498
499         Application.new.disconnect
500       end
501     end
502
503     describe "#clioptions" do
504       it "should pass the excluded argument section" do
505         oparser = mock
506         oparser.stubs(:parse)
507
508         Application.exclude_argument_sections "rpc"
509
510         Optionparser.expects(:new).with({:verbose => false, :progress_bar => true}, "filter", ["rpc"]).returns(oparser)
511
512         Application.new.clioptions(false)
513       end
514
515       it "should add the RPC options" do
516         oparser = mock
517         oparser.stubs(:parse).yields(oparser, {})
518         oparser.stubs(:banner=)
519         oparser.stubs(:define_tail)
520
521         Optionparser.stubs(:new).with({:verbose => false, :progress_bar => true}, "filter", []).returns(oparser)
522         RPC::Helpers.expects(:add_simplerpc_options).with(oparser, {})
523
524         Application.new.clioptions(false)
525       end
526
527       it "should support bypassing the RPC options" do
528         oparser = mock
529         oparser.stubs(:parse).yields(oparser, {})
530         oparser.stubs(:banner=)
531         oparser.stubs(:define_tail)
532
533         Application.exclude_argument_sections "rpc"
534
535         Optionparser.stubs(:new).with({:verbose => false, :progress_bar => true}, "filter", ["rpc"]).returns(oparser)
536         RPC::Helpers.expects(:add_simplerpc_options).never
537
538         Application.new.clioptions(false)
539       end
540
541       it "should return the help text if requested" do
542         parser = mock
543         parser.expects(:help)
544
545         oparser = mock
546         oparser.stubs(:parse).yields(oparser, {})
547         oparser.stubs(:banner=)
548         oparser.stubs(:define_tail)
549         oparser.expects(:parser).returns(parser)
550
551         Optionparser.stubs(:new).with({:verbose => false, :progress_bar => true}, "filter", []).returns(oparser)
552         RPC::Helpers.expects(:add_simplerpc_options).with(oparser, {})
553
554         Application.new.clioptions(true)
555       end
556     end
557
558     describe "#application_failure" do
559       before do
560         @app = Application.new
561       end
562
563       it "on SystemExit it should disconnect and exit without backtraces or error messages" do
564         @app.expects(:disconnect)
565         expect { @app.application_failure(SystemExit.new) }.to raise_error(SystemExit)
566       end
567
568       it "should print a single line error message" do
569         out = StringIO.new
570         @app.stubs(:disconnect)
571         @app.stubs(:exit).with(1)
572         @app.stubs(:options).returns({})
573
574         Config.instance.stubs(:color).returns(false)
575         e = mock
576         e.stubs(:backtrace).returns([])
577         e.stubs(:to_s).returns("rspec")
578
579         out.expects(:puts).with(regexp_matches(/rspec application failed to run/))
580
581         @app.application_failure(e, out)
582       end
583
584       it "should print a backtrace if options are unset or verbose is enabled" do
585         out = StringIO.new
586         @app.stubs(:disconnect)
587         @app.stubs(:exit).with(1)
588         @app.stubs(:options).returns(nil)
589
590         Config.instance.stubs(:color).returns(false)
591         e = mock
592         e.stubs(:backtrace).returns(["rspec"])
593         e.stubs(:to_s).returns("rspec")
594
595         @app.expects(:options).returns({:verbose => true}).times(3)
596         out.expects(:puts).with(regexp_matches(/ application failed to run/))
597         out.expects(:puts).with(regexp_matches(/from rspec  <---/))
598         out.expects(:puts).with(regexp_matches(/rspec.+Mocha::Mock/))
599
600         @app.application_failure(e, out)
601       end
602     end
603
604     describe "#run" do
605       before do
606         @app = Application.new
607       end
608
609       it "should parse the application options, run main and disconnect" do
610         @app.expects(:application_parse_options)
611         @app.expects(:main)
612         @app.expects(:disconnect)
613
614         @app.run
615       end
616
617       it "should allow the application plugin to validate configuration variables" do
618         @app.expects("respond_to?").with(:validate_configuration).returns(true)
619         @app.expects(:validate_configuration).once
620
621         @app.stubs(:application_parse_options)
622         @app.stubs(:main)
623         @app.stubs(:disconnect)
624
625         @app.run
626       end
627
628       it "should start the sleeper thread on windows" do
629         Util.expects("windows?").returns(true)
630         Util.expects(:setup_windows_sleeper).once
631
632         @app.stubs(:application_parse_options)
633         @app.stubs(:main)
634         @app.stubs(:disconnect)
635
636         @app.run
637       end
638
639       it "should catch handle exit() correctly" do
640         @app.expects(:main).raises(SystemExit)
641         @app.expects(:disconnect).once
642
643         expect { @app.run }.to raise_error(SystemExit)
644       end
645
646       it "should catch all exceptions and process them correctly" do
647         @app.expects(:main).raises("rspec")
648         @app.expects(:application_failure).once
649         @app.run
650       end
651     end
652   end
653 end