summaryrefslogtreecommitdiffstats
path: root/spec/unit/util/file_locking_spec.rb
blob: 35e7f781e22810a05c088a010f1e9ac8819fd8aa (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
#!/usr/bin/env ruby

Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") }

require 'puppet/util/file_locking'

class FileLocker
    include Puppet::Util::FileLocking
end

describe Puppet::Util::FileLocking do
    it "should have a module method for getting a read lock on files" do
        Puppet::Util::FileLocking.should respond_to(:readlock)
    end

    it "should have a module method for getting a write lock on files" do
        Puppet::Util::FileLocking.should respond_to(:writelock)
    end

    it "should have an instance method for getting a read lock on files" do
        FileLocker.new.private_methods.should be_include("readlock")
    end

    it "should have an instance method for getting a write lock on files" do
        FileLocker.new.private_methods.should be_include("writelock")
    end

    describe "when acquiring a read lock" do
        before do
            File.stubs(:exists?).with('/file').returns true
            File.stubs(:file?).with('/file').returns true
        end
        
        it "should use a global shared mutex" do
            @sync = mock 'sync'
            @sync.expects(:synchronize).with(Sync::SH).once
            Puppet::Util.expects(:sync).with('/file').returns @sync

            Puppet::Util::FileLocking.readlock '/file'
        end

        it "should use a shared lock on the file" do
            @sync = mock 'sync'
            @sync.stubs(:synchronize).yields
            Puppet::Util.expects(:sync).with('/file').returns @sync

            fh = mock 'filehandle'
            File.expects(:open).with("/file").yields fh
            fh.expects(:lock_shared).yields "locked_fh"

            result = nil
            Puppet::Util::FileLocking.readlock('/file') { |l| result = l }
            result.should == "locked_fh"
        end

        it "should only work on regular files" do
            File.expects(:file?).with('/file').returns false
            proc { Puppet::Util::FileLocking.readlock('/file') }.should raise_error(ArgumentError)
        end

        it "should create missing files" do
            @sync = mock 'sync'
            @sync.stubs(:synchronize).yields
            Puppet::Util.expects(:sync).with('/file').returns @sync

            File.expects(:exists?).with('/file').returns false
            File.expects(:open).with('/file').once

            Puppet::Util::FileLocking.readlock('/file') 
        end
    end

    describe "when acquiring a write lock" do
        before do
            @sync = mock 'sync'
            Puppet::Util.stubs(:sync).returns @sync
            @sync.stubs(:synchronize).yields
            File.stubs(:file?).with('/file').returns true
            File.stubs(:exists?).with('/file').returns true
        end

        it "should fail if the parent directory does not exist" do
            FileTest.expects(:directory?).with("/my/dir").returns false
            File.stubs(:file?).with('/my/dir/file').returns true
            File.stubs(:exists?).with('/my/dir/file').returns true

            lambda { Puppet::Util::FileLocking.writelock('/my/dir/file') }.should raise_error(Puppet::DevError)
        end

        it "should use a global exclusive mutex" do
            sync = mock 'sync'
            sync.expects(:synchronize).with(Sync::EX)
            Puppet::Util.expects(:sync).with("/file").returns sync

            Puppet::Util::FileLocking.writelock '/file'
        end

        it "should use any specified mode when opening the file" do
            File.expects(:open).with("/file", "w", :mymode)

            Puppet::Util::FileLocking.writelock('/file', :mymode)
        end

        it "should use the mode of the existing file if no mode is specified" do
            File.expects(:stat).with("/file").returns(mock("stat", :mode => 0755))
            File.expects(:open).with("/file", "w", 0755)

            Puppet::Util::FileLocking.writelock('/file')
        end

        it "should use 0600 as the mode if no mode is specified and the file does not exist" do
            File.expects(:stat).raises(Errno::ENOENT)
            File.expects(:open).with("/file", "w", 0600)

            Puppet::Util::FileLocking.writelock('/file')
        end

        it "should create an exclusive file lock" do
            fh = mock 'fh'
            File.expects(:open).yields fh
            fh.expects(:lock_exclusive)

            Puppet::Util::FileLocking.writelock('/file')
        end

        it "should allow the caller to write to the locked file" do
            fh = mock 'fh'
            File.expects(:open).yields fh

            lfh = mock 'locked_filehandle'
            fh.expects(:lock_exclusive).yields(lfh)

            lfh.expects(:print).with "foo"

            Puppet::Util::FileLocking.writelock('/file') do |f|
                f.print "foo"
            end
        end

        it "should only work on regular files" do
            File.expects(:file?).with('/file').returns false
            proc { Puppet::Util::FileLocking.writelock('/file') }.should raise_error(ArgumentError)
        end

        it "should create missing files" do
            @sync = mock 'sync'
            @sync.stubs(:synchronize).yields
            Puppet::Util.expects(:sync).with('/file').returns @sync

            File.expects(:exists?).with('/file').returns false
            File.expects(:open).with('/file', 'w', 0600).once

            Puppet::Util::FileLocking.writelock('/file') 
        end
    end
end