fakefs 2.6.0 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: ae15dc7d6aeb86af398a96ad824d7e53fa9ea82d73a207b23e404e8abfa3815f
|
4
|
+
data.tar.gz: 3f2794b9dd3db6a5596c90d9c556fd3ef2493244daca68f1328490a95dc205d8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c8160c6d4d18841aa27ca9886923721897144f2543c133374dc0df81ebea56e79ea93cb1b2409138cd89a46772b4fe8232633ff72e898f286f115961652f5886
|
7
|
+
data.tar.gz: c77ced5468712332efde9928a22c7ba56070ab4c16598cc6662211f0e95273df39dc019860a234161513bec8d17d54e80997a4b52f5717f374321ce1aa8122ed
|
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 |= File::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