fakefs 2.5.0 → 2.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4a0f94695c0070ec3847b3177acebdf1160f2064ba02d950d04a31385c43207
4
- data.tar.gz: bb0e627c6a32b31b8a1068cae3a5d4542b150d2ed06fecc0719a029889a0815c
3
+ metadata.gz: ae15dc7d6aeb86af398a96ad824d7e53fa9ea82d73a207b23e404e8abfa3815f
4
+ data.tar.gz: 3f2794b9dd3db6a5596c90d9c556fd3ef2493244daca68f1328490a95dc205d8
5
5
  SHA512:
6
- metadata.gz: 646053f9768a3a408c3d365ee2230f089131477decf9ccdb4e51bcd6c9080e1fb56bada71f2f661a685fa41866de408cab4b9de7c07cf50df3fe21b2dde09a85
7
- data.tar.gz: 87d29cda1d981939b941023104f5b7a1ee6f58ea9ca983ca16e379a4471c55ce1aea045a2a6c560b07bd760dc238d14f56b68dd7881a651991a587ca0294e0f8
6
+ metadata.gz: c8160c6d4d18841aa27ca9886923721897144f2543c133374dc0df81ebea56e79ea93cb1b2409138cd89a46772b4fe8232633ff72e898f286f115961652f5886
7
+ data.tar.gz: c77ced5468712332efde9928a22c7ba56070ab4c16598cc6662211f0e95273df39dc019860a234161513bec8d17d54e80997a4b52f5717f374321ce1aa8122ed
data/README.md CHANGED
@@ -128,7 +128,6 @@ describe "my spec" do
128
128
  end
129
129
  ```
130
130
 
131
-
132
131
  FakeFs --- `TypeError: superclass mismatch for class File`
133
132
  --------------
134
133
 
@@ -168,7 +167,7 @@ yourself on the equivalent FakeFS classes. For example,
168
167
  [FileMagic](https://rubygems.org/gems/ruby-filemagic) adds `File#content_type`.
169
168
  A fake version can be provided as follows:
170
169
 
171
- ``` ruby
170
+ ```ruby
172
171
  FakeFS::File.class_eval do
173
172
  def content_type
174
173
  'fake/file'
@@ -176,6 +175,15 @@ FakeFS::File.class_eval do
176
175
  end
177
176
  ```
178
177
 
178
+ File.lock
179
+ ---------
180
+
181
+ Warning: experimental and might break if you obtain more that one flock per file using different descriptors
182
+
183
+ ```ruby
184
+ require 'fakefs/flockable_file'
185
+ ```
186
+
179
187
  Caveats
180
188
  -------
181
189
 
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 == '.'
@@ -56,7 +56,11 @@ module FakeFS
56
56
  assert_dir d
57
57
  object.name = parts.last
58
58
  object.parent = d
59
- d[parts.last] ||= object
59
+ if object.is_a? FakeDir
60
+ d[parts.last] ||= object
61
+ else
62
+ d[parts.last] = object
63
+ end
60
64
  end
61
65
 
62
66
  # copies directories and files from the real filesystem
@@ -0,0 +1,50 @@
1
+ require_relative 'file'
2
+
3
+ module FakeFS
4
+ # Be careful using this, as it may break things if
5
+ # you obtain more that one flock per file using
6
+ # different descriptors.
7
+ # Real flock call blocks/returns false in that case -
8
+ # see https://man7.org/linux/man-pages/man2/flock.2.html,
9
+ # it says it "may be denied", but it does, it fact, deny.
10
+ # This implementation simply returns 0.
11
+ # This may also be a problem if you, it fact, are accessing a
12
+ # real file, which is locked by another process.
13
+ class File < StringIO
14
+ # yep, File::LOCK_UN | File::LOCK_NB is allowed
15
+ FAKE_FS_ALLOWED_FLOCK_MODES = [RealFile::LOCK_EX, RealFile::LOCK_SH, RealFile::LOCK_UN].flat_map do |mode|
16
+ [mode, mode | RealFile::LOCK_NB]
17
+ end.freeze
18
+
19
+ remove_method :flock
20
+
21
+ def flock(mode)
22
+ # all successful calls - even UN - seem to return 0
23
+ unless mode.is_a?(Integer)
24
+ unless mode.respond_to?(:to_int)
25
+ raise TypeError, "no implicit conversion of #{mode.class} into Integer"
26
+ end
27
+ int_mode = mode.to_int
28
+
29
+ unless int_mode.is_a?(Integer)
30
+ raise TypeError, "can't convert #{mode.class} to Integer (#{mode.class}#to_int gives #{int_mode.class})"
31
+ end
32
+ mode = int_mode
33
+ end
34
+
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.
40
+ unless FAKE_FS_ALLOWED_FLOCK_MODES.include?(mode)
41
+ # real exception
42
+ # Invalid argument @ rb_file_flock - filepath (Errno::EINVAL)
43
+ # raise Errno::EINVAL.new(@path, 'rb_file_flock')
44
+ # represents it better, but fails on JRuby
45
+ raise Errno::EINVAL, @path
46
+ end
47
+ 0
48
+ end
49
+ end
50
+ end
@@ -1,7 +1,7 @@
1
1
  module FakeFS
2
2
  # Version module
3
3
  module Version
4
- VERSION = '2.5.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.5.0
4
+ version: 2.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Wanstrath
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2023-05-23 00:00:00.000000000 Z
15
+ date: 2024-12-08 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: bump
@@ -118,6 +118,7 @@ files:
118
118
  - lib/fakefs/file_system.rb
119
119
  - lib/fakefs/file_test.rb
120
120
  - lib/fakefs/fileutils.rb
121
+ - lib/fakefs/flockable_file.rb
121
122
  - lib/fakefs/globber.rb
122
123
  - lib/fakefs/io.rb
123
124
  - lib/fakefs/irb.rb
@@ -146,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
146
147
  - !ruby/object:Gem::Version
147
148
  version: '0'
148
149
  requirements: []
149
- rubygems_version: 3.4.0.dev
150
+ rubygems_version: 3.5.11
150
151
  signing_key:
151
152
  specification_version: 4
152
153
  summary: A fake filesystem. Use it in your tests.