summaryrefslogtreecommitdiffstats
path: root/spec/unit/util/file_locking_spec.rb
blob: 26147426356ced05134452030bb6479cd6e2535d (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
#!/usr/bin/env rspec
require 'spec_helper'

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
      Puppet::Util.expects(:synchronize_on).with('/file',Sync::SH).once
      Puppet::Util::FileLocking.readlock '/file'
    end

    it "should use a shared lock on the file" do
      Puppet::Util.expects(:synchronize_on).with('/file',Sync::SH).yields

      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
      Puppet::Util.expects(:synchronize_on).with('/file',Sync::SH).yields

      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
      Puppet::Util.stubs(:synchronize_on).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
      Puppet::Util.expects(:synchronize_on).with("/file",Sync::EX)
      Puppet::Util::FileLocking.writelock '/file'
    end

    it "should use any specified mode when opening the file" do
      File.expects(:open).with("/file", File::Constants::CREAT | File::Constants::WRONLY , :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", File::Constants::CREAT | File::Constants::WRONLY, 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", File::Constants::CREAT | File::Constants::WRONLY, 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.stubs(:seek)
      lfh.stubs(:truncate)
      lfh.expects(:print).with "foo"

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

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

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

      lfh.expects(:seek).with(0, IO::SEEK_SET)
      lfh.expects(:truncate).with(0)
      lfh.stubs(:print)

      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
      Puppet::Util.expects(:synchronize_on).with('/file',Sync::EX).yields

      File.expects(:exists?).with('/file').returns false
      File.expects(:open).with('/file', File::Constants::CREAT | File::Constants::WRONLY, 0600).once

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