fakefs 2.6.0 → 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/fakefs/file.rb +227 -52
- data/lib/fakefs/flockable_file.rb +9 -5
- data/lib/fakefs/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c651948e15e48a7fc0a55e795e637b8a7f416b3346d6b7732f9c9db36750b6b
|
4
|
+
data.tar.gz: e5b0f2eea98fe2a4d58a34daf9ad8af627186eff773fc42f93790a1ca048d5d5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8c356f48b8cab450871cffbf4dca4ca48bdf7631201c624c5cabf4a8482867cc07258900de12ee49aa0409a9b2b186c99f2654c0a564b5bdcf796347c0e68f1a
|
7
|
+
data.tar.gz: 8ad9c0fa9d589ef555c6c25514e1f743afe43da096092ae4e8b3c21d5a0291014ef30cc4ed4829724a660a2ebaf033b50097ebcf47f0a35e943649df9b368b8f
|
data/lib/fakefs/file.rb
CHANGED
@@ -12,8 +12,26 @@ module FakeFS
|
|
12
12
|
APPEND_READ_WRITE = 'a+'.freeze
|
13
13
|
].freeze
|
14
14
|
|
15
|
+
FMODE_READABLE = 0x00000001
|
16
|
+
FMODE_WRITABLE = 0x00000002
|
17
|
+
FMODE_READWRITE = (FMODE_READABLE | FMODE_WRITABLE)
|
18
|
+
FMODE_BINMODE = 0x00000004
|
19
|
+
FMODE_APPEND = 0x00000040
|
20
|
+
FMODE_CREATE = 0x00000080
|
21
|
+
FMODE_EXCL = 0x00000400
|
22
|
+
FMODE_TRUNC = 0x00000800
|
23
|
+
FMODE_TEXTMODE = 0x00001000
|
24
|
+
|
25
|
+
TEXT_MODES = {
|
26
|
+
'r' => FMODE_READABLE,
|
27
|
+
'w' => FMODE_WRITABLE | FMODE_TRUNC | FMODE_CREATE,
|
28
|
+
'a' => FMODE_WRITABLE | FMODE_APPEND | FMODE_CREATE
|
29
|
+
}.freeze
|
30
|
+
|
15
31
|
FILE_CREATION_MODES = (MODES - [READ_ONLY, READ_WRITE]).freeze
|
32
|
+
FILE_ACCESS_MODE = (RealFile::RDONLY | RealFile::WRONLY | RealFile::RDWR)
|
16
33
|
|
34
|
+
# copied from https://github.com/ruby/ruby/blob/v2_7_8/include/ruby/io.h#L108
|
17
35
|
MODE_BITMASK = (
|
18
36
|
RealFile::RDONLY |
|
19
37
|
RealFile::WRONLY |
|
@@ -23,12 +41,11 @@ module FakeFS
|
|
23
41
|
RealFile::EXCL |
|
24
42
|
RealFile::NONBLOCK |
|
25
43
|
RealFile::TRUNC |
|
44
|
+
RealFile::BINARY |
|
26
45
|
(RealFile.const_defined?(:NOCTTY) ? RealFile::NOCTTY : 0) |
|
27
46
|
(RealFile.const_defined?(:SYNC) ? RealFile::SYNC : 0)
|
28
47
|
)
|
29
48
|
|
30
|
-
FILE_CREATION_BITMASK = RealFile::CREAT
|
31
|
-
|
32
49
|
def self.extname(path)
|
33
50
|
RealFile.extname(path)
|
34
51
|
end
|
@@ -181,6 +198,7 @@ module FakeFS
|
|
181
198
|
symlink.target
|
182
199
|
end
|
183
200
|
|
201
|
+
# TODO: support open_key_args
|
184
202
|
def self.read(path, *args)
|
185
203
|
options = args[-1].is_a?(Hash) ? args.pop : {}
|
186
204
|
length = args.empty? ? nil : args.shift
|
@@ -490,17 +508,102 @@ module FakeFS
|
|
490
508
|
|
491
509
|
attr_reader :path
|
492
510
|
|
493
|
-
def initialize(path,
|
511
|
+
def initialize(path, *args)
|
512
|
+
# unable to pass args otherwise on jruby, it may cause false passes on MRI, though
|
513
|
+
# because explicit hash isn't supported
|
514
|
+
opts = args.last.is_a?(Hash) ? args.pop : {}
|
515
|
+
if args.size > 2
|
516
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size + 1}, expected 1..3)"
|
517
|
+
end
|
518
|
+
mode, _perm = args
|
519
|
+
|
494
520
|
@path = path
|
495
|
-
@
|
496
|
-
|
497
|
-
|
521
|
+
@file = FileSystem.find(@path)
|
522
|
+
# real rb_scan_open_args - and rb_io_extract_modeenc - is much more complex
|
523
|
+
raise ArgumentError, 'mode specified twice' unless mode.nil? || opts[:mode].nil?
|
524
|
+
|
525
|
+
mode_opt = mode.nil? ? opts[:mode] : mode
|
526
|
+
# see vmode_handle
|
527
|
+
if mode_opt.nil?
|
528
|
+
@oflags = RealFile::RDONLY
|
529
|
+
elsif mode_opt.respond_to?(:to_int) && (intmode = mode_opt.to_int).instance_of?(Integer)
|
530
|
+
@oflags = intmode
|
531
|
+
else
|
532
|
+
unless mode_opt.is_a?(String)
|
533
|
+
unless mode_opt.respond_to?(:to_str)
|
534
|
+
raise TypeError, "no implicit conversion of #{mode_opt.class} into String"
|
535
|
+
end
|
536
|
+
|
537
|
+
strmode = mode_opt.to_str
|
538
|
+
unless strmode.is_a?(String)
|
539
|
+
raise TypeError, "can't convert #{mode_opt.class} to String " \
|
540
|
+
"(#{mode_opt.class}#to_str gives #{strmode.class})"
|
541
|
+
end
|
542
|
+
|
543
|
+
mode_opt = strmode
|
544
|
+
end
|
545
|
+
|
546
|
+
@oflags, @fmode = parse_strmode_oflags(mode_opt)
|
547
|
+
end
|
548
|
+
unless opts[:flags].nil?
|
549
|
+
if opts[:flags].is_a?(Integer)
|
550
|
+
@oflags |= opts[:flags]
|
551
|
+
elsif opts[:flags].respond_to?(:to_int)
|
552
|
+
intflags = opts[:flags].to_int
|
553
|
+
unless intflags.instance_of?(Integer)
|
554
|
+
raise TypeError, "can't convert #{opts[:flags].class} to Integer " \
|
555
|
+
"(#{opts[:flags].class}#to_int gives #{intflags.class})"
|
556
|
+
end
|
498
557
|
|
499
|
-
|
558
|
+
@oflags |= intflags
|
559
|
+
@fmode = create_fmode(@oflags)
|
560
|
+
else
|
561
|
+
raise TypeError, "no implicit conversion of #{opts[:flags].class} into Integer"
|
562
|
+
end
|
563
|
+
end
|
564
|
+
@fmode ||= create_fmode(@oflags)
|
565
|
+
@fmode = extract_binmode(opts, @fmode)
|
500
566
|
|
567
|
+
@autoclose = true
|
501
568
|
file_creation_mode? ? create_missing_file : check_file_existence!
|
569
|
+
# StringIO changes enciding of the underlying string to binary
|
570
|
+
# when binary data is written if it's opened in binary mode,
|
571
|
+
# so content might have binary encoding. StringIO also switches to
|
572
|
+
# binary mode if its string have binary encoding, but it might not
|
573
|
+
# be what we want, so insteed we use encoding parsed after super call
|
574
|
+
# and force set it back.
|
575
|
+
|
576
|
+
# truncate doesn't work
|
577
|
+
@file.content.force_encoding(Encoding.default_external)
|
578
|
+
# StringIO.new 'content', nil, **{} # works in MRI, but fails in JRuby
|
579
|
+
# but File.open 'filename', nil, **{} is ok both in MRI and JRuby
|
580
|
+
|
581
|
+
# JRuby StringIO doesn't support kwargs without mode
|
582
|
+
# StringIO.new "buff", encoding: 'binary' # work on MRI, fails on JRuby
|
583
|
+
if RUBY_PLATFORM == "java"
|
584
|
+
# other opts aren't supported
|
585
|
+
super(@file.content, mode_opt || 'r')
|
586
|
+
binmode if binmode? # Looks like it doesn't care about 'b'
|
587
|
+
mode_opt_str = mode_opt.is_a?(String) ? mode_opt : ''
|
588
|
+
raise ArgumentError, 'encoding specified twice' if mode_opt_str[':'] && opts[:encoding]
|
589
|
+
|
590
|
+
# Might raise where real File just warns
|
591
|
+
str_encoding = mode_opt_str.split(':')[1] # internal encoding is ignored anyway
|
592
|
+
if opts[:encoding]
|
593
|
+
set_encoding(opts[:encoding])
|
594
|
+
elsif str_encoding && str_encoding != ''
|
595
|
+
set_encoding(str_encoding)
|
596
|
+
elsif opts[:binmode]
|
597
|
+
set_encoding(Encoding::BINARY)
|
598
|
+
end
|
599
|
+
else
|
600
|
+
super(@file.content, mode, **opts)
|
601
|
+
end
|
502
602
|
|
503
|
-
|
603
|
+
# StringIO is wrtable and readable by default, so we need to disable it
|
604
|
+
# but maybe it was explicitly disabled by opts
|
605
|
+
close_write if @fmode & FMODE_WRITABLE == 0 && !StringIO.instance_method(:closed_write?).bind(self).call
|
606
|
+
close_read if @fmode & FMODE_READABLE == 0 && !StringIO.instance_method(:closed_read?).bind(self).call
|
504
607
|
end
|
505
608
|
|
506
609
|
def exists?
|
@@ -622,10 +725,8 @@ module FakeFS
|
|
622
725
|
end
|
623
726
|
|
624
727
|
def binmode?
|
625
|
-
|
626
|
-
|
627
|
-
@mode.include?('binary')
|
628
|
-
) && !@mode.include?('bom')
|
728
|
+
# File.open('test_mode', mode: 'w:binary').binmode? # => false
|
729
|
+
@fmode & FMODE_BINMODE != 0
|
629
730
|
end
|
630
731
|
|
631
732
|
def close_on_exec=(_bool)
|
@@ -662,31 +763,34 @@ module FakeFS
|
|
662
763
|
|
663
764
|
def advise(_advice, _offset = 0, _len = 0); end
|
664
765
|
|
665
|
-
def self.write(filename, contents, offset = nil, open_args
|
666
|
-
offset, open_args = nil, offset if offset.is_a?(Hash)
|
766
|
+
def self.write(filename, contents, offset = nil, **open_args)
|
667
767
|
mode = offset ? 'r+' : 'w'
|
668
|
-
if open_args
|
669
|
-
|
670
|
-
|
768
|
+
if open_args[:open_args]
|
769
|
+
# see open_key_args
|
770
|
+
# todo: foreach, readlines, read also use it
|
771
|
+
# Treat a final argument as keywords if it is a hash, and not as keywords otherwise.
|
772
|
+
open_args = open_args[:open_args]
|
773
|
+
if open_args.last.is_a?(Hash)
|
774
|
+
args = open_args[0...-1]
|
775
|
+
opt = open_args.last
|
671
776
|
else
|
672
|
-
|
673
|
-
|
777
|
+
args = open_args
|
778
|
+
opt = {}
|
674
779
|
end
|
675
780
|
else
|
676
|
-
args = [
|
781
|
+
args = [open_args.delete(:mode) || mode]
|
782
|
+
opt = open_args
|
677
783
|
end
|
678
784
|
if offset
|
679
|
-
open(*args) do |f| # rubocop:disable Security/Open
|
785
|
+
open(filename, *args, **opt) do |f| # rubocop:disable Security/Open
|
680
786
|
f.seek(offset)
|
681
787
|
f.write(contents)
|
682
788
|
end
|
683
789
|
else
|
684
|
-
open(*args) do |f| # rubocop:disable Security/Open
|
685
|
-
f
|
790
|
+
open(filename, *args, **opt) do |f| # rubocop:disable Security/Open
|
791
|
+
f.write(contents)
|
686
792
|
end
|
687
793
|
end
|
688
|
-
|
689
|
-
contents.length
|
690
794
|
end
|
691
795
|
|
692
796
|
def self.birthtime(path)
|
@@ -701,16 +805,6 @@ module FakeFS
|
|
701
805
|
self.class.birthtime(@path)
|
702
806
|
end
|
703
807
|
|
704
|
-
def read(length = nil, buf = '')
|
705
|
-
read_buf = super(length, buf)
|
706
|
-
if binmode?
|
707
|
-
read_buf&.force_encoding('ASCII-8BIT')
|
708
|
-
else
|
709
|
-
read_buf&.force_encoding(Encoding.default_external)
|
710
|
-
end
|
711
|
-
read_buf
|
712
|
-
end
|
713
|
-
|
714
808
|
def self.convert_symbolic_chmod_to_absolute(new_mode, current_mode)
|
715
809
|
# mode always must be of form <GROUP1>=<FLAGS>,<GROUP2>=<FLAGS,...
|
716
810
|
# e.g.: u=wr,go=x
|
@@ -832,7 +926,7 @@ module FakeFS
|
|
832
926
|
elsif assignment_mode == '-'
|
833
927
|
current_group_mode & ~chmod_perm_num
|
834
928
|
else
|
835
|
-
raise
|
929
|
+
raise ArgumentError, "Unknown assignment mode #{assignment_mode}"
|
836
930
|
end
|
837
931
|
end
|
838
932
|
|
@@ -866,8 +960,95 @@ module FakeFS
|
|
866
960
|
|
867
961
|
private
|
868
962
|
|
869
|
-
def
|
870
|
-
|
963
|
+
def extract_binmode(opts, fmode)
|
964
|
+
textmode = opts[:textmode]
|
965
|
+
unless textmode.nil?
|
966
|
+
raise ArgumentError, "textmode specified twice" if fmode & FMODE_TEXTMODE != 0
|
967
|
+
raise ArgumentError, "both textmode and binmode specified" if fmode & FMODE_BINMODE != 0
|
968
|
+
|
969
|
+
# yep, false has no effect here, but still causes ArgumentError
|
970
|
+
fmode |= FMODE_TEXTMODE if textmode
|
971
|
+
end
|
972
|
+
|
973
|
+
binmode = opts[:binmode]
|
974
|
+
unless binmode.nil?
|
975
|
+
raise ArgumentError, "binmode specified twice" if fmode & FMODE_BINMODE != 0
|
976
|
+
raise ArgumentError, "both textmode and binmode specified" if fmode & FMODE_TEXTMODE != 0
|
977
|
+
|
978
|
+
fmode |= FMODE_BINMODE if binmode
|
979
|
+
end
|
980
|
+
|
981
|
+
if fmode & FMODE_BINMODE != 0 && fmode & FMODE_TEXTMODE != 0
|
982
|
+
raise ArgumentError, "both textmode and binmode specified"
|
983
|
+
end
|
984
|
+
|
985
|
+
fmode
|
986
|
+
end
|
987
|
+
|
988
|
+
def create_fmode(oflags)
|
989
|
+
# rb_io_oflags_fmode
|
990
|
+
fmode = 0
|
991
|
+
case oflags & FILE_ACCESS_MODE
|
992
|
+
when RealFile::RDONLY
|
993
|
+
fmode = FMODE_READABLE
|
994
|
+
when RealFile::WRONLY
|
995
|
+
fmode = FMODE_WRITABLE
|
996
|
+
when RealFile::RDWR
|
997
|
+
fmode = FMODE_READWRITE
|
998
|
+
end
|
999
|
+
fmode |= FMODE_APPEND if (oflags & RealFile::APPEND) != 0
|
1000
|
+
fmode |= FMODE_TRUNC if (oflags & RealFile::TRUNC) != 0
|
1001
|
+
fmode |= FMODE_CREATE if (oflags & RealFile::CREAT) != 0
|
1002
|
+
fmode |= FMODE_EXCL if (oflags & RealFile::EXCL) != 0
|
1003
|
+
fmode |= FMODE_BINMODE if (oflags & RealFile::BINARY) != 0
|
1004
|
+
fmode
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
def parse_strmode_oflags(mode)
|
1008
|
+
# rb_io_modestr_fmode
|
1009
|
+
access_mode = mode[0]
|
1010
|
+
mode_modificators = mode[1..-1].split(':', 2).first
|
1011
|
+
fmode = TEXT_MODES[access_mode]
|
1012
|
+
raise ArgumentError, "invalid access mode #{mode}" unless fmode
|
1013
|
+
|
1014
|
+
mode_modificators&.each_char do |m|
|
1015
|
+
case m
|
1016
|
+
when 'b'
|
1017
|
+
fmode |= FMODE_BINMODE
|
1018
|
+
when 't'
|
1019
|
+
fmode |= FMODE_TEXTMODE
|
1020
|
+
when '+'
|
1021
|
+
fmode |= FMODE_READWRITE
|
1022
|
+
when 'x'
|
1023
|
+
raise ArgumentError, "invalid access mode #{mode}" unless access_mode == 'w'
|
1024
|
+
|
1025
|
+
fmode |= FMODE_EXCL
|
1026
|
+
else
|
1027
|
+
raise ArgumentError, "invalid access mode #{mode}"
|
1028
|
+
end
|
1029
|
+
end
|
1030
|
+
if (fmode & FMODE_BINMODE).nonzero? && (fmode & FMODE_TEXTMODE).nonzero?
|
1031
|
+
raise ArgumentError, "invalid access mode #{mode}"
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
# rb_io_fmode_oflags
|
1035
|
+
oflags = 0
|
1036
|
+
case fmode & FMODE_READWRITE
|
1037
|
+
when FMODE_READABLE
|
1038
|
+
oflags |= RealFile::RDONLY
|
1039
|
+
when FMODE_WRITABLE
|
1040
|
+
oflags |= RealFile::WRONLY
|
1041
|
+
when FMODE_READWRITE
|
1042
|
+
oflags |= RealFile::RDWR
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
oflags |= RealFile::APPEND if (fmode & FMODE_APPEND).nonzero?
|
1046
|
+
oflags |= RealFile::TRUNC if (fmode & FMODE_TRUNC).nonzero?
|
1047
|
+
oflags |= RealFile::CREAT if (fmode & FMODE_CREATE).nonzero?
|
1048
|
+
oflags |= RealFile::EXCL if (fmode & FMODE_EXCL).nonzero?
|
1049
|
+
oflags |= RealFile::BINARY if (fmode & FMODE_BINMODE).nonzero?
|
1050
|
+
|
1051
|
+
[oflags, fmode]
|
871
1052
|
end
|
872
1053
|
|
873
1054
|
def check_file_existence!
|
@@ -875,27 +1056,21 @@ module FakeFS
|
|
875
1056
|
end
|
876
1057
|
|
877
1058
|
def file_creation_mode?
|
878
|
-
mode_in?(
|
1059
|
+
mode_in?(RealFile::CREAT)
|
879
1060
|
end
|
880
1061
|
|
881
|
-
def mode_in?(
|
882
|
-
|
883
|
-
list.any? do |element|
|
884
|
-
@mode.include?(element)
|
885
|
-
end
|
886
|
-
end
|
887
|
-
end
|
888
|
-
|
889
|
-
def mode_in_bitmask?(mask)
|
890
|
-
(@mode & mask) != 0 if @mode.is_a?(Integer)
|
1062
|
+
def mode_in?(mask)
|
1063
|
+
(@oflags & mask) != 0
|
891
1064
|
end
|
892
1065
|
|
893
1066
|
# Create a missing file if the path is valid.
|
894
1067
|
#
|
895
1068
|
def create_missing_file
|
896
|
-
|
897
|
-
|
898
|
-
|
1069
|
+
if @file
|
1070
|
+
raise Errno::EEXIST, @path.to_s if mode_in?(RealFile::EXCL)
|
1071
|
+
raise Errno::EISDIR, path.to_s if File.directory?(@path)
|
1072
|
+
return
|
1073
|
+
end
|
899
1074
|
dirname = RealFile.dirname @path
|
900
1075
|
|
901
1076
|
unless dirname == '.'
|
@@ -27,18 +27,22 @@ module FakeFS
|
|
27
27
|
int_mode = mode.to_int
|
28
28
|
|
29
29
|
unless int_mode.is_a?(Integer)
|
30
|
-
raise TypeError, "can't convert
|
30
|
+
raise TypeError, "can't convert #{mode.class} to Integer (#{mode.class}#to_int gives #{int_mode.class})"
|
31
31
|
end
|
32
32
|
mode = int_mode
|
33
33
|
end
|
34
34
|
|
35
|
-
# real implementation may not fail on `flock 11111` -
|
36
|
-
#
|
37
|
-
#
|
35
|
+
# In fact, real implementation may not fail on `flock 11111` -
|
36
|
+
# - but fails with `f1.flock 11111111` - or may fail
|
37
|
+
# with another error - `f1.flock 1111111111111` gives
|
38
|
+
# `integer 1111111111111 too big to convert to `int' (RangeError)`
|
39
|
+
# - but I think it's safer to allow only documented modes.
|
38
40
|
unless FAKE_FS_ALLOWED_FLOCK_MODES.include?(mode)
|
39
41
|
# real exception
|
40
42
|
# Invalid argument @ rb_file_flock - filepath (Errno::EINVAL)
|
41
|
-
raise Errno::EINVAL.new(@path, 'rb_file_flock')
|
43
|
+
# raise Errno::EINVAL.new(@path, 'rb_file_flock')
|
44
|
+
# represents it better, but fails on JRuby
|
45
|
+
raise Errno::EINVAL, @path
|
42
46
|
end
|
43
47
|
0
|
44
48
|
end
|
data/lib/fakefs/version.rb
CHANGED