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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 765257209fdbaff32e467e7e5f0acbd9d324d6066fe569d5722a0568368a3288
4
- data.tar.gz: fe4e318738464fd61e4b4d150a06285ea1a1923c92fc51bf244f27afa31b30a2
3
+ metadata.gz: ae15dc7d6aeb86af398a96ad824d7e53fa9ea82d73a207b23e404e8abfa3815f
4
+ data.tar.gz: 3f2794b9dd3db6a5596c90d9c556fd3ef2493244daca68f1328490a95dc205d8
5
5
  SHA512:
6
- metadata.gz: 0b2d56c157243b4a95961f35c946697b229358af469692e91e72ac82eec1c0bdb6b97da19215335f26dd0837b731701bf9d072cb41fbb069f14868f7768d66fc
7
- data.tar.gz: 46b48334206cd89958b8f3177f0e4050580a7c84891f8027e0e768cdfcc8685c22b5cadf03e0bdccb4ae4b9f7bbf788623be644377c53e6ab9277955b8472d9f
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, mode = READ_ONLY, _perm = nil)
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
- @mode = mode.is_a?(Hash) ? (mode[:mode] || READ_ONLY) : mode
496
- @file = FileSystem.find(path)
497
- @autoclose = true
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
- check_modes!
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
- super(@file.content, @mode)
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
- @mode.is_a?(String) && (
626
- @mode.include?('b') ||
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.any?
669
- if open_args[:open_args]
670
- args = [filename, *open_args[:open_args]]
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
- mode = open_args[:mode] || mode
673
- args = [filename, mode, open_args]
777
+ args = open_args
778
+ opt = {}
674
779
  end
675
780
  else
676
- args = [filename, mode]
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 << contents
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 ArguementError "Unknown assignment mode #{assignment_mode}"
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 check_modes!
870
- StringIO.new('', @mode)
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?(FILE_CREATION_MODES) || mode_in_bitmask?(FILE_CREATION_BITMASK)
1059
+ mode_in?(RealFile::CREAT)
879
1060
  end
880
1061
 
881
- def mode_in?(list)
882
- if @mode.respond_to?(:include?)
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
- raise Errno::EISDIR, path.to_s if File.directory?(@path)
897
-
898
- return if File.exist?(@path) # Unnecessary check, probably.
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 Object to Integer (#{mode.class}#to_int gives #{int_mode.class})"
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` - but fails with `f1.flock 11111111` - or may fail
36
- # with another error - `f1.flock 1111111111111` gives `integer 1111111111111 too big to convert to `int'
37
- # but I think it's safer to allow only documented modes.
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
@@ -1,7 +1,7 @@
1
1
  module FakeFS
2
2
  # Version module
3
3
  module Version
4
- VERSION = '2.6.0'.freeze
4
+ VERSION = '2.7.0'.freeze
5
5
 
6
6
  def self.to_s
7
7
  VERSION
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fakefs
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.0
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Wanstrath