summaryrefslogtreecommitdiffstats
path: root/spec/unit/util/resolution_spec.rb
blob: 581d0e17f12b642ad4075d79477d4ec5146b23c4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
#!/usr/bin/env ruby

require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')

require 'facter/util/resolution'

describe Facter::Util::Resolution do
    it "should require a name" do
        lambda { Facter::Util::Resolution.new }.should raise_error(ArgumentError)
    end

    it "should have a name" do
        Facter::Util::Resolution.new("yay").name.should == "yay"
    end

    it "should have a method for setting the code" do
        Facter::Util::Resolution.new("yay").should respond_to(:setcode)
    end

    it "should support a timeout value" do
        Facter::Util::Resolution.new("yay").should respond_to(:timeout=)
    end

    it "should default to a timeout of 0 seconds" do
        Facter::Util::Resolution.new("yay").limit.should == 0
    end

    it "should default to nil for code" do
        Facter::Util::Resolution.new("yay").code.should be_nil
    end

    it "should default to nil for interpreter" do
        Facter::Util::Resolution.new("yay").interpreter.should be_nil
    end

    it "should provide a 'limit' method that returns the timeout" do
        res = Facter::Util::Resolution.new("yay")
        res.timeout = "testing"
        res.limit.should == "testing"
    end

    describe "when setting the code" do
        before do
            @resolve = Facter::Util::Resolution.new("yay")
        end

        it "should default to the detected interpreter if a string is provided" do
            Facter::Util::Resolution::INTERPRETER = "/bin/bar"
            @resolve.setcode "foo"
            @resolve.interpreter.should == "/bin/bar"
        end

        it "should set the code to any provided string" do
            @resolve.setcode "foo"
            @resolve.code.should == "foo"
        end

        it "should set the code to any provided block" do
            block = lambda { }
            @resolve.setcode(&block)
            @resolve.code.should equal(block)
        end

        it "should prefer the string over a block" do
            @resolve.setcode("foo") { }
            @resolve.code.should == "foo"
        end

        it "should fail if neither a string nor block has been provided" do
            lambda { @resolve.setcode }.should raise_error(ArgumentError)
        end
    end

    it "should be able to return a value" do
        Facter::Util::Resolution.new("yay").should respond_to(:value)
    end

    describe "when returning the value" do
        before do
            @resolve = Facter::Util::Resolution.new("yay")
        end

        describe "and setcode has not been called" do
            it "should return nil" do
                Facter::Util::Resolution.expects(:exec).with(nil, nil).never
                @resolve.value.should be_nil
            end
        end

        describe "and the code is a string" do
            describe "on windows" do
                before do
                    Facter::Util::Resolution::WINDOWS = true
                    Facter::Util::Resolution::INTERPRETER = "cmd.exe"
                end
                
                it "should return the result of executing the code with the interpreter" do
                    @resolve.setcode "/bin/foo"
                    Facter::Util::Resolution.expects(:exec).once.with("/bin/foo", "cmd.exe").returns "yup"

                    @resolve.value.should == "yup"
                end

                it "should return nil if the value is an empty string" do
                    @resolve.setcode "/bin/foo"
                    Facter::Util::Resolution.expects(:exec).once.returns ""
                    @resolve.value.should be_nil
                end
            end

            describe "on non-windows systems" do
                before do
                    Facter::Util::Resolution::WINDOWS = false
                    Facter::Util::Resolution::INTERPRETER = "/bin/sh"
                end
                
                it "should return the result of executing the code with the interpreter" do
                    @resolve.setcode "/bin/foo"
                    Facter::Util::Resolution.expects(:exec).once.with("/bin/foo", "/bin/sh").returns "yup"

                    @resolve.value.should == "yup"
                end

                it "should return nil if the value is an empty string" do
                    @resolve.setcode "/bin/foo"
                    Facter::Util::Resolution.expects(:exec).once.returns ""
                    @resolve.value.should be_nil
                end
            end
        end

        describe "and the code is a block" do
            it "should warn but not fail if the code fails" do
                @resolve.setcode { raise "feh" }
                @resolve.expects(:warn)
                @resolve.value.should be_nil
            end

            it "should return the value returned by the block" do
                @resolve.setcode { "yayness" }
                @resolve.value.should == "yayness"
            end

            it "should return nil if the value is an empty string" do
                @resolve.setcode { "" }
                @resolve.value.should be_nil
            end

            it "should return nil if the value is an empty block" do
                @resolve.setcode { "" }
                @resolve.value.should be_nil
            end

            it "should use its limit method to determine the timeout, to avoid conflict when a 'timeout' method exists for some other reason" do
                @resolve.expects(:timeout).never
                @resolve.expects(:limit).returns "foo"
                Timeout.expects(:timeout).with("foo")

                @resolve.setcode { sleep 2; "raise This is a test"}
                @resolve.value
            end

            it "should timeout after the provided timeout" do
                @resolve.expects(:warn)
                @resolve.timeout = 0.1
                @resolve.setcode { sleep 2; raise "This is a test" }
                Thread.expects(:new).yields

                @resolve.value.should be_nil
            end

            it "should waitall to avoid zombies if the timeout is exceeded" do
                @resolve.stubs(:warn)
                @resolve.timeout = 0.1
                @resolve.setcode { sleep 2; raise "This is a test" }

                Thread.expects(:new).yields
                Process.expects(:waitall)

                @resolve.value
            end
        end
    end

    it "should return its value when converted to a string" do
        @resolve = Facter::Util::Resolution.new("yay")
        @resolve.expects(:value).returns "myval"
        @resolve.to_s.should == "myval"
    end

    it "should allow the adding of confines" do
        Facter::Util::Resolution.new("yay").should respond_to(:confine)
    end

    it "should provide a method for returning the number of confines" do
        @resolve = Facter::Util::Resolution.new("yay")
        @resolve.confine "one" => "foo", "two" => "fee"
        @resolve.length.should == 2
    end

    it "should return 0 confines when no confines have been added" do
        Facter::Util::Resolution.new("yay").length.should == 0
    end

    it "should have a method for determining if it is suitable" do
        Facter::Util::Resolution.new("yay").should respond_to(:suitable?)
    end

    describe "when adding confines" do
        before do
            @resolve = Facter::Util::Resolution.new("yay")
        end

        it "should accept a hash of fact names and values" do
            lambda { @resolve.confine :one => "two" }.should_not raise_error
        end

        it "should create a Util::Confine instance for every argument in the provided hash" do
            Facter::Util::Confine.expects(:new).with("one", "foo")
            Facter::Util::Confine.expects(:new).with("two", "fee")

            @resolve.confine "one" => "foo", "two" => "fee"
        end

    end

    describe "when determining suitability" do
        before do
            @resolve = Facter::Util::Resolution.new("yay")
        end

        it "should always be suitable if no confines have been added" do
            @resolve.should be_suitable
        end

        it "should be unsuitable if any provided confines return false" do
            confine1 = mock 'confine1', :true? => true
            confine2 = mock 'confine2', :true? => false
            Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2)
            @resolve.confine :one => :two, :three => :four

            @resolve.should_not be_suitable
        end

        it "should be suitable if all provided confines return true" do
            confine1 = mock 'confine1', :true? => true
            confine2 = mock 'confine2', :true? => true
            Facter::Util::Confine.expects(:new).times(2).returns(confine1).then.returns(confine2)
            @resolve.confine :one => :two, :three => :four

            @resolve.should be_suitable
        end
    end

    it "should have a class method for executing code" do
        Facter::Util::Resolution.should respond_to(:exec)
    end

    # It's not possible, AFAICT, to mock %x{}, so I can't really test this bit.
    describe "when executing code" do
        it "should fail if any interpreter other than /bin/sh is requested" do
            lambda { Facter::Util::Resolution.exec("/something", "/bin/perl") }.should raise_error(ArgumentError)
        end

        it "should execute the binary" do
            Facter::Util::Resolution.exec("echo foo").should == "foo"
        end
    end
end