memfs 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,928 @@
1
+ require 'spec_helper'
2
+
3
+ module MemFs
4
+ describe File do
5
+ subject { MemFs::File }
6
+
7
+ let(:file) { subject.new('/test-file') }
8
+ let(:random_string) { ('a'..'z').to_a.sample(10).join }
9
+
10
+ before :each do
11
+ fs.mkdir '/test-dir'
12
+ fs.touch '/test-file', '/test-file2'
13
+ subject.symlink '/test-file', '/test-link'
14
+ subject.symlink '/no-file', '/no-link'
15
+ end
16
+
17
+ describe '.atime' do
18
+ it "returns the last access time for the named file as a Time object" do
19
+ expect(subject.atime('/test-file')).to be_a(Time)
20
+ end
21
+
22
+ it "raises an error if the entry does not exist" do
23
+ expect { subject.atime('/no-file') }.to raise_error(Errno::ENOENT)
24
+ end
25
+
26
+ context "when the entry is a symlink" do
27
+ let(:time) { Time.now - 500000 }
28
+
29
+ it "returns the last access time of the last target of the link chain" do
30
+ fs.find!('/test-file').atime = time
31
+ subject.symlink('/test-link', '/test-link2')
32
+ expect(subject.atime('/test-link2')).to eq(time)
33
+ end
34
+ end
35
+ end
36
+
37
+ describe ".basename" do
38
+ it "returns the last component of the filename given in +file_name+" do
39
+ expect(subject.basename('/path/to/file.txt')).to eq('file.txt')
40
+ end
41
+
42
+ context "when +suffix+ is given" do
43
+ context "when it is present at the end of +file_name+" do
44
+ it "removes the +suffix+ from the filename basename" do
45
+ expect(subject.basename('/path/to/file.txt', '.txt')).to eq('file')
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ describe '.chmod' do
52
+ it "changes permission bits on the named file" do
53
+ subject.chmod(0777, '/test-file')
54
+ expect(subject.stat('/test-file').mode).to eq(0100777)
55
+ end
56
+
57
+ it "changes permission bits on the named files (in list)" do
58
+ subject.chmod(0777, '/test-file', '/test-file2')
59
+ expect(subject.stat('/test-file2').mode).to eq(0100777)
60
+ end
61
+ end
62
+
63
+ describe ".chown" do
64
+ it "changes the owner of the named file to the given numeric owner id" do
65
+ subject.chown(42, nil, '/test-file')
66
+ expect(subject.stat('/test-file').uid).to eq(42)
67
+ end
68
+
69
+ it "changes owner on the named files (in list)" do
70
+ subject.chown(42, nil, '/test-file', '/test-file2')
71
+ expect(subject.stat('/test-file2').uid).to eq(42)
72
+ end
73
+
74
+ it "changes the group of the named file to the given numeric group id" do
75
+ subject.chown(nil, 42, '/test-file')
76
+ expect(subject.stat('/test-file').gid).to eq(42)
77
+ end
78
+
79
+ it "returns the number of files" do
80
+ expect(subject.chown(42, 42, '/test-file', '/test-file2')).to eq(2)
81
+ end
82
+
83
+ it "ignores nil user id" do
84
+ previous_uid = subject.stat('/test-file').uid
85
+
86
+ subject.chown(nil, 42, '/test-file')
87
+ expect(subject.stat('/test-file').uid).to eq(previous_uid)
88
+ end
89
+
90
+ it "ignores nil group id" do
91
+ previous_gid = subject.stat('/test-file').gid
92
+
93
+ subject.chown(42, nil, '/test-file')
94
+ expect(subject.stat('/test-file').gid).to eq(previous_gid)
95
+ end
96
+
97
+ it "ignores -1 user id" do
98
+ previous_uid = subject.stat('/test-file').uid
99
+
100
+ subject.chown(-1, 42, '/test-file')
101
+ expect(subject.stat('/test-file').uid).to eq(previous_uid)
102
+ end
103
+
104
+ it "ignores -1 group id" do
105
+ previous_gid = subject.stat('/test-file').gid
106
+
107
+ subject.chown(42, -1, '/test-file')
108
+ expect(subject.stat('/test-file').gid).to eq(previous_gid)
109
+ end
110
+
111
+ context "when the named entry is a symlink" do
112
+ it "changes the owner on the last target of the link chain" do
113
+ subject.chown(42, nil, '/test-link')
114
+ expect(subject.stat('/test-file').uid).to eq(42)
115
+ end
116
+
117
+ it "changes the group on the last target of the link chain" do
118
+ subject.chown(nil, 42, '/test-link')
119
+ expect(subject.stat('/test-file').gid).to eq(42)
120
+ end
121
+
122
+ it "does not change the owner of the symlink" do
123
+ subject.chown(42, nil, '/test-link')
124
+ expect(subject.lstat('/test-link').uid).not_to eq(42)
125
+ end
126
+
127
+ it "does not change the group of the symlink" do
128
+ subject.chown(nil, 42, '/test-link')
129
+ expect(subject.lstat('/test-link').gid).not_to eq(42)
130
+ end
131
+ end
132
+ end
133
+
134
+ describe ".delete" do
135
+ it_behaves_like 'aliased method', :delete, :unlink
136
+ end
137
+
138
+ describe '.directory?' do
139
+ context "when the named entry is a directory" do
140
+ it "returns true" do
141
+ expect(subject.directory?('/test-dir')).to be_true
142
+ end
143
+ end
144
+
145
+ context "when the named entry is not a directory" do
146
+ it "returns false" do
147
+ expect(subject.directory?('/test-file')).to be_false
148
+ end
149
+ end
150
+ end
151
+
152
+ describe ".dirname" do
153
+ it "returns all components of the filename given in +file_name+ except the last one" do
154
+ expect(subject.dirname('/path/to/some/file.txt')).to eq('/path/to/some')
155
+ end
156
+
157
+ it "returns / if file_name is /" do
158
+ expect(subject.dirname('/')).to eq('/')
159
+ end
160
+ end
161
+
162
+ describe ".exists?" do
163
+ it "returns true if the file exists" do
164
+ expect(subject.exists?('/test-file')).to be_true
165
+ end
166
+
167
+ it "returns false if the file does not exist" do
168
+ expect(subject.exists?('/no-file')).to be_false
169
+ end
170
+ end
171
+
172
+ describe ".exist?" do
173
+ it_behaves_like 'aliased method', :exist?, :exists?
174
+ end
175
+
176
+ describe ".expand_path" do
177
+ it "converts a pathname to an absolute pathname" do
178
+ fs.chdir('/')
179
+ expect(subject.expand_path('test-file')).to eq("/test-file")
180
+ end
181
+
182
+ it "references path from the current working directory" do
183
+ fs.chdir('/test-dir')
184
+ expect(subject.expand_path('test-file')).to eq("/test-dir/test-file")
185
+ end
186
+
187
+ context "when +dir_string+ is provided" do
188
+ it "uses +dir_string+ as the stating point" do
189
+ expect(subject.expand_path('test-file', '/test')).to eq("/test/test-file")
190
+ end
191
+ end
192
+ end
193
+
194
+ describe ".file?" do
195
+ context "when the named file exists" do
196
+ context "and it is a regular file" do
197
+ it "returns true" do
198
+ expect(subject.file?('/test-file')).to be_true
199
+ end
200
+ end
201
+
202
+ context "and it is not a regular file" do
203
+ it "returns false" do
204
+ expect(subject.file?('/test-dir')).to be_false
205
+ end
206
+ end
207
+ end
208
+
209
+ context "when the named file does not exist" do
210
+ it "returns false" do
211
+ expect(subject.file?('/no-file')).to be_false
212
+ end
213
+ end
214
+ end
215
+
216
+ describe ".identical?" do
217
+ before :each do
218
+ subject.open('/test-file', 'w') { |f| f.puts 'test' }
219
+ subject.open('/test-file2', 'w') { |f| f.puts 'test' }
220
+ subject.symlink '/test-file', '/test-file-link'
221
+ subject.symlink '/test-file', '/test-file-link2'
222
+ subject.symlink '/test-file2', '/test-file2-link'
223
+ end
224
+
225
+ context "when two paths represent the same path" do
226
+ it "returns true" do
227
+ expect(subject.identical?('/test-file', '/test-file')).to be_true
228
+ end
229
+ end
230
+
231
+ context "when two paths do not represent the same file" do
232
+ it "returns false" do
233
+ expect(subject.identical?('/test-file', '/test-file2')).to be_false
234
+ end
235
+ end
236
+
237
+ context "when one of the paths does not exist" do
238
+ it "returns false" do
239
+ expect(subject.identical?('/test-file', '/no-file')).to be_false
240
+ end
241
+ end
242
+
243
+ context "when a path is a symlink" do
244
+ context "and the linked file is the same as the other path" do
245
+ it "returns true" do
246
+ expect(subject.identical?('/test-file', '/test-file-link')).to be_true
247
+ end
248
+ end
249
+
250
+ context "and the linked file is different from the other path" do
251
+ it "returns false" do
252
+ expect(subject.identical?('/test-file2', '/test-file-link')).to be_false
253
+ end
254
+ end
255
+ end
256
+
257
+ context "when the two paths are symlinks" do
258
+ context "and both links point to the same file" do
259
+ it "returns true" do
260
+ expect(subject.identical?('/test-file-link', '/test-file-link2')).to be_true
261
+ end
262
+ end
263
+
264
+ context "and both links do not point to the same file" do
265
+ it "returns false" do
266
+ expect(subject.identical?('/test-file-link', '/test-file2-link')).to be_false
267
+ end
268
+ end
269
+ end
270
+ end
271
+
272
+ describe ".join" do
273
+ it "Returns a new string formed by joining the strings using File::SEPARATOR" do
274
+ expect(subject.join('a', 'b', 'c')).to eq('a/b/c')
275
+ end
276
+ end
277
+
278
+ describe ".lchmod" do
279
+ context "when the named file is a regular file" do
280
+ it "acts like chmod" do
281
+ subject.lchmod(0777, '/test-file')
282
+ expect(subject.stat('/test-file').mode).to eq(0100777)
283
+ end
284
+ end
285
+
286
+ context "when the named file is a symlink" do
287
+ it "changes permission bits on the symlink" do
288
+ subject.lchmod(0777, '/test-link')
289
+ expect(subject.lstat('/test-link').mode).to eq(0100777)
290
+ end
291
+
292
+ it "does not change permission bits on the link's target" do
293
+ mode = subject.stat('/test-file').mode
294
+ subject.lchmod(0777, '/test-link')
295
+ expect(subject.stat('/test-file').mode).to eq(mode)
296
+ end
297
+ end
298
+ end
299
+
300
+ describe ".link" do
301
+ before :each do
302
+ subject.open('/test-file', 'w') { |f| f.puts 'test' }
303
+ end
304
+
305
+ it "creates a new name for an existing file using a hard link" do
306
+ subject.link('/test-file', '/new-file')
307
+ expect(subject.read('/new-file')).to eq(subject.read('/test-file'))
308
+ end
309
+
310
+ it "returns zero" do
311
+ expect(subject.link('/test-file', '/new-file')).to eq(0)
312
+ end
313
+
314
+ context "when +old_name+ does not exist" do
315
+ it "raises an exception" do
316
+ expect {
317
+ subject.link('/no-file', '/nowhere')
318
+ }.to raise_error(Errno::ENOENT)
319
+ end
320
+ end
321
+
322
+ context "when +new_name+ already exists" do
323
+ it "raises an exception" do
324
+ subject.open('/test-file2', 'w') { |f| f.puts 'test2' }
325
+ expect {
326
+ subject.link('/test-file', '/test-file2')
327
+ }.to raise_error(SystemCallError)
328
+ end
329
+ end
330
+ end
331
+
332
+ describe '.lstat' do
333
+ it "returns a File::Stat object for the named file" do
334
+ expect(subject.lstat('/test-file')).to be_a(File::Stat)
335
+ end
336
+
337
+ context "when the named file does not exist" do
338
+ it "raises an exception" do
339
+ expect { subject.lstat('/no-file') }.to raise_error(Errno::ENOENT)
340
+ end
341
+ end
342
+
343
+ context "when the named file is a symlink" do
344
+ it "does not follow the last symbolic link" do
345
+ expect(subject.lstat('/test-link').symlink?).to be_true
346
+ end
347
+
348
+ context "and its target does not exist" do
349
+ it "ignores errors" do
350
+ expect {
351
+ subject.lstat('/no-link')
352
+ }.not_to raise_error(Errno::ENOENT)
353
+ end
354
+ end
355
+ end
356
+ end
357
+
358
+ describe '.new' do
359
+ context "when the mode is provided" do
360
+ context "and it is an integer" do
361
+ it "sets the mode to the integer value" do
362
+ file = subject.new('/test-file', File::RDWR)
363
+ expect(file.opening_mode).to eq(File::RDWR)
364
+ end
365
+ end
366
+
367
+ context "and it is a string" do
368
+ it "sets the mode to the integer value" do
369
+ file = subject.new('/test-file', 'r+')
370
+ expect(file.opening_mode).to eq(File::RDWR)
371
+ end
372
+ end
373
+
374
+ context "and it specifies that the file must be created" do
375
+ context "and the file already exists" do
376
+ it "changes the mtime of the file" do
377
+ fs.should_receive(:touch).with('/test-file')
378
+ subject.new('/test-file', 'w')
379
+ end
380
+ end
381
+ end
382
+ end
383
+
384
+ context "when no argument is given" do
385
+ it "raises an exception" do
386
+ expect { subject.new }.to raise_error(ArgumentError)
387
+ end
388
+ end
389
+
390
+ context "when too many arguments are given" do
391
+ it "raises an exception" do
392
+ expect { subject.new(1,2,3,4) }.to raise_error(ArgumentError)
393
+ end
394
+ end
395
+ end
396
+
397
+ describe '.path' do
398
+ context "when the path is a string" do
399
+ let(:path) { '/some/path' }
400
+
401
+ it "returns the string representation of the path" do
402
+ expect(subject.path(path)).to eq('/some/path')
403
+ end
404
+ end
405
+
406
+ context "when the path is a Pathname" do
407
+ let(:path) { Pathname.new('/some/path') }
408
+
409
+ it "returns the string representation of the path" do
410
+ expect(subject.path(path)).to eq('/some/path')
411
+ end
412
+ end
413
+ end
414
+
415
+ describe ".read" do
416
+ before :each do
417
+ subject.open('/test-file', 'w') { |f| f.puts "test" }
418
+ end
419
+
420
+ it "reads the content of the given file" do
421
+ expect(subject.read('/test-file')).to eq("test\n")
422
+ end
423
+
424
+ context "when +lenght+ is provided" do
425
+ it "reads only +length+ characters" do
426
+ expect(subject.read('/test-file', 2)).to eq('te')
427
+ end
428
+
429
+ context "when +length+ is bigger than the file size" do
430
+ it "reads until the end of the file" do
431
+ expect(subject.read('/test-file', 1000)).to eq("test\n")
432
+ end
433
+ end
434
+ end
435
+
436
+ context "when +offset+ is provided" do
437
+ it "starts reading from the offset" do
438
+ expect(subject.read('/test-file', 2, 1)).to eq('es')
439
+ end
440
+
441
+ it "raises an error if offset is negative" do
442
+ expect {
443
+ subject.read('/test-file', 2, -1)
444
+ }.to raise_error(Errno::EINVAL)
445
+ end
446
+ end
447
+
448
+ context "when the last argument is a hash" do
449
+ it "passes the contained options to +open+" do
450
+ subject.should_receive(:open)
451
+ .with('/test-file', File::RDONLY, encoding: 'UTF-8')
452
+ .and_return(file)
453
+ subject.read('/test-file', encoding: 'UTF-8')
454
+ end
455
+
456
+ context "when it contains the +open_args+ key" do
457
+ it "takes precedence over the other options" do
458
+ subject.should_receive(:open)
459
+ .with('/test-file', 'r')
460
+ .and_return(file)
461
+ subject.read('/test-file', mode: 'w', open_args: ['r'])
462
+ end
463
+ end
464
+ end
465
+ end
466
+
467
+ describe ".readlink" do
468
+ it "returns the name of the file referenced by the given link" do
469
+ expect(subject.readlink('/test-link')).to eq('/test-file')
470
+ end
471
+ end
472
+
473
+ describe ".rename" do
474
+ it "renames the given file to the new name" do
475
+ subject.rename('/test-file', '/test-file2')
476
+ expect(subject.exists?('/test-file2')).to be_true
477
+ end
478
+
479
+ it "returns zero" do
480
+ expect(subject.rename('/test-file', '/test-file2')).to eq(0)
481
+ end
482
+ end
483
+
484
+ describe ".size" do
485
+ it "returns the size of the file" do
486
+ subject.open('/test-file', 'w') { |f| f.puts random_string }
487
+ expect(subject.size('/test-file')).to eq(random_string.size + 1)
488
+ end
489
+ end
490
+
491
+ describe '.stat' do
492
+ it "returns a File::Stat object for the named file" do
493
+ expect(subject.stat('/test-file')).to be_a(File::Stat)
494
+ end
495
+
496
+ it "follows the last symbolic link" do
497
+ expect(subject.stat('/test-link').symlink?).to be_false
498
+ end
499
+
500
+ context "when the named file does not exist" do
501
+ it "raises an exception" do
502
+ expect { subject.stat('/no-file') }.to raise_error(Errno::ENOENT)
503
+ end
504
+ end
505
+
506
+ context "when the named file is a symlink" do
507
+ context "and its target does not exist" do
508
+ it "raises an exception" do
509
+ expect { subject.stat('/no-link') }.to raise_error(Errno::ENOENT)
510
+ end
511
+ end
512
+ end
513
+
514
+ it "always returns a new object" do
515
+ stat = subject.stat('/test-file')
516
+ expect(subject.stat('/test-file')).not_to be(stat)
517
+ end
518
+ end
519
+
520
+ describe '.symlink' do
521
+ it "creates a symbolic link named new_name" do
522
+ expect(subject.symlink?('/test-link')).to be_true
523
+ end
524
+
525
+ it "creates a symbolic link that points to an entry named old_name" do
526
+ expect(fs.find!('/test-link').target).to eq('/test-file')
527
+ end
528
+
529
+ context "when the target does not exist" do
530
+ it "creates a symbolic link" do
531
+ expect(subject.symlink?('/no-link')).to be_true
532
+ end
533
+ end
534
+
535
+ it "returns 0" do
536
+ expect(subject.symlink('/test-file', '/new-link')).to eq(0)
537
+ end
538
+ end
539
+
540
+ describe '.symlink?' do
541
+ context "when the named entry is a symlink" do
542
+ it "returns true" do
543
+ expect(subject.symlink?('/test-link')).to be_true
544
+ end
545
+ end
546
+
547
+ context "when the named entry is not a symlink" do
548
+ it "returns false" do
549
+ expect(subject.symlink?('/test-file')).to be_false
550
+ end
551
+ end
552
+
553
+ context "when the named entry does not exist" do
554
+ it "returns false" do
555
+ expect(subject.symlink?('/no-file')).to be_false
556
+ end
557
+ end
558
+ end
559
+
560
+ describe '.umask' do
561
+ before :each do
562
+ subject.umask(0022)
563
+ end
564
+
565
+ it "returns the current umask value for this process" do
566
+ expect(subject.umask).to eq(0022)
567
+ end
568
+
569
+ context "when the optional argument is given" do
570
+ it "sets the umask to that value" do
571
+ subject.umask 0777
572
+ expect(subject.umask).to eq(0777)
573
+ end
574
+
575
+ it "return the previous value" do
576
+ expect(subject.umask(0777)).to eq(0022)
577
+ end
578
+ end
579
+ end
580
+
581
+ describe ".unlink" do
582
+ it "deletes the named file" do
583
+ subject.unlink('/test-file')
584
+ expect(subject.exists?('/test-file')).to be_false
585
+ end
586
+
587
+ it "returns the number of names passed as arguments" do
588
+ expect(subject.unlink('/test-file', '/test-file2')).to eq(2)
589
+ end
590
+
591
+ context "when multiple file names are given" do
592
+ it "deletes the named files" do
593
+ subject.unlink('/test-file', '/test-file2')
594
+ expect(subject.exists?('/test-file2')).to be_false
595
+ end
596
+ end
597
+
598
+ context "when the entry is a directory" do
599
+ it "raises an exception" do
600
+ expect { subject.unlink('/test-dir') }.to raise_error(Errno::EPERM)
601
+ end
602
+ end
603
+ end
604
+
605
+ describe '.utime' do
606
+ let(:time) { Time.now - 500000 }
607
+
608
+ it "sets the access time of each named file to the first argument" do
609
+ subject.utime(time, time, '/test-file')
610
+ expect(subject.atime('/test-file')).to eq(time)
611
+ end
612
+
613
+ it "sets the modification time of each named file to the second argument" do
614
+ subject.utime(time, time, '/test-file')
615
+ expect(subject.mtime('/test-file')).to eq(time)
616
+ end
617
+
618
+ it "returns the number of file names in the argument list" do
619
+ expect(subject.utime(time, time, '/test-file', '/test-file2')).to eq(2)
620
+ end
621
+
622
+ it "raises en error if the entry does not exist" do
623
+ expect {
624
+ subject.utime(time, time, '/no-file')
625
+ }.to raise_error(Errno::ENOENT)
626
+ end
627
+ end
628
+
629
+ describe '#chmod' do
630
+ it "changes permission bits on the file" do
631
+ file.chmod(0777)
632
+ expect(file.stat.mode).to eq(0100777)
633
+ end
634
+
635
+ it "returns zero" do
636
+ expect(file.chmod(0777)).to eq(0)
637
+ end
638
+ end
639
+
640
+ describe "#chown" do
641
+ it "changes the owner of the named file to the given numeric owner id" do
642
+ file.chown(42, nil)
643
+ expect(file.stat.uid).to be(42)
644
+ end
645
+
646
+ it "changes owner on the named files (in list)" do
647
+ file.chown(42)
648
+ expect(file.stat.uid).to be(42)
649
+ end
650
+
651
+ it "changes the group of the named file to the given numeric group id" do
652
+ file.chown(nil, 42)
653
+ expect(file.stat.gid).to be(42)
654
+ end
655
+
656
+ it "returns zero" do
657
+ expect(file.chown(42, 42)).to eq(0)
658
+ end
659
+
660
+ it "ignores nil user id" do
661
+ previous_uid = file.stat.uid
662
+
663
+ file.chown(nil, 42)
664
+ expect(file.stat.uid).to eq(previous_uid)
665
+ end
666
+
667
+ it "ignores nil group id" do
668
+ previous_gid = file.stat.gid
669
+
670
+ file.chown(42, nil)
671
+ expect(file.stat.gid).to eq(previous_gid)
672
+ end
673
+
674
+ it "ignores -1 user id" do
675
+ previous_uid = file.stat.uid
676
+
677
+ file.chown(-1, 42)
678
+ expect(file.stat.uid).to eq(previous_uid)
679
+ end
680
+
681
+ it "ignores -1 group id" do
682
+ previous_gid = file.stat.gid
683
+
684
+ file.chown(42, -1)
685
+ expect(file.stat.gid).to eq(previous_gid)
686
+ end
687
+
688
+ context "when the named entry is a symlink" do
689
+ let(:symlink) { subject.new('/test-link') }
690
+
691
+ it "changes the owner on the last target of the link chain" do
692
+ symlink.chown(42, nil)
693
+ expect(file.stat.uid).to be(42)
694
+ end
695
+
696
+ it "changes the group on the last target of the link chain" do
697
+ symlink.chown(nil, 42)
698
+ expect(file.stat.gid).to be(42)
699
+ end
700
+
701
+ it "does not change the owner of the symlink" do
702
+ symlink.chown(42, nil)
703
+ expect(symlink.lstat.uid).not_to be(42)
704
+ end
705
+
706
+ it "does not change the group of the symlink" do
707
+ symlink.chown(nil, 42)
708
+ expect(symlink.lstat.gid).not_to be(42)
709
+ end
710
+ end
711
+ end
712
+
713
+ describe "#close" do
714
+ it "closes the file stream" do
715
+ file = subject.open('/test-file')
716
+ file.close
717
+ expect(file).to be_closed
718
+ end
719
+ end
720
+
721
+ describe "#closed?" do
722
+ it "returns true when the file is closed" do
723
+ file = subject.open('/test-file')
724
+ file.close
725
+ expect(file.closed?).to be_true
726
+ end
727
+
728
+ it "returns false when the file is open" do
729
+ file = subject.open('/test-file')
730
+ expect(file.closed?).to be_false
731
+ file.close
732
+ end
733
+ end
734
+
735
+ describe '#lstat' do
736
+ it "returns the File::Stat object of the file" do
737
+ expect(file.lstat).to be_a(File::Stat)
738
+ end
739
+
740
+ it "does not follow the last symbolic link" do
741
+ file = subject.new('/test-link')
742
+ expect(file.lstat).to be_symlink
743
+ end
744
+
745
+ context "when the named file is a symlink" do
746
+ context "and its target does not exist" do
747
+ it "ignores errors" do
748
+ file = subject.new('/no-link')
749
+ expect { file.lstat }.not_to raise_error(Errno::ENOENT)
750
+ end
751
+ end
752
+ end
753
+ end
754
+
755
+ describe "#path" do
756
+ it "returns the path of the file" do
757
+ file = subject.new('/test-file')
758
+ expect(file.path).to eq('/test-file')
759
+ end
760
+ end
761
+
762
+ describe "#pos" do
763
+ before :each do
764
+ subject.open('/test-file', 'w') { |f| f.puts 'test' }
765
+ end
766
+
767
+ it "returns zero when the file was just opened" do
768
+ expect(file.pos).to be_zero
769
+ end
770
+
771
+ it "returns the reading offset when some of the file has been read" do
772
+ file.read(2)
773
+ expect(file.pos).to eq(2)
774
+ end
775
+ end
776
+
777
+ describe "#puts" do
778
+ it "appends content to the file" do
779
+ file = subject.new('/test-file', 'w')
780
+ file.puts "test"
781
+ file.close
782
+ expect(file.content.to_s).to eq("test\n")
783
+ end
784
+
785
+ it "does not override the file's content" do
786
+ file = subject.new('/test-file', 'w')
787
+ file.puts "test"
788
+ file.puts "test"
789
+ file.close
790
+ expect(file.content.to_s).to eq("test\ntest\n")
791
+ end
792
+
793
+ it "raises an exception if the file is not writable" do
794
+ file = subject.new('/test-file')
795
+ expect { file.puts "test" }.to raise_error(IOError)
796
+ end
797
+ end
798
+
799
+ describe "#read" do
800
+ before :each do
801
+ subject.open('/test-file', 'w') { |f| f.puts random_string }
802
+ end
803
+
804
+ context "when no length is given" do
805
+ it "returns the content of the named file" do
806
+ expect(file.read).to eq(random_string + "\n")
807
+ end
808
+
809
+ it "returns an empty string if called a second time" do
810
+ file.read
811
+ expect(file.read).to be_empty
812
+ end
813
+ end
814
+
815
+ context "when a length is given" do
816
+ it "returns a string of the given length" do
817
+ expect(file.read(2)).to eq(random_string[0, 2])
818
+ end
819
+
820
+ it "returns nil when there is nothing more to read" do
821
+ file.read(1000)
822
+ expect(file.read(1000)).to be_nil
823
+ end
824
+ end
825
+
826
+ context "when a buffer is given" do
827
+ it "fills the buffer with the read content" do
828
+ buffer = String.new
829
+ file.read(2, buffer)
830
+ expect(buffer).to eq(random_string[0, 2])
831
+ end
832
+ end
833
+ end
834
+
835
+ describe "#seek" do
836
+ before :each do
837
+ subject.open('/test-file', 'w') { |f| f.puts 'test' }
838
+ end
839
+
840
+ it "returns zero" do
841
+ expect(file.seek(1)).to eq(0)
842
+ end
843
+
844
+ context "when +whence+ is not provided" do
845
+ it "seeks to the absolute location given by +amount+" do
846
+ file.seek(3)
847
+ expect(file.pos).to eq(3)
848
+ end
849
+ end
850
+
851
+ context "when +whence+ is IO::SEEK_CUR" do
852
+ it "seeks to +amount+ plus current position" do
853
+ file.read(1)
854
+ file.seek(1, IO::SEEK_CUR)
855
+ expect(file.pos).to eq(2)
856
+ end
857
+ end
858
+
859
+ context "when +whence+ is IO::SEEK_END" do
860
+ it "seeks to +amount+ plus end of stream" do
861
+ file.seek(-1, IO::SEEK_END)
862
+ expect(file.pos).to eq(4)
863
+ end
864
+ end
865
+
866
+ context "when +whence+ is IO::SEEK_SET" do
867
+ it "seeks to the absolute location given by +amount+" do
868
+ file.seek(3, IO::SEEK_SET)
869
+ expect(file.pos).to eq(3)
870
+ end
871
+ end
872
+
873
+ context "when +whence+ is invalid" do
874
+ it "raises an exception" do
875
+ expect { file.seek(0, 42) }.to raise_error(Errno::EINVAL)
876
+ end
877
+ end
878
+
879
+ context "if the position ends up to be less than zero" do
880
+ it "raises an exception" do
881
+ expect { file.seek(-1) }.to raise_error(Errno::EINVAL)
882
+ end
883
+ end
884
+ end
885
+
886
+ describe "#size" do
887
+ it "returns the size of the file" do
888
+ subject.open('/test-file', 'w') { |f| f.puts random_string }
889
+ expect(subject.new('/test-file').size).to eq(random_string.size + 1)
890
+ end
891
+ end
892
+
893
+ describe "#stat" do
894
+ it "returns the +Stat+ object of the file" do
895
+ file = subject.new('/test-file')
896
+ file.stat == subject.stat('/test-file')
897
+ end
898
+ end
899
+
900
+ describe "#write" do
901
+ it "writes the given string to file" do
902
+ subject.open('/test-file', 'w') { |f| f.write "test" }
903
+ expect(subject.read('/test-file')).to eq("test")
904
+ end
905
+
906
+ it "returns the number of bytes written" do
907
+ file = subject.open('/test-file', 'w')
908
+ expect(file.write('test')).to eq(4)
909
+ file.close
910
+ end
911
+
912
+ context "when the file is not opened for writing" do
913
+ it "raises an exception" do
914
+ file = subject.open('/test-file')
915
+ expect { file.write('test') }.to raise_error(IOError)
916
+ file.close
917
+ end
918
+ end
919
+
920
+ context "when the argument is not a string" do
921
+ it "will be converted to a string using to_s" do
922
+ subject.open('/test-file', 'w') { |f| f.write 42 }
923
+ expect(subject.read('/test-file')).to eq('42')
924
+ end
925
+ end
926
+ end
927
+ end
928
+ end