Update mcollective.init according to OSCI-855
[packages/precise/mcollective.git] / spec / unit / util_spec.rb
1 #!/usr/bin/env rspec
2
3 require 'spec_helper'
4
5 module MCollective
6   describe Util do
7     before do
8       class MCollective::Connector::Stomp<MCollective::Connector::Base; end
9
10       PluginManager.clear
11       PluginManager << {:type => "connector_plugin", :class => MCollective::Connector::Stomp.new}
12     end
13
14     describe "#t" do
15       it "should correctly translate the message" do
16         I18n.expects(:t).with("PLMC1.pattern", {:rspec => "test"})
17         I18n.expects(:t).with("PLMC1.expanded", {:rspec => "test"})
18         Util.t(:PLMC1, :rspec => "test")
19         Util.t("PLMC1.expanded", :rspec => "test")
20       end
21     end
22
23     describe "#windows?" do
24       it "should correctly detect windows on unix platforms" do
25         RbConfig::CONFIG.expects("[]").returns("linux")
26         Util.windows?.should == false
27       end
28
29       it "should correctly detect windows on windows platforms" do
30         RbConfig::CONFIG.expects("[]").returns("win32")
31         Util.windows?.should == true
32       end
33     end
34
35     describe "#setup_windows_sleeper" do
36       it "should set up a thread on the windows platform" do
37         Thread.expects(:new)
38         Util.expects("windows?").returns(true).once
39         Util.setup_windows_sleeper
40       end
41
42       it "should not set up a thread on other platforms" do
43         Thread.expects(:new).never
44         Util.expects("windows?").returns(false).once
45         Util.setup_windows_sleeper
46       end
47     end
48
49     describe "#has_cf_class?" do
50       before do
51         logger = mock
52         logger.stubs(:log)
53         logger.stubs(:start)
54         Log.configure(logger)
55
56         config = mock
57         config.stubs(:classesfile).returns("/some/file")
58         Config.expects(:instance).returns(config)
59       end
60
61       it "should read the classes lines from the correct file" do
62         File.expects(:readlines).with("/some/file")
63
64         Util.has_cf_class?("test")
65       end
66
67       it "should support regular expression searches" do
68         File.stubs(:readlines).returns(["test_class_test"])
69         String.any_instance.expects(:match).with("^/").returns(true)
70         String.any_instance.expects(:match).with(Regexp.new("class")).returns(true)
71
72         Util.has_cf_class?("/class/").should == true
73       end
74
75       it "should support exact string matches" do
76         File.stubs(:readlines).returns(["test_class_test"])
77         String.any_instance.expects(:match).with("^/").returns(false)
78         String.any_instance.expects(:match).with(Regexp.new("test_class_test")).never
79
80         Util.has_cf_class?("test_class_test").should == true
81       end
82
83       it "should report a warning when the classes file cannot be parsed" do
84         File.stubs(:readlines).returns(nil)
85         Log.expects(:warn).with("Parsing classes file '/some/file' failed: NoMethodError: undefined method `each' for nil:NilClass")
86
87         Util.has_cf_class?("test_class_test").should == false
88       end
89     end
90
91     describe "#shellescape" do
92       it "should return '' for empty strings" do
93         Util.shellescape("").should == "''"
94       end
95
96       it "should quote newlines" do
97         Util.shellescape("\n").should == "'\n'"
98       end
99
100       it "should escape unwanted characters" do
101         Util.shellescape("foo;bar").should == 'foo\;bar'
102         Util.shellescape('foo`bar').should == 'foo\`bar'
103         Util.shellescape('foo$bar').should == 'foo\$bar'
104         Util.shellescape('foo|bar').should == 'foo\|bar'
105         Util.shellescape('foo&&bar').should == 'foo\&\&bar'
106         Util.shellescape('foo||bar').should == 'foo\|\|bar'
107         Util.shellescape('foo>bar').should == 'foo\>bar'
108         Util.shellescape('foo<bar').should == 'foo\<bar'
109         Util.shellescape('foobar').should == 'foobar'
110       end
111     end
112
113     describe "#make_subscription" do
114       it "should validate target types" do
115         expect {
116           Util.make_subscriptions("test", "test", "test")
117         }.to raise_error("Unknown target type test")
118
119         Config.instance.stubs(:collectives).returns(["test"])
120         Util.make_subscriptions("test", :broadcast, "test")
121       end
122
123       it "should return a subscription for each collective" do
124         Config.instance.stubs(:collectives).returns(["collective1", "collective2"])
125         Util.make_subscriptions("test", :broadcast).should == [{:type=>:broadcast,
126                                                                  :agent=>"test",
127                                                                  :collective=>"collective1"},
128                                                                {:type=>:broadcast,
129                                                                  :agent=>"test",
130                                                                  :collective=>"collective2"}]
131       end
132
133       it "should validate given collective" do
134         Config.instance.stubs(:collectives).returns(["collective1", "collective2"])
135
136         expect {
137           Util.make_subscriptions("test", :broadcast, "test")
138         }.to raise_error("Unknown collective 'test' known collectives are 'collective1, collective2'")
139       end
140
141       it "should return a single subscription array given a collective" do
142         Config.instance.stubs(:collectives).returns(["collective1", "collective2"])
143         Util.make_subscriptions("test", :broadcast, "collective1").should == [{:type=>:broadcast, :agent=>"test", :collective=>"collective1"}]
144       end
145     end
146
147     describe "#subscribe" do
148       it "should subscribe to multiple topics given an Array" do
149         subs1 = {:agent => "test_agent", :type => "test_type", :collective => "test_collective"}
150         subs2 = {:agent => "test_agent2", :type => "test_type2", :collective => "test_collective2"}
151
152         MCollective::Connector::Stomp.any_instance.expects(:subscribe).with("test_agent", "test_type", "test_collective").once
153         MCollective::Connector::Stomp.any_instance.expects(:subscribe).with("test_agent2", "test_type2", "test_collective2").once
154
155         Util.subscribe([subs1, subs2])
156       end
157
158       it "should subscribe to a single topic given a hash" do
159         MCollective::Connector::Stomp.any_instance.expects(:subscribe).with("test_agent", "test_type", "test_collective").once
160         Util.subscribe({:agent => "test_agent", :type => "test_type", :collective => "test_collective"})
161       end
162     end
163
164     describe "#unsubscribe" do
165       it "should unsubscribe to multiple topics given an Array" do
166         subs1 = {:agent => "test_agent", :type => "test_type", :collective => "test_collective"}
167         subs2 = {:agent => "test_agent2", :type => "test_type2", :collective => "test_collective2"}
168         MCollective::Connector::Stomp.any_instance.expects(:unsubscribe).with("test_agent", "test_type", "test_collective").once
169         MCollective::Connector::Stomp.any_instance.expects(:unsubscribe).with("test_agent2", "test_type2", "test_collective2").once
170
171         Util.unsubscribe([subs1, subs2])
172       end
173
174       it "should subscribe to a single topic given a hash" do
175         MCollective::Connector::Stomp.any_instance.expects(:unsubscribe).with("test_agent", "test_type", "test_collective").once
176         Util.unsubscribe({:agent => "test_agent", :type => "test_type", :collective => "test_collective"})
177       end
178     end
179
180     describe "#empty_filter?" do
181       it "should correctly compare empty filters" do
182         Util.empty_filter?(Util.empty_filter).should == true
183       end
184
185       it "should treat an empty hash as an empty filter" do
186         Util.empty_filter?({}).should == true
187       end
188
189       it "should detect non empty filters correctly" do
190         filter = Util.empty_filter
191         filter["cf_class"] << "meh"
192
193
194         Util.empty_filter?(filter).should == false
195       end
196     end
197
198     describe "#empty_filter" do
199       it "should create correct empty filters" do
200         Util.empty_filter.should == {"fact" => [], "cf_class" => [], "agent" => [], "identity" => [], "compound" => []}
201       end
202     end
203
204     describe "#default_options" do
205       it "should supply correct default options" do
206         Config.instance.stubs(:default_discovery_options).returns([])
207         empty_filter = Util.empty_filter
208         config_file = Util.config_file_for_user
209
210         Util.default_options.should == {:verbose => false, :disctimeout => nil, :timeout => 5, :config => config_file, :filter => empty_filter, :collective => nil, :discovery_method => nil, :discovery_options => []}
211       end
212     end
213
214     describe "#has_fact?" do
215       it "should handle missing facts correctly" do
216         MCollective::Facts.expects("[]").with("foo").returns(nil).once
217         Util.has_fact?("foo", "1", "==").should == false
218       end
219
220       it "should handle regex in a backward compatible way" do
221         MCollective::Facts.expects("[]").with("foo").returns("foo").times(6)
222         Util.has_fact?("foo", "foo", "=~").should == true
223         Util.has_fact?("foo", "/foo/", "=~").should == true
224         Util.has_fact?("foo", "foo", "=~").should == true
225         Util.has_fact?("foo", "bar", "=~").should == false
226         Util.has_fact?("foo", "/bar/", "=~").should == false
227         Util.has_fact?("foo", "bar", "=~").should == false
228       end
229
230       it "should evaluate equality" do
231         MCollective::Facts.expects("[]").with("foo").returns("foo").twice
232         Util.has_fact?("foo", "foo", "==").should == true
233         Util.has_fact?("foo", "bar", "==").should == false
234       end
235
236       it "should handle numeric comparisons correctly" do
237         MCollective::Facts.expects("[]").with("foo").returns("1").times(8)
238         Util.has_fact?("foo", "2", ">=").should == false
239         Util.has_fact?("foo", "1", ">=").should == true
240         Util.has_fact?("foo", "2", "<=").should == true
241         Util.has_fact?("foo", "1", "<=").should == true
242         Util.has_fact?("foo", "1", "<").should == false
243         Util.has_fact?("foo", "1", ">").should == false
244         Util.has_fact?("foo", "1", "!=").should == false
245         Util.has_fact?("foo", "2", "!=").should == true
246       end
247
248       it "should handle alphabetic comparisons correctly" do
249         MCollective::Facts.expects("[]").with("foo").returns("b").times(8)
250         Util.has_fact?("foo", "c", ">=").should == false
251         Util.has_fact?("foo", "a", ">=").should == true
252         Util.has_fact?("foo", "a", "<=").should == false
253         Util.has_fact?("foo", "b", "<=").should == true
254         Util.has_fact?("foo", "b", "<").should == false
255         Util.has_fact?("foo", "b", ">").should == false
256         Util.has_fact?("foo", "b", "!=").should == false
257         Util.has_fact?("foo", "a", "!=").should == true
258       end
259     end
260
261     describe "#parse_fact_string" do
262       it "should parse old style regex fact matches" do
263         Util.parse_fact_string("foo=/bar/").should == {:fact => "foo", :value => "/bar/", :operator => "=~"}
264         Util.parse_fact_string("foo = /bar/").should == {:fact => "foo", :value => "/bar/", :operator => "=~"}
265       end
266
267       it "should parse old style equality" do
268         Util.parse_fact_string("foo=bar").should == {:fact => "foo", :value => "bar", :operator => "=="}
269         Util.parse_fact_string("foo = bar").should == {:fact => "foo", :value => "bar", :operator => "=="}
270       end
271
272       it "should parse regex fact matches" do
273         Util.parse_fact_string("foo=~bar").should == {:fact => "foo", :value => "bar", :operator => "=~"}
274         Util.parse_fact_string("foo =~ bar").should == {:fact => "foo", :value => "bar", :operator => "=~"}
275       end
276
277       it "should treat => like >=" do
278         Util.parse_fact_string("foo=>bar").should == {:fact => "foo", :value => "bar", :operator => ">="}
279         Util.parse_fact_string("foo => bar").should == {:fact => "foo", :value => "bar", :operator => ">="}
280       end
281
282       it "should treat =< like <=" do
283         Util.parse_fact_string("foo=<bar").should == {:fact => "foo", :value => "bar", :operator => "<="}
284         Util.parse_fact_string("foo =< bar").should == {:fact => "foo", :value => "bar", :operator => "<="}
285       end
286
287       it "should parse less than or equal" do
288         Util.parse_fact_string("foo<=bar").should == {:fact => "foo", :value => "bar", :operator => "<="}
289         Util.parse_fact_string("foo <= bar").should == {:fact => "foo", :value => "bar", :operator => "<="}
290       end
291
292       it "should parse greater than or equal" do
293         Util.parse_fact_string("foo>=bar").should == {:fact => "foo", :value => "bar", :operator => ">="}
294         Util.parse_fact_string("foo >= bar").should == {:fact => "foo", :value => "bar", :operator => ">="}
295       end
296
297       it "should parse less than" do
298         Util.parse_fact_string("foo<bar").should == {:fact => "foo", :value => "bar", :operator => "<"}
299         Util.parse_fact_string("foo < bar").should == {:fact => "foo", :value => "bar", :operator => "<"}
300       end
301
302       it "should parse greater than" do
303         Util.parse_fact_string("foo>bar").should == {:fact => "foo", :value => "bar", :operator => ">"}
304         Util.parse_fact_string("foo > bar").should == {:fact => "foo", :value => "bar", :operator => ">"}
305       end
306
307       it "should parse greater than" do
308         Util.parse_fact_string("foo>bar").should == {:fact => "foo", :value => "bar", :operator => ">"}
309         Util.parse_fact_string("foo > bar").should == {:fact => "foo", :value => "bar", :operator => ">"}
310       end
311
312       it "should parse not equal" do
313         Util.parse_fact_string("foo!=bar").should == {:fact => "foo", :value => "bar", :operator => "!="}
314         Util.parse_fact_string("foo != bar").should == {:fact => "foo", :value => "bar", :operator => "!="}
315       end
316
317       it "should parse equal to" do
318         Util.parse_fact_string("foo==bar").should == {:fact => "foo", :value => "bar", :operator => "=="}
319         Util.parse_fact_string("foo == bar").should == {:fact => "foo", :value => "bar", :operator => "=="}
320       end
321
322       it "should fail for facts in the wrong format" do
323         expect {
324           Util.parse_fact_string("foo")
325         }.to raise_error("Could not parse fact foo it does not appear to be in a valid format")
326       end
327     end
328
329     describe "#colorize" do
330       it "should not add color codes when color is disabled" do
331         Config.instance.stubs(:color).returns(false)
332         Util.colorize(:red, "hello world").should == "hello world"
333       end
334
335       it "should add color when color is enabled" do
336         Config.instance.stubs(:color).returns(true)
337         Util.colorize(:red, "hello world").should == "\e[31mhello world\e[0m"
338       end
339     end
340
341     describe "#align_text" do
342       it "should default to 80 if the terminal dimensions are unknown" do
343         Util.stubs(:terminal_dimensions).returns([0,0])
344
345         rootdir = File.dirname(__FILE__)
346         input = File.read("#{rootdir}/../fixtures/util/4.in")
347         output = File.read("#{rootdir}/../fixtures/util/4.out")
348
349         (Util.align_text(input, nil, 3) + "\n").should == output
350       end
351
352       it "should return the origional string if console lines are 0" do
353         result = Util.align_text("test", 0)
354         result.should == "test"
355       end
356
357       it "should return the origional string if preamble is greater than console lines" do
358         result = Util.align_text("test", 5, 6)
359         result.should == "test"
360       end
361
362       it "should return a string prefixed by the preamble" do
363         result = Util.align_text("test")
364         result.should == "     test"
365       end
366
367       it "should correctly align strings" do
368         rootdir = File.dirname(__FILE__)
369         (1..2).each do |i|
370           input = File.read("#{rootdir}/../fixtures/util/#{i}.in")
371           output = File.read("#{rootdir}/../fixtures/util/#{i}.out")
372
373           (Util.align_text(input, 158 , 5) + "\n").should == output
374         end
375
376         input = File.read("#{rootdir}/../fixtures/util/3.in")
377         output = File.read("#{rootdir}/../fixtures/util/3.out")
378
379         (Util.align_text(input, 30, 0) + "\n").should == output
380       end
381     end
382
383     describe "#terminal_dimensions" do
384       it "should return 0 if there is no tty" do
385         stdout = mock()
386         stdout.expects(:tty?).returns(false)
387         result = Util.terminal_dimensions(stdout)
388         result.should == [0,0]
389       end
390
391       it "should return the default dimensions for a windows terminal" do
392         stdout = mock()
393         stdout.expects(:tty?).returns(true)
394         Util.expects(:windows?).returns(true)
395         result = Util.terminal_dimensions(stdout)
396         result.should == [80, 40]
397       end
398
399       it "should return 0 if an exception was raised" do
400         stdout = mock()
401         stdout.expects(:tty?).raises("error")
402         result = Util.terminal_dimensions(stdout)
403         result.should == [0, 0]
404       end
405
406       it "should return the correct dimensions if ENV columns and lines are set" do
407         stdout = mock()
408         stdout.expects(:tty?).returns(true)
409         environment = mock()
410         environment.expects(:[]).with("COLUMNS").returns(5).twice
411         environment.expects(:[]).with("LINES").returns(5).twice
412         result = Util.terminal_dimensions(stdout, environment)
413         result.should == [5,5]
414       end
415
416       it "should return the correct dimensions if ENV term is set and tput is present" do
417         stdout = mock()
418         stdout.expects(:tty?).returns(true)
419         environment = mock()
420         environment.expects(:[]).with("COLUMNS").returns(false)
421         environment.expects(:[]).with("TERM").returns(true)
422
423         Util.expects(:command_in_path?).with("tput").returns(true)
424         Util.stubs(:`).returns("5")
425
426         result = Util.terminal_dimensions(stdout, environment)
427         result.should == [5,5]
428       end
429
430       it "should return the correct dimensions if stty is present" do
431         stdout = mock()
432         stdout.expects(:tty?).returns(true)
433
434         environment = mock()
435         environment.expects(:[]).with("COLUMNS").returns(false)
436         environment.expects(:[]).with("TERM").returns(false)
437
438         Util.expects(:command_in_path?).with("stty").returns(true)
439         Util.stubs(:`).returns("5 5")
440
441         result = Util.terminal_dimensions(stdout, environment)
442         result.should == [5,5]
443       end
444     end
445
446     describe "#command_in_path?" do
447       it "should return true if the command is found" do
448         File.stubs(:exist?).returns(true)
449         result = Util.command_in_path?("test")
450         result.should == true
451       end
452
453       it "should return false if the command cannot be found" do
454         File.stubs(:exist?).returns(false)
455         result = Util.command_in_path?("test")
456         result.should == false
457       end
458     end
459
460     describe "#absolute_path?" do
461       it "should work correctly validate the path" do
462         Util.absolute_path?('.', '/', '\\').should == false
463         Util.absolute_path?('foo/foo', '/', '\\').should == false
464         Util.absolute_path?('foo\\bar', '/', '\\').should == false
465         Util.absolute_path?('../foo/bar', '/', '\\').should == false
466
467         Util.absolute_path?('\\foo/foo', '/', '\\').should == true
468         Util.absolute_path?('\\', '/', '\\').should == true
469         Util.absolute_path?('/foo', '/', '\\').should == true
470         Util.absolute_path?('/foo/foo', '/', '\\').should == true
471
472         Util.absolute_path?('.', '/', nil).should == false
473         Util.absolute_path?('foo/foo', '/', nil).should == false
474         Util.absolute_path?('foo\\bar', '/', nil).should == false
475         Util.absolute_path?('../foo/bar', '/', nil).should == false
476
477         Util.absolute_path?('\\foo/foo', '/', nil).should == false
478         Util.absolute_path?('\\', '/', nil).should == false
479         Util.absolute_path?('/foo', '/', nil).should == true
480         Util.absolute_path?('/foo/foo', '/', nil).should == true
481       end
482     end
483
484     describe "#versioncmp" do
485       it "should be able to sort a long set of various unordered versions" do
486         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}
487
488         newary = ary.sort {|a, b| Util.versioncmp(a,b) }
489
490         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"]
491       end
492     end
493   end
494 end