2
require 'puppet/file_system'
3
require 'puppet/util/platform'
5
describe Puppet::FileSystem::File do
6
include PuppetSpec::Files
8
context "#exclusive_open" do
9
it "opens ands allows updating of an existing file" do
10
file = Puppet::FileSystem::File.new(file_containing("file_to_update", "the contents"))
12
file.exclusive_open(0660, 'r+') do |fh|
16
fh.write("updated #{old}")
19
expect(file.read).to eq("updated the contents")
22
it "opens, creates ands allows updating of a new file" do
23
file = Puppet::FileSystem::File.new(tmpfile("file_to_update"))
25
file.exclusive_open(0660, 'w') do |fh|
26
fh.write("updated new file")
29
expect(file.read).to eq("updated new file")
32
it "excludes other processes from updating at the same time", :unless => Puppet::Util::Platform.windows? do
33
file = Puppet::FileSystem::File.new(file_containing("file_to_update", "0"))
35
increment_counter_in_multiple_processes(file, 5, 'r+')
37
expect(file.read).to eq("5")
40
it "excludes other processes from updating at the same time even when creating the file", :unless => Puppet::Util::Platform.windows? do
41
file = Puppet::FileSystem::File.new(tmpfile("file_to_update"))
43
increment_counter_in_multiple_processes(file, 5, 'a+')
45
expect(file.read).to eq("5")
48
it "times out if the lock cannot be aquired in a specified amount of time", :unless => Puppet::Util::Platform.windows? do
49
file = tmpfile("file_to_update")
51
child = spawn_process_that_locks(file)
54
Puppet::FileSystem::File.new(file).exclusive_open(0666, 'a', 0.1) do |f|
56
end.to raise_error(Timeout::Error)
58
Process.kill(9, child)
61
def spawn_process_that_locks(file)
64
child = Kernel.fork do
66
Puppet::FileSystem::File.new(file).exclusive_open(0666, 'a') do |fh|
80
def increment_counter_in_multiple_processes(file, num_procs, options)
83
children << Kernel.fork do
84
file.exclusive_open(0660, options) do |fh|
86
contents = (fh.read || 0).to_i
89
fh.write((contents + 1).to_s)
95
children.each { |pid| Process.wait(pid) }
100
:if => ! Puppet.features.manages_symlinks? &&
101
Puppet.features.microsoft_windows? do
103
let (:file) { Puppet::FileSystem::File.new(tmpfile("somefile")) }
104
let (:missing_file) { Puppet::FileSystem::File.new(tmpfile("missingfile")) }
105
let (:expected_msg) { "This version of Windows does not support symlinks. Windows Vista / 2008 or higher is required." }
108
FileUtils.touch(file.path)
111
it "should raise an error when trying to create a symlink" do
112
expect { file.symlink('foo') }.to raise_error(Puppet::Util::Windows::Error)
115
it "should return false when trying to check if a path is a symlink" do
116
file.symlink?.should be_false
119
it "should raise an error when trying to read a symlink" do
120
expect { file.readlink }.to raise_error(Puppet::Util::Windows::Error)
123
it "should return a File::Stat instance when calling stat on an existing file" do
124
file.stat.should be_instance_of(File::Stat)
127
it "should raise Errno::ENOENT when calling stat on a missing file" do
128
expect { missing_file.stat }.to raise_error(Errno::ENOENT)
131
it "should fall back to stat when trying to lstat a file" do
132
Puppet::Util::Windows::File.expects(:stat).with(file.path)
138
describe "symlink", :if => Puppet.features.manages_symlinks? do
140
let (:file) { Puppet::FileSystem::File.new(tmpfile("somefile")) }
141
let (:missing_file) { Puppet::FileSystem::File.new(tmpfile("missingfile")) }
142
let (:dir) { Puppet::FileSystem::File.new(tmpdir("somedir")) }
145
FileUtils.touch(file.path)
148
it "should return true for exist? on a present file" do
149
file.exist?.should be_true
150
Puppet::FileSystem::File.exist?(file.path).should be_true
153
it "should return false for exist? on a non-existant file" do
154
missing_file.exist?.should be_false
155
Puppet::FileSystem::File.exist?(missing_file.path).should be_false
158
it "should return true for exist? on a present directory" do
159
dir.exist?.should be_true
160
Puppet::FileSystem::File.exist?(dir.path).should be_true
163
it "should return false for exist? on a dangling symlink" do
164
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
165
missing_file.symlink(symlink.path)
167
missing_file.exist?.should be_false
168
symlink.exist?.should be_false
171
it "should return true for exist? on valid symlinks" do
172
[file, dir].each do |target|
173
symlink = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_link"))
174
target.symlink(symlink.path)
176
target.exist?.should be_true
177
symlink.exist?.should be_true
181
it "should not create a symlink when the :noop option is specified" do
182
[file, dir].each do |target|
183
symlink = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_link"))
184
target.symlink(symlink.path, { :noop => true })
186
target.exist?.should be_true
187
symlink.exist?.should be_false
191
it "should raise Errno::EEXIST if trying to create a file / directory symlink when the symlink path already exists as a file" do
192
existing_file = Puppet::FileSystem::File.new(tmpfile("#{file.path.basename.to_s}_link"))
193
FileUtils.touch(existing_file.path)
195
[file, dir].each do |target|
196
expect { target.symlink(existing_file.path) }.to raise_error(Errno::EEXIST)
198
existing_file.exist?.should be_true
199
existing_file.symlink?.should be_false
203
it "should silently fail if trying to create a file / directory symlink when the symlink path already exists as a directory" do
204
existing_dir = Puppet::FileSystem::File.new(tmpdir("#{file.path.basename.to_s}_dir"))
206
[file, dir].each do |target|
207
target.symlink(existing_dir.path).should == 0
209
existing_dir.exist?.should be_true
210
File.directory?(existing_dir.path).should be_true
211
existing_dir.symlink?.should be_false
215
it "should silently fail to modify an existing directory symlink to reference a new file or directory" do
216
[file, dir].each do |target|
217
existing_dir = Puppet::FileSystem::File.new(tmpdir("#{target.path.basename.to_s}_dir"))
218
symlink = Puppet::FileSystem::File.new(tmpfile("#{existing_dir.path.basename.to_s}_link"))
219
existing_dir.symlink(symlink.path)
221
symlink.readlink.should == existing_dir.path.to_s
223
# now try to point it at the new target, no error raised, but file system unchanged
224
target.symlink(symlink.path).should == 0
225
symlink.readlink.should == existing_dir.path.to_s
229
it "should raise Errno::EEXIST if trying to modify a file symlink to reference a new file or directory" do
230
symlink = Puppet::FileSystem::File.new(tmpfile("#{file.path.basename.to_s}_link"))
231
file_2 = Puppet::FileSystem::File.new(tmpfile("#{file.path.basename.to_s}_2"))
232
FileUtils.touch(file_2.path)
234
file_2.symlink(symlink.path)
236
[file, dir].each do |target|
237
expect { target.symlink(symlink.path) }.to raise_error(Errno::EEXIST)
238
symlink.readlink.should == file_2.path.to_s
242
it "should delete the existing file when creating a file / directory symlink with :force when the symlink path exists as a file" do
243
[file, dir].each do |target|
244
existing_file = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_existing"))
245
FileUtils.touch(existing_file.path)
246
existing_file.symlink?.should be_false
248
target.symlink(existing_file.path, { :force => true })
250
existing_file.symlink?.should be_true
251
existing_file.readlink.should == target.path.to_s
255
it "should modify an existing file symlink when using :force to reference a new file or directory" do
256
[file, dir].each do |target|
257
existing_file = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_existing"))
258
FileUtils.touch(existing_file.path)
259
existing_symlink = Puppet::FileSystem::File.new(tmpfile("#{existing_file.path.basename.to_s}_link"))
260
existing_file.symlink(existing_symlink.path)
262
existing_symlink.readlink.should == existing_file.path.to_s
264
target.symlink(existing_symlink.path, { :force => true })
266
existing_symlink.readlink.should == target.path.to_s
270
it "should silently fail if trying to overwrite an existing directory with a new symlink when using :force to reference a file or directory" do
271
[file, dir].each do |target|
272
existing_dir = Puppet::FileSystem::File.new(tmpdir("#{target.path.basename.to_s}_existing"))
274
target.symlink(existing_dir.path, { :force => true }).should == 0
276
existing_dir.symlink?.should be_false
280
it "should silently fail if trying to modify an existing directory symlink when using :force to reference a new file or directory" do
281
[file, dir].each do |target|
282
existing_dir = Puppet::FileSystem::File.new(tmpdir("#{target.path.basename.to_s}_existing"))
283
existing_symlink = Puppet::FileSystem::File.new(tmpfile("#{existing_dir.path.basename.to_s}_link"))
284
existing_dir.symlink(existing_symlink.path)
286
existing_symlink.readlink.should == existing_dir.path.to_s
288
target.symlink(existing_symlink.path, { :force => true }).should == 0
290
existing_symlink.readlink.should == existing_dir.path.to_s
294
it "should accept a string, Pathname or object with to_str (Puppet::Util::WatchedFile) for exist?" do
296
Pathname.new(tmpfile('bogus2')),
297
Puppet::Util::WatchedFile.new(tmpfile('bogus3'))
298
].each { |f| Puppet::FileSystem::File.exist?(f).should be_false }
301
it "should return a File::Stat instance when calling stat on an existing file" do
302
file.stat.should be_instance_of(File::Stat)
305
it "should raise Errno::ENOENT when calling stat on a missing file" do
306
expect { missing_file.stat }.to raise_error(Errno::ENOENT)
309
it "should be able to create a symlink, and verify it with symlink?" do
310
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
311
file.symlink(symlink.path)
313
symlink.symlink?.should be_true
316
it "should report symlink? as false on file, directory and missing files" do
317
[file, dir, missing_file].each do |f|
318
f.symlink?.should be_false
322
it "should return a File::Stat with ftype 'link' when calling lstat on a symlink pointing to existing file" do
323
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
324
file.symlink(symlink.path)
327
stat.should be_instance_of(File::Stat)
328
stat.ftype.should == 'link'
331
it "should return a File::Stat of ftype 'link' when calling lstat on a symlink pointing to missing file" do
332
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
333
missing_file.symlink(symlink.path)
336
stat.should be_instance_of(File::Stat)
337
stat.ftype.should == 'link'
340
it "should return a File::Stat of ftype 'file' when calling stat on a symlink pointing to existing file" do
341
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
342
file.symlink(symlink.path)
345
stat.should be_instance_of(File::Stat)
346
stat.ftype.should == 'file'
349
it "should return a File::Stat of ftype 'directory' when calling stat on a symlink pointing to existing directory" do
350
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
351
dir.symlink(symlink.path)
354
stat.should be_instance_of(File::Stat)
355
stat.ftype.should == 'directory'
358
it "should return a File::Stat of ftype 'file' when calling stat on a symlink pointing to another symlink" do
359
# point symlink -> file
360
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
361
file.symlink(symlink.path)
363
# point symlink2 -> symlink
364
symlink2 = Puppet::FileSystem::File.new(tmpfile("somefile_link2"))
365
symlink.symlink(symlink2.path)
367
symlink2.stat.ftype.should == 'file'
371
it "should raise Errno::ENOENT when calling stat on a dangling symlink" do
372
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
373
missing_file.symlink(symlink.path)
375
expect { symlink.stat }.to raise_error(Errno::ENOENT)
378
it "should be able to readlink to resolve the physical path to a symlink" do
379
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
380
file.symlink(symlink.path)
382
file.exist?.should be_true
383
symlink.readlink.should == file.path.to_s
386
it "should not resolve entire symlink chain with readlink on a symlink'd symlink" do
387
# point symlink -> file
388
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
389
file.symlink(symlink.path)
391
# point symlink2 -> symlink
392
symlink2 = Puppet::FileSystem::File.new(tmpfile("somefile_link2"))
393
symlink.symlink(symlink2.path)
395
file.exist?.should be_true
396
symlink2.readlink.should == symlink.path.to_s
399
it "should be able to readlink to resolve the physical path to a dangling symlink" do
400
symlink = Puppet::FileSystem::File.new(tmpfile("somefile_link"))
401
missing_file.symlink(symlink.path)
403
missing_file.exist?.should be_false
404
symlink.readlink.should == missing_file.path.to_s
407
it "should delete only the symlink and not the target when calling unlink instance method" do
408
[file, dir].each do |target|
409
symlink = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_link"))
410
target.symlink(symlink.path)
412
target.exist?.should be_true
413
symlink.readlink.should == target.path.to_s
415
symlink.unlink.should == 1 # count of files
417
target.exist?.should be_true
418
symlink.exist?.should be_false
422
it "should delete only the symlink and not the target when calling unlink class method" do
423
[file, dir].each do |target|
424
symlink = Puppet::FileSystem::File.new(tmpfile("#{target.path.basename.to_s}_link"))
425
target.symlink(symlink.path)
427
target.exist?.should be_true
428
symlink.readlink.should == target.path.to_s
430
Puppet::FileSystem::File.unlink(symlink.path).should == 1 # count of files
432
target.exist?.should be_true
433
symlink.exist?.should be_false
438
it "should delete files with unlink" do
439
file.exist?.should be_true
441
file.unlink.should == 1 # count of files
443
file.exist?.should be_false
446
it "should delete files with unlink class method" do
447
file.exist?.should be_true
449
Puppet::FileSystem::File.unlink(file.path).should == 1 # count of files
451
file.exist?.should be_false
454
it "should delete multiple files with unlink class method" do
455
paths = (1..3).collect do |i|
456
f = Puppet::FileSystem::File.new(tmpfile("somefile_#{i}"))
457
FileUtils.touch(f.path)
458
f.exist?.should be_true
462
Puppet::FileSystem::File.unlink(*paths).should == 3 # count of files
464
paths.each { |p| Puppet::FileSystem::File.exist?(p).should be_false }
467
it "should raise Errno::EPERM or Errno::EISDIR when trying to delete a directory with the unlink class method" do
468
dir.exist?.should be_true
472
Puppet::FileSystem::File.unlink(dir.path)
473
rescue Exception => e
478
Errno::EPERM, # Windows and OSX
479
Errno::EISDIR # Linux
480
].should include ex.class
482
dir.exist?.should be_true