memfs-1.0.0/0000755000004100000410000000000013126766224012660 5ustar www-datawww-datamemfs-1.0.0/Rakefile0000644000004100000410000000226413126766224014331 0ustar www-datawww-datarequire 'bundler/gem_tasks' require 'rspec/core/rake_task' require 'memfs' RSpec::Core::RakeTask.new(:spec) task default: :spec desc 'Compares a MemFs class to the original Ruby one ' \ '(set CLASS to the compared class)' task :compare do class_name = ENV['CLASS'] || 'File' klass = Object.const_get(class_name) memfs_klass = MemFs.const_get(class_name) original_methods = (klass.methods - Object.methods).sort original_i_methods = (klass.instance_methods - Object.methods).sort implemented_methods = MemFs.activate { (memfs_klass.methods - Object.methods).sort } implemented_i_methods = MemFs.activate { (memfs_klass.instance_methods - Object.methods).sort } puts "CLASS: #{class_name}" puts puts 'MISSING CLASS METHODS' puts puts original_methods - implemented_methods puts puts 'MISSING INSTANCE METHODS' puts puts original_i_methods - implemented_i_methods puts puts 'ADDITIONAL METHODS' puts puts implemented_methods - original_methods puts puts 'ADDITIONAL INSTANCE METHODS' puts puts implemented_i_methods - original_i_methods end task :console do require 'irb' require 'irb/completion' require 'memfs' ARGV.clear IRB.start end memfs-1.0.0/memfs.png0000644000004100000410000000506113126766224014477 0ustar www-datawww-dataPNG  IHDRbIwPiҀ)-4Ai ('4Air1;'lw+T3r'Zȕ +_",͛R KiP:ܘThJ/94dYj0T\Gr.Dg|=LrsXVJّ$n Ҡ/ܳH@7~Ai!(-Y}JIkn4F J EiO}nJBQ p(a; Qz-]=ldni~Lu^JK3,Ys ȅF\02d)?.0%a"wr̾RVu.YSGjӄJݴ!> L=c_p?͈eߤ?8!6:yKg[wliS57ftÈO7fJ3(K63r`}(_ &,MRJDDNrO0dg'TڕYFuL1EIJȖ"{C51Hy*֜B<D?OSvwL'њʥLg+JXHK:p:w@klz/Ғvt"+}Kj:=U%,&ȘB<Tx)=ϭoSfVEe;ݳi&K`Ws80vPUK usMT;9XF"F7,Ԩb2Q&JuF4T[,J3¥(nvEm&ЊY+lh@B.q}CO}m&ꃥB-ȬR_=e:3*-(Lp5D/P9Miֆ?*NL (-P@PJ%Ai ( JҠ4( JҠ4ҠFU'US J}=ٌ:Ҡ!x_XTaz~{JRB@Ҡ{|4.* ?V'APr\Kd<~/?IENDB`memfs-1.0.0/Gemfile0000644000004100000410000000013213126766224014147 0ustar www-datawww-datasource 'https://rubygems.org' # Specify your gem's dependencies in memfs.gemspec gemspec memfs-1.0.0/.rspec0000644000004100000410000000003613126766224013774 0ustar www-datawww-data--color --require spec_helper memfs-1.0.0/.hound.yml0000644000004100000410000000004513126766224014575 0ustar www-datawww-dataruby: config_file: .ruby-style.yml memfs-1.0.0/LICENSE.txt0000644000004100000410000000205713126766224014507 0ustar www-datawww-dataCopyright (c) 2013 Simon COURTOIS MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. memfs-1.0.0/spec/0000755000004100000410000000000013126766224013612 5ustar www-datawww-datamemfs-1.0.0/spec/memfs_spec.rb0000644000004100000410000000626613126766224016272 0ustar www-datawww-datarequire 'spec_helper' RSpec.describe MemFs do describe '.activate' do it 'calls the given block with MemFs activated' do described_class.activate do expect(::Dir).to be(described_class::Dir) end end it 'resets the original classes once finished' do described_class.activate {} expect(::Dir).to be(described_class::OriginalDir) end it 'deactivates MemFs even when an exception occurs' do begin described_class.activate { fail 'Some error' } rescue RuntimeError end expect(::Dir).to be(described_class::OriginalDir) end end describe '.activate!' do before(:each) { described_class.activate! } after(:each) { described_class.deactivate! } it 'replaces Ruby Dir class with a fake one' do expect(::Dir).to be(described_class::Dir) end it 'replaces Ruby File class with a fake one' do expect(::File).to be(described_class::File) end end describe '.deactivate!' do before :each do described_class.activate! described_class.deactivate! end it 'sets back the Ruby Dir class to the original one' do expect(::Dir).to be(described_class::OriginalDir) end it 'sets back the Ruby File class to the original one' do expect(::File).to be(described_class::OriginalFile) end end describe '.halt' do before(:each) { described_class.activate! } after(:each) { described_class.deactivate! } it 'switches back to the original Ruby Dir & File classes' do described_class.halt do expect(::Dir).to be(described_class::OriginalDir) expect(::File).to be(described_class::OriginalFile) end end it 'switches back to the faked Dir & File classes' do described_class.halt expect(::Dir).to be(described_class::Dir) expect(::File).to be(described_class::File) end it 'switches back to the faked Dir & File classes no matter what' do begin described_class.halt { raise StandardError.new } rescue expect(::Dir).to be(described_class::Dir) expect(::File).to be(described_class::File) end end it 'maintains the state of the faked fs' do _fs.touch('file.rb') described_class.halt do expect(File.exist?('file.rb')).to be false end expect(File.exist?('file.rb')).to be true end end describe '.touch' do around(:each) { |example| described_class.activate { example.run } } it 'creates the specified file' do _fs.mkdir('/path') _fs.mkdir('/path/to') _fs.mkdir('/path/to/some') described_class.touch('/path/to/some/file.rb') expect(File.exist?('/path/to/some/file.rb')).to be true end context 'when the parent folder do not exist' do it 'creates them all' do described_class.touch('/path/to/some/file.rb') expect(File.exist?('/path/to/some/file.rb')).to be true end end context 'when several files are specified' do it 'creates every file' do described_class.touch('/some/path', '/other/path') expect(File.exist?('/some/path')).to be true expect(File.exist?('/other/path')).to be true end end end end memfs-1.0.0/spec/spec_helper.rb0000644000004100000410000000165513126766224016437 0ustar www-datawww-datarequire 'coveralls' require 'memfs' Coveralls.wear! def _fs MemFs::FileSystem.instance end RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end config.filter_run :focus config.run_all_when_everything_filtered = true config.example_status_persistence_file_path = "spec/examples.txt" config.disable_monkey_patching! config.warnings = true if config.files_to_run.one? config.default_formatter = 'doc' end # config.profile_examples = 10 config.order = :random Kernel.srand config.seed config.before { MemFs::FileSystem.instance.clear! } end RSpec.shared_examples 'aliased method' do |method, original_method| it "##{original_method}" do expect(subject.method(method)).to eq(subject.method(original_method)) end end memfs-1.0.0/spec/fileutils_spec.rb0000644000004100000410000007555413126766224017171 0ustar www-datawww-datarequire 'date' require 'fileutils' require 'spec_helper' RSpec.describe FileUtils do before :each do MemFs::File.umask(0020) MemFs.activate! described_class.mkdir '/test' end after :each do MemFs.deactivate! end describe '.cd' do it 'changes the current working directory' do described_class.cd '/test' expect(described_class.pwd).to eq('/test') end it 'returns nil' do expect(described_class.cd('/test')).to be_nil end it "raises an error when the given path doesn't exist" do expect { described_class.cd('/nowhere') }.to raise_error(Errno::ENOENT) end it 'raises an error when the given path is not a directory' do described_class.touch('/test-file') expect { described_class.cd('/test-file') }.to raise_error(Errno::ENOTDIR) end context 'when called with a block' do it 'changes current working directory for the block execution' do described_class.cd '/test' do expect(described_class.pwd).to eq('/test') end end it 'resumes to the old working directory after the block execution finished' do described_class.cd '/' expect { described_class.cd('/test') {} }.to_not change { described_class.pwd } end end context 'when the destination is a symlink' do before :each do described_class.symlink('/test', '/test-link') end it 'changes directory to the last target of the link chain' do described_class.cd('/test-link') expect(described_class.pwd).to eq('/test') end it "raises an error if the last target of the link chain doesn't exist" do expect { described_class.cd('/nowhere-link') }.to raise_error(Errno::ENOENT) end end end describe '.chmod' do it 'changes permission bits on the named file to the bit pattern represented by mode' do described_class.touch '/test-file' described_class.chmod 0777, '/test-file' expect(File.stat('/test-file').mode).to eq(0100777) end it 'changes permission bits on the named files (in list) to the bit pattern represented by mode' do described_class.touch ['/test-file', '/test-file2'] described_class.chmod 0777, ['/test-file', '/test-file2'] expect(File.stat('/test-file2').mode).to eq(0100777) end it 'returns an array containing the file names' do file_names = %w[/test-file /test-file2] described_class.touch file_names expect(described_class.chmod(0777, file_names)).to eq(file_names) end it 'raises an error if an entry does not exist' do expect { described_class.chmod(0777, '/test-file') }.to raise_error(Errno::ENOENT) end context 'when the named file is a symlink' do before :each do described_class.touch '/test-file' described_class.symlink '/test-file', '/test-link' end context 'when File responds to lchmod' do it 'changes the mode on the link' do described_class.chmod(0777, '/test-link') expect(File.lstat('/test-link').mode).to eq(0100777) end it "doesn't change the mode of the link's target" do mode = File.lstat('/test-file').mode described_class.chmod(0777, '/test-link') expect(File.lstat('/test-file').mode).to eq(mode) end end context "when File doesn't respond to lchmod" do it 'does nothing' do allow_any_instance_of(described_class::Entry_).to \ receive_messages(have_lchmod?: false) mode = File.lstat('/test-link').mode described_class.chmod(0777, '/test-link') expect(File.lstat('/test-link').mode).to eq(mode) end end end end describe '.chmod_R' do before :each do described_class.touch '/test/test-file' end it 'changes the permission bits on the named entry' do described_class.chmod_R(0777, '/test') expect(File.stat('/test').mode).to eq(0100777) end it 'changes the permission bits on any sub-directory of the named entry' do described_class.chmod_R(0777, '/') expect(File.stat('/test').mode).to eq(0100777) end it 'changes the permission bits on any descendant file of the named entry' do described_class.chmod_R(0777, '/') expect(File.stat('/test/test-file').mode).to eq(0100777) end end describe '.chown' do it 'changes owner on the named file' do described_class.chown(42, nil, '/test') expect(File.stat('/test').uid).to eq(42) end it 'changes owner on the named files (in list)' do described_class.touch('/test-file') described_class.chown(42, nil, ['/test', '/test-file']) expect(File.stat('/test-file').uid).to eq(42) end it 'changes group on the named entry' do described_class.chown(nil, 42, '/test') expect(File.stat('/test').gid).to eq(42) end it 'changes group on the named entries in list' do described_class.touch('/test-file') described_class.chown(nil, 42, ['/test', '/test-file']) expect(File.stat('/test-file').gid).to eq(42) end it "doesn't change user if user is nil" do described_class.chown(nil, 42, '/test') expect(File.stat('/test').uid).not_to be(42) end it "doesn't change group if group is nil" do described_class.chown(42, nil, '/test') expect(File.stat('/test').gid).not_to be(42) end context 'when the name entry is a symlink' do before :each do described_class.touch '/test-file' described_class.symlink '/test-file', '/test-link' end it 'changes the owner on the last target of the link chain' do described_class.chown(42, nil, '/test-link') expect(File.stat('/test-file').uid).to eq(42) end it 'changes the group on the last target of the link chain' do described_class.chown(nil, 42, '/test-link') expect(File.stat('/test-file').gid).to eq(42) end it "doesn't change the owner of the symlink" do described_class.chown(42, nil, '/test-link') expect(File.lstat('/test-link').uid).not_to be(42) end it "doesn't change the group of the symlink" do described_class.chown(nil, 42, '/test-link') expect(File.lstat('/test-link').gid).not_to be(42) end end end describe '.chown_R' do before :each do described_class.touch '/test/test-file' end it 'changes the owner on the named entry' do described_class.chown_R(42, nil, '/test') expect(File.stat('/test').uid).to eq(42) end it 'changes the group on the named entry' do described_class.chown_R(nil, 42, '/test') expect(File.stat('/test').gid).to eq(42) end it 'changes the owner on any sub-directory of the named entry' do described_class.chown_R(42, nil, '/') expect(File.stat('/test').uid).to eq(42) end it 'changes the group on any sub-directory of the named entry' do described_class.chown_R(nil, 42, '/') expect(File.stat('/test').gid).to eq(42) end it 'changes the owner on any descendant file of the named entry' do described_class.chown_R(42, nil, '/') expect(File.stat('/test/test-file').uid).to eq(42) end it 'changes the group on any descendant file of the named entry' do described_class.chown_R(nil, 42, '/') expect(File.stat('/test/test-file').gid).to eq(42) end end describe '.cmp' do it_behaves_like 'aliased method', :cmp, :compare_file end describe '.compare_file' do it 'returns true if the contents of a file A and a file B are identical' do File.open('/test-file', 'w') { |f| f.puts 'this is a test' } File.open('/test-file2', 'w') { |f| f.puts 'this is a test' } expect(described_class.compare_file('/test-file', '/test-file2')).to be true end it 'returns false if the contents of a file A and a file B are not identical' do File.open('/test-file', 'w') { |f| f.puts 'this is a test' } File.open('/test-file2', 'w') { |f| f.puts 'this is not a test' } expect(described_class.compare_file('/test-file', '/test-file2')).to be false end end describe '.compare_stream' do it 'returns true if the contents of a stream A and stream B are identical' do File.open('/test-file', 'w') { |f| f.puts 'this is a test' } File.open('/test-file2', 'w') { |f| f.puts 'this is a test' } file1 = File.open('/test-file') file2 = File.open('/test-file2') expect(described_class.compare_stream(file1, file2)).to be true end it 'returns false if the contents of a stream A and stream B are not identical' do File.open('/test-file', 'w') { |f| f.puts 'this is a test' } File.open('/test-file2', 'w') { |f| f.puts 'this is not a test' } file1 = File.open('/test-file') file2 = File.open('/test-file2') expect(described_class.compare_stream(file1, file2)).to be false end end describe '.copy' do it_behaves_like 'aliased method', :copy, :cp end describe '.copy_entry' do it 'copies a file system entry +src+ to +dest+' do File.open('/test-file', 'w') { |f| f.puts 'test' } described_class.copy_entry('/test-file', '/test-copy') expect(File.read('/test-copy')).to eq("test\n") end it 'preserves file types' do described_class.touch('/test-file') described_class.symlink('/test-file', '/test-link') described_class.copy_entry('/test-link', '/test-copy') expect(File.symlink?('/test-copy')).to be true end context 'when +src+ does not exist' do it 'raises an exception' do expected_exception = RUBY_VERSION >= '2.4.0' ? Errno::ENOENT : RuntimeError expect { described_class.copy_entry('/test-file', '/test-copy') }.to raise_error(expected_exception) end end context 'when +preserve+ is true' do let(:time) { Date.parse('2013-01-01') } before :each do described_class.touch('/test-file') described_class.chown(1042, 1042, '/test-file') described_class.chmod(0777, '/test-file') _fs.find('/test-file').mtime = time described_class.copy_entry('/test-file', '/test-copy', true) end it 'preserves owner' do expect(File.stat('/test-copy').uid).to eq(1042) end it 'preserves group' do expect(File.stat('/test-copy').gid).to eq(1042) end it 'preserves permissions' do expect(File.stat('/test-copy').mode).to eq(0100777) end it 'preserves modified time' do expect(File.stat('/test-copy').mtime).to eq(time) end end context 'when +dest+ already exists' do it 'overwrite it' do File.open('/test-file', 'w') { |f| f.puts 'test' } described_class.touch('/test-copy') described_class.copy_entry('/test-file', '/test-copy') expect(File.read('/test-copy')).to eq("test\n") end end context 'when +remove_destination+ is true' do it 'removes each destination file before copy' do described_class.touch(['/test-file', '/test-copy']) expect(File).to receive(:unlink).with('/test-copy') described_class.copy_entry('/test-file', '/test-copy', false, false, true) end end context 'when +src+ is a directory' do it 'copies its contents recursively' do described_class.mkdir_p('/test-dir/test-sub-dir') described_class.copy_entry('/test-dir', '/test-copy') expect(Dir.exist?('/test-copy/test-sub-dir')).to be true end end end describe '.copy_file' do it 'copies file contents of src to dest' do File.open('/test-file', 'w') { |f| f.puts 'test' } described_class.copy_file('/test-file', '/test-file2') expect(File.read('/test-file2')).to eq("test\n") end end describe '.copy_stream' do # This method is not implemented since it is delegated to the IO class. end describe '.cp' do before :each do File.open('/test-file', 'w') { |f| f.puts 'test' } end it 'copies a file content +src+ to +dest+' do described_class.cp('/test-file', '/copy-file') expect(File.read('/copy-file')).to eq("test\n") end context 'when +src+ and +dest+ are the same file' do it 'raises an error' do expect { described_class.cp('/test-file', '/test-file') }.to raise_error(ArgumentError) end end context 'when +dest+ is a directory' do it 'copies +src+ to +dest/src+' do described_class.mkdir('/dest') described_class.cp('/test-file', '/dest/copy-file') expect(File.read('/dest/copy-file')).to eq("test\n") end end context 'when src is a list of files' do context 'when +dest+ is not a directory' do it 'raises an error' do described_class.touch(['/dest', '/test-file2']) expect { described_class.cp(['/test-file', '/test-file2'], '/dest') }.to raise_error(Errno::ENOTDIR) end end end end describe '.cp_r' do it 'copies +src+ to +dest+' do File.open('/test-file', 'w') { |f| f.puts 'test' } described_class.cp_r('/test-file', '/copy-file') expect(File.read('/copy-file')).to eq("test\n") end context 'when +src+ is a directory' do it 'copies all its contents recursively' do described_class.mkdir('/test/dir') described_class.touch('/test/dir/file') described_class.cp_r('/test', '/dest') expect(File.exist?('/dest/dir/file')).to be true end end context 'when +dest+ is a directory' do it 'copies +src+ to +dest/src+' do described_class.mkdir(['/test/dir', '/dest']) described_class.touch('/test/dir/file') described_class.cp_r('/test', '/dest') expect(File.exist?('/dest/test/dir/file')).to be true end end context 'when +src+ is a list of files' do it 'copies each of them in +dest+' do described_class.mkdir(['/test/dir', '/test/dir2', '/dest']) described_class.touch(['/test/dir/file', '/test/dir2/file']) described_class.cp_r(['/test/dir', '/test/dir2'], '/dest') expect(File.exist?('/dest/dir2/file')).to be true end end end describe '.getwd' do it_behaves_like 'aliased method', :getwd, :pwd end describe '.identical?' do it_behaves_like 'aliased method', :identical?, :compare_file end describe '.install' do before :each do File.open('/test-file', 'w') { |f| f.puts 'test' } end it 'copies +src+ to +dest+' do described_class.install('/test-file', '/test-file2') expect(File.read('/test-file2')).to eq("test\n") end context 'when +:mode+ is set' do it 'changes the permission mode to +mode+' do expect(File).to receive(:chmod).with(0777, '/test-file2') described_class.install('/test-file', '/test-file2', mode: 0777) end end context 'when +src+ and +dest+ are the same file' do it 'raises an exception' do expect { described_class.install('/test-file', '/test-file') }.to raise_error ArgumentError end end context 'when +dest+ already exists' do it 'removes destination before copy' do expect(File).to receive(:unlink).with('/test-file2') described_class.install('/test-file', '/test-file2') end context 'and +dest+ is a directory' do it 'installs +src+ in dest/src' do described_class.mkdir('/test-dir') described_class.install('/test-file', '/test-dir') expect(File.read('/test-dir/test-file')).to eq("test\n") end end end end describe '.link' do it_behaves_like 'aliased method', :link, :ln end describe '.ln' do before :each do File.open('/test-file', 'w') { |f| f.puts 'test' } end it 'creates a hard link +dest+ which points to +src+' do described_class.ln('/test-file', '/test-file2') expect(File.read('/test-file2')).to eq(File.read('/test-file')) end it 'creates a hard link, not a symlink' do described_class.ln('/test-file', '/test-file2') expect(File.symlink?('/test-file2')).to be false end context 'when +dest+ already exists' do context 'and is a directory' do it 'creates a link dest/src' do described_class.mkdir('/test-dir') described_class.ln('/test-file', '/test-dir') expect(File.read('/test-dir/test-file')).to eq(File.read('/test-file')) end end context 'and it is not a directory' do it 'raises an exception' do described_class.touch('/test-file2') expect { described_class.ln('/test-file', '/test-file2') }.to raise_error(SystemCallError) end context 'and +:force+ is set' do it 'overwrites +dest+' do described_class.touch('/test-file2') described_class.ln('/test-file', '/test-file2', force: true) expect(File.read('/test-file2')).to eq(File.read('/test-file')) end end end end context 'when passing a list of paths' do it 'creates a link for each path in +destdir+' do described_class.touch('/test-file2') described_class.mkdir('/test-dir') described_class.ln(['/test-file', '/test-file2'], '/test-dir') end context 'and +destdir+ is not a directory' do it 'raises an exception' do described_class.touch(['/test-file2', '/not-a-dir']) expect { described_class.ln(['/test-file', '/test-file2'], '/not-a-dir') }.to raise_error(Errno::ENOTDIR) end end end end describe '.ln_s' do before :each do File.open('/test-file', 'w') { |f| f.puts 'test' } described_class.touch('/not-a-dir') described_class.mkdir('/test-dir') end it 'creates a symbolic link +new+' do described_class.ln_s('/test-file', '/test-link') expect(File.symlink?('/test-link')).to be true end it 'creates a symbolic link which points to +old+' do described_class.ln_s('/test-file', '/test-link') expect(File.read('/test-link')).to eq(File.read('/test-file')) end context 'when +new+ already exists' do context 'and it is a directory' do it 'creates a symbolic link +new/old+' do described_class.ln_s('/test-file', '/test-dir') expect(File.symlink?('/test-dir/test-file')).to be true end end context 'and it is not a directory' do it 'raises an exeption' do expect { described_class.ln_s('/test-file', '/not-a-dir') }.to raise_error(Errno::EEXIST) end context 'and +:force+ is set' do it 'overwrites +new+' do described_class.ln_s('/test-file', '/not-a-dir', force: true) expect(File.symlink?('/not-a-dir')).to be true end end end end context 'when passing a list of paths' do before :each do File.open('/test-file2', 'w') { |f| f.puts 'test2' } end it 'creates several symbolic links in +destdir+' do described_class.ln_s(['/test-file', '/test-file2'], '/test-dir') expect(File.exist?('/test-dir/test-file2')).to be true end it 'creates symbolic links pointing to each item in the list' do described_class.ln_s(['/test-file', '/test-file2'], '/test-dir') expect(File.read('/test-dir/test-file2')).to eq(File.read('/test-file2')) end context 'when +destdir+ is not a directory' do it 'raises an error' do expect { described_class.ln_s(['/test-file', '/test-file2'], '/not-a-dir') }.to raise_error(Errno::ENOTDIR) end end end end describe '.ln_sf' do it 'calls ln_s with +:force+ set to true' do File.open('/test-file', 'w') { |f| f.puts 'test' } File.open('/test-file2', 'w') { |f| f.puts 'test2' } described_class.ln_sf('/test-file', '/test-file2') expect(File.read('/test-file2')).to eq(File.read('/test-file')) end end describe '.makedirs' do it_behaves_like 'aliased method', :makedirs, :mkdir_p end describe '.mkdir' do it 'creates one directory' do described_class.mkdir('/test-dir') expect(File.directory?('/test-dir')).to be true end context 'when passing a list of paths' do it 'creates several directories' do described_class.mkdir(['/test-dir', '/test-dir2']) expect(File.directory?('/test-dir2')).to be true end end context 'when passing options' do context 'when passing mode parameter' do it 'creates directory with specified permissions' do described_class.mkdir('/test-dir', mode: 0654) expect(File.exist?('/test-dir')).to be true expect(File.stat('/test-dir').mode).to eq(0100654) end end context 'when passing noop parameter' do it 'does not create any directories' do described_class.mkdir(['/test-dir', '/another-dir'], noop: true) expect(File.directory?('/test-dir')).to be false expect(File.directory?('/another-dir')).to be false end end end end describe '.mkdir_p' do it 'creates a directory' do described_class.mkdir_p('/test-dir') expect(File.directory?('/test-dir')).to be true end it 'creates all the parent directories' do described_class.mkdir_p('/path/to/some/test-dir') expect(File.directory?('/path/to/some')).to be true end context 'when passing a list of paths' do it 'creates each directory' do described_class.mkdir_p(['/test-dir', '/test-dir']) expect(File.directory?('/test-dir')).to be true end it "creates each directory's parents" do described_class.mkdir_p(['/test-dir', '/path/to/some/test-dir']) expect(File.directory?('/path/to/some')).to be true end end context 'when passing options' do context 'when passing mode parameter' do it 'creates directory with specified permissions' do described_class.mkdir_p('/test-dir', mode: 0654) expect(File.exist?('/test-dir')).to be true expect(File.stat('/test-dir').mode).to eq(0100654) end end context 'when passing noop parameter' do it 'does not create any directories' do described_class.mkdir_p(['/test-dir', '/another-dir'], noop: true) expect(File.directory?('/test-dir')).to be false expect(File.directory?('/another-dir')).to be false end end end end describe '.mkpath' do it_behaves_like 'aliased method', :mkpath, :mkdir_p end describe '.move' do it_behaves_like 'aliased method', :move, :mv end describe '.mv' do it 'moves +src+ to +dest+' do described_class.touch('/test-file') described_class.mv('/test-file', '/test-file2') expect(File.exist?('/test-file2')).to be true end it 'removes +src+' do described_class.touch('/test-file') described_class.mv('/test-file', '/test-file2') expect(File.exist?('/test-file')).to be false end context 'when +dest+ already exists' do context 'and is a directory' do it 'moves +src+ to dest/src' do described_class.touch('/test-file') described_class.mkdir('/test-dir') described_class.mv('/test-file', '/test-dir') expect(File.exist?('/test-dir/test-file')).to be true end end context 'and +dest+ is not a directory' do it 'it overwrites +dest+' do File.open('/test-file', 'w') { |f| f.puts 'test' } described_class.touch('/test-file2') described_class.mv('/test-file', '/test-file2') expect(File.read('/test-file2')).to eq("test\n") end end end end describe '.pwd' do it 'returns the name of the current directory' do described_class.cd '/test' expect(described_class.pwd).to eq('/test') end end describe '.remove' do it_behaves_like 'aliased method', :remove, :rm end describe '.remove_dir' do it 'removes the given directory +dir+' do described_class.mkdir('/test-dir') described_class.remove_dir('/test-dir') expect(File.exist?('/test-dir')).to be false end it 'removes the contents of the given directory +dir+' do described_class.mkdir_p('/test-dir/test-sub-dir') described_class.remove_dir('/test-dir') expect(File.exist?('/test-dir/test-sub-dir')).to be false end context 'when +force+ is set' do it 'ignores standard errors' do expect { described_class.remove_dir('/test-dir', true) }.not_to raise_error end end end describe '.remove_entry' do it 'removes a file system entry +path+' do described_class.touch('/test-file') described_class.remove_entry('/test-file') expect(File.exist?('/test-file')).to be false end context 'when +path+ is a directory' do it 'removes it recursively' do described_class.mkdir_p('/test-dir/test-sub-dir') described_class.remove_entry('/test-dir') expect(Dir.exist?('/test-dir')).to be false end end end describe '.remove_entry_secure' do before :each do described_class.mkdir_p('/test-dir/test-sub-dir') end it 'removes a file system entry +path+' do described_class.chmod(0755, '/') described_class.remove_entry_secure('/test-dir') expect(Dir.exist?('/test-dir')).to be false end context 'when +path+ is a directory' do it 'removes it recursively' do described_class.chmod(0755, '/') described_class.remove_entry_secure('/test-dir') expect(Dir.exist?('/test-dir/test-sub-dir')).to be false end context 'and is word writable' do it 'calls chown(2) on it' do described_class.chmod(01777, '/') directory = _fs.find('/test-dir') expect(directory).to receive(:uid=).at_least(:once) described_class.remove_entry_secure('/test-dir') end it 'calls chmod(2) on all sub directories' do described_class.chmod(01777, '/') directory = _fs.find('/test-dir') expect(directory).to receive(:mode=).at_least(:once) described_class.remove_entry_secure('/test-dir') end end end end describe '.remove_file' do it 'removes a file path' do described_class.touch('/test-file') described_class.remove_file('/test-file') expect(File.exist?('/test-file')).to be false end context 'when +force+ is set' do it 'ignores StandardError' do expect { described_class.remove_file('/no-file', true) }.not_to raise_error end end end describe '.rm' do it 'removes the specified file' do described_class.touch('/test-file') described_class.rm('/test-file') expect(File.exist?('/test-file')).to be false end it 'removes files specified in list' do described_class.touch(['/test-file', '/test-file2']) described_class.rm(['/test-file', '/test-file2']) expect(File.exist?('/test-file2')).to be false end it 'cannot remove a directory' do described_class.mkdir('/test-dir') expect { described_class.rm('/test-dir') }.to raise_error(Errno::EPERM) end context 'when +:force+ is set' do it 'ignores StandardError' do described_class.mkdir('/test-dir') expect { described_class.rm('/test-dir', force: true) }.not_to raise_error end end end describe '.rm_f' do it 'calls rm with +:force+ set to true' do expect(described_class) .to receive(:rm).with('test', a_hash_including(force: true)) described_class.rm_f('test') end end describe '.rm_r' do before :each do described_class.touch(['/test-file', '/test-file2']) end it 'removes a list of files' do described_class.rm_r(['/test-file', '/test-file2']) expect(File.exist?('/test-file2')).to be false end context 'when an item of the list is a directory' do it 'removes all its contents recursively' do described_class.mkdir('/test-dir') described_class.touch('/test-dir/test-file') described_class.rm_r(['/test-file', '/test-file2', '/test-dir']) expect(File.exist?('/test-dir/test-file')).to be false end end context 'when +:force+ is set' do it 'ignores StandardError' do expect { described_class.rm_r(['/no-file'], force: true) }.not_to raise_error end end end describe '.rm_rf' do it 'calls rm with +:force+ set to true' do expect(described_class) .to receive(:rm_r).with('test', a_hash_including(force: true)) described_class.rm_rf('test') end end describe '.rmdir' do it 'Removes a directory' do described_class.mkdir('/test-dir') described_class.rmdir('/test-dir') expect(Dir.exist?('/test-dir')).to be false end it 'Removes a list of directories' do described_class.mkdir('/test-dir') described_class.mkdir('/test-dir2') described_class.rmdir(['/test-dir', '/test-dir2']) expect(Dir.exist?('/test-dir2')).to be false end context 'when a directory is not empty' do before :each do described_class.mkdir('/test-dir') described_class.touch('/test-dir/test-file') end it 'ignores errors' do expect { described_class.rmdir('/test-dir') }.not_to raise_error end it "doesn't remove the directory" do described_class.rmdir('/test-dir') expect(Dir.exist?('/test-dir')).to be true end end end describe '.rmtree' do it_behaves_like 'aliased method', :rmtree, :rm_rf end describe '.safe_unlink' do it_behaves_like 'aliased method', :safe_unlink, :rm_f end describe '.symlink' do it_behaves_like 'aliased method', :symlink, :ln_s end describe '.touch' do it "creates a file if it doesn't exist" do described_class.touch('/test-file') expect(_fs.find('/test-file')).not_to be_nil end it "creates a list of files if they don't exist" do described_class.touch(['/test-file', '/test-file2']) expect(_fs.find('/test-file2')).not_to be_nil end end describe '.uptodate?' do before :each do described_class.touch('/test-file') described_class.touch('/old-file') _fs.find!('/old-file').mtime = Time.now - 3600 end it 'returns true if +newer+ is newer than all +old_list+' do expect(described_class.uptodate?('/test-file', ['/old-file'])).to be true end context 'when +newer+ does not exist' do it 'consideres it as older' do expect(described_class.uptodate?('/no-file', ['/old-file'])).to be false end end context 'when a item of +old_list+ does not exist' do it 'consideres it as older than +newer+' do uptodate = described_class.uptodate?('/test-file', ['/old-file', '/no-file']) expect(uptodate).to be true end end end end memfs-1.0.0/spec/memfs/0000755000004100000410000000000013126766224014721 5ustar www-datawww-datamemfs-1.0.0/spec/memfs/file/0000755000004100000410000000000013126766224015640 5ustar www-datawww-datamemfs-1.0.0/spec/memfs/file/stat_spec.rb0000644000004100000410000005657113126766224020170 0ustar www-datawww-datarequire 'spec_helper' module MemFs ::RSpec.describe File::Stat do let(:file_stat) { described_class.new('/test-file') } let(:dereferenced_file_stat) { described_class.new('/test-file', true) } let(:dir_link_stat) { described_class.new('/test-dir-link') } let(:dereferenced_dir_link_stat) { described_class.new('/test-dir-link', true) } let(:link_stat) { described_class.new('/test-link') } let(:dereferenced_link_stat) { described_class.new('/test-link', true) } let(:dir_stat) { described_class.new('/test-dir') } let(:dereferenced_dir_stat) { described_class.new('/test-dir', true) } let(:entry) { _fs.find!('/test-file') } before :each do _fs.mkdir('/test-dir') _fs.touch('/test-file') _fs.symlink('/test-file', '/test-link') _fs.symlink('/test-dir', '/test-dir-link') _fs.symlink('/no-file', '/test-no-file-link') end describe '.new' do context 'when optional dereference argument is set to true' do context 'when the last target of the link chain does not exist' do it 'raises an exception' do expect { described_class.new('/test-no-file-link', true) }.to raise_error(Errno::ENOENT) end end end end describe '#atime' do let(:time) { Time.now - 500_000 } it 'returns the access time of the entry' do entry = _fs.find!('/test-file') entry.atime = time expect(file_stat.atime).to eq(time) end context 'when the entry is a symlink' do context 'and the optional dereference argument is true' do it 'returns the access time of the last target of the link chain' do entry.atime = time expect(dereferenced_link_stat.atime).to eq(time) end end context 'and the optional dereference argument is false' do it 'returns the access time of the symlink itself' do entry.atime = time expect(link_stat.atime).not_to eq(time) end end end end describe '#blksize' do it 'returns the block size of the file' do expect(file_stat.blksize).to be(4096) end end describe '#blockdev?' do context 'when the file is a block device' do it 'returns true' do _fs.touch('/block-file') file = _fs.find('/block-file') file.block_device = true block_stat = described_class.new('/block-file') expect(block_stat.blockdev?).to be true end end context 'when the file is not a block device' do it 'returns false' do expect(file_stat.blockdev?).to be false end end end describe '#chardev?' do context 'when the file is a character device' do it 'returns true' do _fs.touch('/character-file') file = _fs.find('/character-file') file.character_device = true character_stat = described_class.new('/character-file') expect(character_stat.chardev?).to be true end end context 'when the file is not a character device' do it 'returns false' do expect(file_stat.chardev?).to be false end end end describe '#ctime' do let(:time) { Time.now - 500_000 } it 'returns the access time of the entry' do entry.ctime = time expect(file_stat.ctime).to eq(time) end context 'when the entry is a symlink' do context 'and the optional dereference argument is true' do it 'returns the access time of the last target of the link chain' do entry.ctime = time expect(dereferenced_link_stat.ctime).to eq(time) end end context 'and the optional dereference argument is false' do it 'returns the access time of the symlink itself' do entry.ctime = time expect(link_stat.ctime).not_to eq(time) end end end end describe '#dev' do it 'returns an integer representing the device on which stat resides' do expect(file_stat.dev).to be_a(Integer) end end describe '#directory?' do context 'when dereference is true' do context 'when the entry is a directory' do it 'returns true' do expect(dereferenced_dir_stat.directory?).to be true end end context 'when the entry is not a directory' do it 'returns false' do expect(dereferenced_file_stat.directory?).to be false end end context 'when the entry is a symlink' do context 'and the last target of the link chain is a directory' do it 'returns true' do expect(dereferenced_dir_link_stat.directory?).to be true end end context 'and the last target of the link chain is not a directory' do it 'returns false' do expect(dereferenced_link_stat.directory?).to be false end end end end context 'when dereference is false' do context 'when the entry is a directory' do it 'returns true' do expect(dir_stat.directory?).to be true end end context 'when the entry is not a directory' do it 'returns false' do expect(file_stat.directory?).to be false end end context 'when the entry is a symlink' do context 'and the last target of the link chain is a directory' do it 'returns false' do expect(dir_link_stat.directory?).to be false end end context 'and the last target of the link chain is not a directory' do it 'returns false' do expect(link_stat.directory?).to be false end end end end end describe '#entry' do it 'returns the comcerned entry' do expect(file_stat.entry).to be_a(Fake::File) end end describe '#executable?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before :each do entry.mode = access entry.uid = uid entry.gid = gid end context 'when the file is not executable by anyone' do it 'return false' do expect(file_stat.executable?).to be false end end context 'when the file is user executable' do let(:access) { MemFs::Fake::Entry::UEXEC } context 'and the current user owns the file' do let(:uid) { Process.euid } it 'returns true' do expect(file_stat.executable?).to be true end end end context 'when the file is group executable' do let(:access) { MemFs::Fake::Entry::GEXEC } context 'and the current user is part of the owner group' do let(:gid) { Process.egid } it 'returns true' do expect(file_stat.executable?).to be true end end end context 'when the file is executable by anyone' do let(:access) { MemFs::Fake::Entry::OEXEC } context 'and the user has no specific right on it' do it 'returns true' do expect(file_stat.executable?).to be true end end end context 'when the file does not exist' do it 'returns false' do expect(file_stat.executable?).to be false end end end describe '#executable_real?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before :each do entry.mode = access entry.uid = uid entry.gid = gid end context 'when the file is not executable by anyone' do it 'return false' do expect(file_stat.executable_real?).to be false end end context 'when the file is user executable' do let(:access) { MemFs::Fake::Entry::UEXEC } context 'and the current user owns the file' do let(:uid) { Process.uid } it 'returns true' do expect(file_stat.executable_real?).to be true end end end context 'when the file is group executable' do let(:access) { MemFs::Fake::Entry::GEXEC } context 'and the current user is part of the owner group' do let(:gid) { Process.gid } it 'returns true' do expect(file_stat.executable_real?).to be true end end end context 'when the file is executable by anyone' do let(:access) { MemFs::Fake::Entry::OEXEC } context 'and the user has no specific right on it' do it 'returns true' do expect(file_stat.executable_real?).to be true end end end context 'when the file does not exist' do it 'returns false' do expect(file_stat.executable_real?).to be false end end end describe '#file?' do context 'when dereference is true' do context 'when the entry is a regular file' do it 'returns true' do expect(dereferenced_file_stat.file?).to be true end end context 'when the entry is not a regular file' do it 'returns false' do expect(dereferenced_dir_stat.file?).to be false end end context 'when the entry is a symlink' do context 'and the last target of the link chain is a regular file' do it 'returns true' do expect(dereferenced_link_stat.file?).to be true end end context 'and the last target of the link chain is not a regular file' do it 'returns false' do expect(dereferenced_dir_link_stat.file?).to be false end end end end context 'when dereference is false' do context 'when the entry is a regular file' do it 'returns true' do expect(file_stat.file?).to be true end end context 'when the entry is not a regular file' do it 'returns false' do expect(dir_stat.file?).to be false end end context 'when the entry is a symlink' do context 'and the last target of the link chain is a regular file' do it 'returns false' do expect(link_stat.file?).to be false end end context 'and the last target of the link chain is not a regular file' do it 'returns false' do expect(dir_link_stat.file?).to be false end end end end end describe '#ftype' do context 'when the entry is a regular file' do it "returns 'file'" do expect(file_stat.ftype).to eq('file') end end context 'when the entry is a directory' do it "returns 'directory'" do expect(dir_stat.ftype).to eq('directory') end end context 'when the entry is a block device' do it "returns 'blockSpecial'" do _fs.touch('/block-file') file = _fs.find('/block-file') file.block_device = true block_stat = described_class.new('/block-file') expect(block_stat.ftype).to eq('blockSpecial') end end context 'when the entry is a character device' do it "returns 'characterSpecial'" do _fs.touch('/character-file') file = _fs.find('/character-file') file.character_device = true character_stat = described_class.new('/character-file') expect(character_stat.ftype).to eq('characterSpecial') end end context 'when the entry is a symlink' do it "returns 'link'" do expect(link_stat.ftype).to eq('link') end end # fifo and socket not handled for now context 'when the entry has no specific type' do it "returns 'unknown'" do root = _fs.find('/') root.add_entry Fake::Entry.new('test-entry') entry_stat = described_class.new('/test-entry') expect(entry_stat.ftype).to eq('unknown') end end end describe '#gid' do it 'returns the group id of the named entry' do _fs.chown(nil, 42, '/test-file') expect(file_stat.gid).to be(42) end end describe '#grpowned?' do context 'when the effective user group owns of the file' do it 'returns true' do _fs.chown(0, Process.egid, '/test-file') expect(file_stat.grpowned?).to be true end end context 'when the effective user group does not own of the file' do it 'returns false' do _fs.chown(0, 0, '/test-file') expect(file_stat.grpowned?).to be false end end end describe '#ino' do it 'returns the inode number for stat.' do expect(file_stat.ino).to be_a(Integer) end end describe '#mode' do it 'returns an integer representing the permission bits of stat' do _fs.chmod(0777, '/test-file') expect(file_stat.mode).to be(0100777) end end describe '#owned?' do context 'when the effective user owns of the file' do it 'returns true' do _fs.chown(Process.euid, 0, '/test-file') expect(file_stat.owned?).to be true end end context 'when the effective user does not own of the file' do it 'returns false' do _fs.chown(0, 0, '/test-file') expect(file_stat.owned?).to be false end end end describe '#pipe?' do # Pipes are not handled for now context 'when the file is not a pipe' do it 'returns false' do expect(file_stat.pipe?).to be false end end end describe '#readable?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before :each do entry.mode = access entry.uid = uid entry.gid = gid end context 'when the file is not readable by anyone' do it 'return false' do expect(file_stat.readable?).to be false end end context 'when the file is user readable' do let(:access) { MemFs::Fake::Entry::UREAD } context 'and the current user owns the file' do let(:uid) { Process.euid } it 'returns true' do expect(file_stat.readable?).to be true end end end context 'when the file is group readable' do let(:access) { MemFs::Fake::Entry::GREAD } context 'and the current user is part of the owner group' do let(:gid) { Process.egid } it 'returns true' do expect(file_stat.readable?).to be true end end end context 'when the file is readable by anyone' do let(:access) { MemFs::Fake::Entry::OREAD } context 'and the user has no specific right on it' do it 'returns true' do expect(file_stat.readable?).to be true end end end end describe '#readable_real?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before :each do entry.mode = access entry.uid = uid entry.gid = gid end context 'when the file is not readable by anyone' do it 'return false' do expect(file_stat.readable_real?).to be false end end context 'when the file is user readable' do let(:access) { MemFs::Fake::Entry::UREAD } context 'and the current user owns the file' do let(:uid) { Process.euid } it 'returns true' do expect(file_stat.readable_real?).to be true end end end context 'when the file is group readable' do let(:access) { MemFs::Fake::Entry::GREAD } context 'and the current user is part of the owner group' do let(:gid) { Process.egid } it 'returns true' do expect(file_stat.readable_real?).to be true end end end context 'when the file is readable by anyone' do let(:access) { MemFs::Fake::Entry::OREAD } context 'and the user has no specific right on it' do it 'returns true' do expect(file_stat.readable_real?).to be true end end end context 'when the file does not exist' do it 'returns false' do expect(file_stat.readable_real?).to be false end end end describe '#setgid?' do context 'when the file has the setgid bit set' do it 'returns true' do _fs.chmod(02000, '/test-file') expect(file_stat.setgid?).to be true end end context 'when the file does not have the setgid bit set' do it 'returns false' do _fs.chmod(0644, '/test-file') expect(file_stat.setgid?).to be false end end end describe '#setuid?' do context 'when the file has the setuid bit set' do it 'returns true' do _fs.chmod(04000, '/test-file') expect(file_stat.setuid?).to be true end end context 'when the file does not have the setuid bit set' do it 'returns false' do _fs.chmod(0644, '/test-file') expect(file_stat.setuid?).to be false end end end describe '#socket?' do # Sockets are not handled for now context 'when the file is not a socket' do it 'returns false' do expect(file_stat.socket?).to be false end end end describe '#sticky?' do it 'returns true if the named file has the sticky bit set' do _fs.chmod(01777, '/test-file') expect(file_stat.sticky?).to be true end it "returns false if the named file hasn't' the sticky bit set" do _fs.chmod(0666, '/test-file') expect(file_stat.sticky?).to be false end end describe '#symlink?' do context 'when dereference is true' do context 'when the entry is a symlink' do it 'returns false' do expect(dereferenced_link_stat.symlink?).to be false end end context 'when the entry is not a symlink' do it 'returns false' do expect(dereferenced_file_stat.symlink?).to be false end end end context 'when dereference is false' do context 'when the entry is a symlink' do it 'returns true' do expect(link_stat.symlink?).to be true end end context 'when the entry is not a symlink' do it 'returns false' do expect(file_stat.symlink?).to be false end end end end describe '#uid' do it 'returns the user id of the named entry' do _fs.chown(42, nil, '/test-file') expect(file_stat.uid).to be(42) end end describe '#world_reable?' do context 'when +file_name+ is readable by others' do it 'returns an integer representing the file permission bits of +file_name+' do _fs.chmod(MemFs::Fake::Entry::OREAD, '/test-file') expect(file_stat.world_readable?).to eq(MemFs::Fake::Entry::OREAD) end end context 'when +file_name+ is not readable by others' do it 'returns nil' do _fs.chmod(MemFs::Fake::Entry::UREAD, '/test-file') expect(file_stat.world_readable?).to be_nil end end end describe '#world_writable?' do context 'when +file_name+ is writable by others' do it 'returns an integer representing the file permission bits of +file_name+' do _fs.chmod(MemFs::Fake::Entry::OWRITE, '/test-file') expect(file_stat.world_writable?).to eq(MemFs::Fake::Entry::OWRITE) end end context 'when +file_name+ is not writable by others' do it 'returns nil' do _fs.chmod(MemFs::Fake::Entry::UWRITE, '/test-file') expect(file_stat.world_writable?).to be_nil end end end describe '#writable?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before :each do entry.mode = access entry.uid = uid entry.gid = gid end context 'when the file is not executable by anyone' do it 'return false' do expect(file_stat.writable?).to be false end end context 'when the file is user executable' do let(:access) { MemFs::Fake::Entry::UWRITE } context 'and the current user owns the file' do let(:uid) { Process.euid } it 'returns true' do expect(file_stat.writable?).to be true end end end context 'when the file is group executable' do let(:access) { MemFs::Fake::Entry::GWRITE } context 'and the current user is part of the owner group' do let(:gid) { Process.egid } it 'returns true' do expect(file_stat.writable?).to be true end end end context 'when the file is executable by anyone' do let(:access) { MemFs::Fake::Entry::OWRITE } context 'and the user has no specific right on it' do it 'returns true' do expect(file_stat.writable?).to be true end end end context 'when the file does not exist' do it 'returns false' do expect(file_stat.writable?).to be false end end end describe '#writable_real?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before :each do entry.mode = access entry.uid = uid entry.gid = gid end context 'when the file is not executable by anyone' do it 'return false' do expect(file_stat.writable_real?).to be false end end context 'when the file is user executable' do let(:access) { MemFs::Fake::Entry::UWRITE } context 'and the current user owns the file' do let(:uid) { Process.euid } it 'returns true' do expect(file_stat.writable_real?).to be true end end end context 'when the file is group executable' do let(:access) { MemFs::Fake::Entry::GWRITE } context 'and the current user is part of the owner group' do let(:gid) { Process.egid } it 'returns true' do expect(file_stat.writable_real?).to be true end end end context 'when the file is executable by anyone' do let(:access) { MemFs::Fake::Entry::OWRITE } context 'and the user has no specific right on it' do it 'returns true' do expect(file_stat.writable_real?).to be true end end end context 'when the file does not exist' do it 'returns false' do expect(file_stat.writable_real?).to be false end end end describe '#zero?' do context 'when the file has a zero size' do it 'returns true' do expect(file_stat.zero?).to be true end end context 'when the file does not have a zero size' do before :each do _fs.find!('/test-file').content << 'test' end it 'returns false' do expect(file_stat.zero?).to be false end end end end end memfs-1.0.0/spec/memfs/file_system_spec.rb0000644000004100000410000003253413126766224020612 0ustar www-datawww-datarequire 'spec_helper' module MemFs ::RSpec.describe FileSystem do subject { _fs } before :each do subject.mkdir '/test-dir' end describe '#chdir' do it 'changes the current working directory' do subject.chdir '/test-dir' expect(subject.getwd).to eq('/test-dir') end it 'raises an error if directory does not exist' do expect { subject.chdir('/nowhere') }.to raise_error(Errno::ENOENT) end it 'raises an error if the destination is not a directory' do subject.touch('/test-file') expect { subject.chdir('/test-file') }.to raise_error(Errno::ENOTDIR) end context 'when a block is given' do it 'changes current working directory for the block' do location = nil subject.chdir '/test-dir' do location = subject.getwd end expect(location).to eq('/test-dir') end it 'gets back to previous directory once the block is finished' do subject.chdir '/' expect { subject.chdir('/test-dir') {} }.to_not change { subject.getwd } end end context 'when the destination is a symlink' do it 'sets current directory as the last link chain target' do subject.symlink('/test-dir', '/test-link') subject.chdir('/test-link') expect(subject.getwd).to eq('/test-dir') end end end describe '#chmod' do it 'changes permission bits on the named file' do subject.touch('/some-file') subject.chmod(0777, '/some-file') expect(subject.find!('/some-file').mode).to be(0100777) end context 'when the named file is a symlink' do it 'changes the permission bits on the symlink itself' do subject.touch('/some-file') subject.symlink('/some-file', '/some-link') subject.chmod(0777, '/some-link') expect(subject.find!('/some-link').mode).to be(0100777) end end end describe '#chown' do before :each do subject.touch '/test-file' end it 'changes the owner of the named file to the given numeric owner id' do subject.chown(42, nil, '/test-file') expect(subject.find!('/test-file').uid).to be(42) end it 'changes the group of the named file to the given numeric group id' do subject.chown(nil, 42, '/test-file') expect(subject.find!('/test-file').gid).to be(42) end it 'ignores nil user id' do expect { subject.chown(nil, 42, '/test-file') }.to_not change { subject.find!('/test-file').uid } end it 'ignores nil group id' do expect { subject.chown(42, nil, '/test-file') }.to_not change { subject.find!('/test-file').gid } end it 'ignores -1 user id' do expect { subject.chown(-1, 42, '/test-file') }.to_not change { subject.find!('/test-file').uid } end it 'ignores -1 group id' do expect { subject.chown(42, -1, '/test-file') }.to_not change { subject.find!('/test-file').gid } end context 'when the named entry is a symlink' do before :each do subject.symlink '/test-file', '/test-link' end it 'changes the owner on the last target of the link chain' do subject.chown(42, nil, '/test-link') expect(subject.find!('/test-file').uid).to be(42) end it 'changes the group on the last target of the link chain' do subject.chown(nil, 42, '/test-link') expect(subject.find!('/test-file').gid).to be(42) end it "doesn't change the owner of the symlink" do subject.chown(42, nil, '/test-link') expect(subject.find!('/test-link').uid).not_to eq(42) end it "doesn't change the group of the symlink" do subject.chown(nil, 42, '/test-link') expect(subject.find!('/test-link').gid).not_to eq(42) end end end describe '#clear!' do it 'clear the registred entries' do subject.clear! expect(subject.root.entry_names).to eq(%w[. .. tmp]) end it 'sets the current directory to /' do subject.clear! expect(subject.getwd).to eq('/') end end describe '#entries' do it 'returns an array containing all of the filenames in the given directory' do %w[/test-dir/new-dir /test-dir/new-dir2].each { |dir| subject.mkdir dir } subject.touch '/test-dir/test-file', '/test-dir/test-file2' expect(subject.entries('/test-dir')).to eq(%w[. .. new-dir new-dir2 test-file test-file2]) end end describe '#find' do context 'when the entry for the given path exists' do it 'returns the entry' do entry = subject.find('/test-dir') expect(entry).not_to be_nil end end context 'when there is no entry for the given path' do it 'returns nil' do entry = subject.find('/no-file') expect(entry).to be_nil end end context 'when a part of the given path is a symlink' do before :each do subject.symlink('/test-dir', '/test-dir-link') subject.symlink('/no-dir', '/test-no-link') subject.touch('/test-dir/test-file') end context "and the symlink's target exists" do it 'returns the entry' do entry = subject.find('/test-dir-link/test-file') expect(entry).not_to be_nil end end context "and the symlink's target does not exist" do it 'returns nil' do entry = subject.find('/test-no-link/test-file') expect(entry).to be_nil end end end end describe '#find!' do context 'when the entry for the given path exists' do it 'returns the entry' do entry = subject.find!('/test-dir') expect(entry).not_to be_nil end end context 'when there is no entry for the given path' do it 'raises an exception' do expect { subject.find!('/no-file') }.to raise_error Errno::ENOENT end end context 'when a part of the given path is a symlink' do before :each do _fs.symlink('/test-dir', '/test-dir-link') _fs.touch('/test-dir/test-file') end context "and the symlink's target exists" do it 'returns the entry' do entry = subject.find!('/test-dir-link/test-file') expect(entry).not_to be_nil end end context "and the symlink's target does not exist" do it 'raises an exception' do expect { subject.find!('/test-no-link/test-file') }.to raise_error Errno::ENOENT end end end end describe '#find_directory!' do it 'returns the named directory' do expect(subject.find_directory!('/test-dir')).to be_a(Fake::Directory) end it 'raises an error if the named entry is not a directory' do subject.touch '/test-file' expect { subject.find_directory!('/test-file') }.to raise_error(Errno::ENOTDIR) end end describe '#find_parent!' do it 'returns the parent directory of the named entry' do expect(subject.find_parent!('/test-dir/test-file')).to be_a(Fake::Directory) end it 'raises an error if the parent directory does not exist' do expect { subject.find_parent!('/no-dir/test-file') }.to raise_error(Errno::ENOENT) end it 'raises an error if the parent is not a directory' do subject.touch('/test-file') expect { subject.find_parent!('/test-file/test') }.to raise_error(Errno::ENOTDIR) end end describe '#getwd' do it 'returns the current working directory' do subject.chdir '/test-dir' expect(subject.getwd).to eq('/test-dir') end end describe '#link' do before :each do subject.touch('/some-file') end it 'creates a hard link +dest+ that points to +src+' do subject.link('/some-file', '/some-link') expect(subject.find!('/some-link').content).to be(subject.find!('/some-file').content) end it 'does not create a symbolic link' do subject.link('/some-file', '/some-link') expect(subject.find!('/some-link')).not_to be_a(Fake::Symlink) end context 'when +new_name+ already exists' do it 'raises an exception' do subject.touch('/some-link') expect { subject.link('/some-file', '/some-link') }.to raise_error(SystemCallError) end end end describe '#mkdir' do it 'creates a directory' do subject.mkdir '/new-dir' expect(subject.find!('/new-dir')).to be_a(Fake::Directory) end it 'sets directory permissions to default 0777' do subject.mkdir '/new-dir' expect(subject.find!('/new-dir').mode).to eq(0100777) end context 'when permissions are specified' do it 'sets directory permission to specified value' do subject.mkdir '/new-dir', 0644 expect(subject.find!('/new-dir').mode).to eq(0100644) end end context 'when a relative path is given' do it 'creates a directory in current directory' do subject.chdir '/test-dir' subject.mkdir 'new-dir' expect(subject.find!('/test-dir/new-dir')).to be_a(Fake::Directory) end end context 'when the directory already exists' do it 'raises an exception' do expect { subject.mkdir('/') }.to raise_error(Errno::EEXIST) end end end describe '#new' do it 'creates the root directory' do expect(subject.find!('/')).to be(subject.root) end end describe '#paths' do before do subject.mkdir('/test-dir/subdir') subject.touch('/test-dir/subdir/file1', '/test-dir/subdir/file2') end it 'returns the list of all the existing paths' do expect(subject.paths).to eq \ %w[/ /tmp /test-dir /test-dir/subdir /test-dir/subdir/file1 /test-dir/subdir/file2] end end describe '#pwd' do it_behaves_like 'aliased method', :pwd, :getwd end describe '#rename' do it 'renames the given file to the new name' do subject.touch('/test-file') subject.rename('/test-file', '/test-file2') expect(subject.find('/test-file2')).not_to be_nil end it 'removes the old file' do subject.touch('/test-file') subject.rename('/test-file', '/test-file2') expect(subject.find('/test-file')).to be_nil end it 'can move a file in another directory' do subject.touch('/test-file') subject.rename('/test-file', '/test-dir/test-file') expect(subject.find('/test-dir/test-file')).not_to be_nil end end describe '#rmdir' do it 'removes the given directory' do subject.rmdir('/test-dir') expect(subject.find('/test-dir')).to be_nil end context 'when the directory is not empty' do it 'raises an exception' do subject.mkdir('/test-dir/test-sub-dir') expect { subject.rmdir('/test-dir') }.to raise_error(Errno::ENOTEMPTY) end end end describe '#symlink' do it 'creates a symbolic link' do subject.touch('/some-file') subject.symlink('/some-file', '/some-link') expect(subject.find!('/some-link')).to be_a(Fake::Symlink) end context 'when +new_name+ already exists' do it 'raises an exception' do subject.touch('/some-file') subject.touch('/some-file2') expect { subject.symlink('/some-file', '/some-file2') }.to raise_error(Errno::EEXIST) end end end describe '#touch' do it 'creates a regular file' do subject.touch '/some-file' expect(subject.find!('/some-file')).to be_a(Fake::File) end it 'creates a regular file for each named filed' do subject.touch '/some-file', '/some-file2' expect(subject.find!('/some-file2')).to be_a(Fake::File) end it "creates an entry only if it doesn't exist" do subject.touch '/some-file' expect(MemFs::Fake::File).not_to receive(:new) subject.touch '/some-file' end context 'when the named file already exists' do let(:time) { Time.now - 5000 } before :each do subject.touch '/some-file' file = subject.find!('/some-file') file.atime = file.mtime = time end it 'sets the access time of the touched file' do subject.touch '/some-file' expect(subject.find!('/some-file').atime).not_to eq(time) end it 'sets the modification time of the touched file' do subject.touch '/some-file' expect(subject.find!('/some-file').atime).not_to eq(time) end end end describe '#unlink' do it 'deletes the named file' do subject.touch('/some-file') subject.unlink('/some-file') expect(subject.find('/some-file')).to be_nil end context 'when the entry is a directory' do it 'raises an exception' do expect { subject.unlink('/test-dir') }.to raise_error Errno::EPERM end end end end end memfs-1.0.0/spec/memfs/fake/0000755000004100000410000000000013126766224015627 5ustar www-datawww-datamemfs-1.0.0/spec/memfs/fake/file/0000755000004100000410000000000013126766224016546 5ustar www-datawww-datamemfs-1.0.0/spec/memfs/fake/file/content_spec.rb0000644000004100000410000001062513126766224021563 0ustar www-datawww-data# encoding: utf-8 require 'spec_helper' module MemFs module Fake ::RSpec.describe File::Content do describe '#<<' do it 'writes the given string to the contained string' do subject << 'test' expect(subject.to_s).to eq('test') end end describe '#initialize' do context 'when no argument is given' do it 'initialize the contained string to an empty one' do expect(subject.to_s).to eq('') end end context 'when an argument is given' do subject { described_class.new(base_value) } context 'when the argument is a string' do let(:base_value) { 'test' } it 'initialize the contained string with the given one' do expect(subject.to_s).to eq('test') end it 'duplicates the original string to prevent modifications on it' do expect(subject.to_s).not_to be(base_value) end end context 'when the argument is not a string' do let(:base_value) { 42 } it 'converts it to a string' do expect(subject.to_s).to eq('42') end end end end describe '#puts' do it 'appends the given string to the contained string' do subject.puts 'test' expect(subject.to_s).to eq("test\n") end it 'appends all given strings to the contained string' do subject.puts 'this', 'is', 'a', 'test' expect(subject.to_s).to eq("this\nis\na\ntest\n") end context 'when a line break is present at the end of the given string' do it "doesn't add any line break" do subject.puts "test\n" expect(subject.to_s).not_to eq("test\n\n") end end end describe '#to_s' do context 'when the content is empty' do it 'returns an empty string' do expect(subject.to_s).to eq('') end end context 'when the content is not empty' do it "returns the content's string" do subject << 'test' expect(subject.to_s).to eq('test') end end end describe '#truncate' do subject { described_class.new('x' * 50) } it 'truncates the content to length characters' do subject.truncate(5) expect(subject.length).to eq(5) end end describe '#write' do it 'writes the given string in content' do subject.write 'test' expect(subject.to_s).to eq('test') end it 'returns the number of bytes written' do expect(subject.write('test')).to eq(4) end context 'when the argument is not a string' do it 'converts it to a string' do subject.write 42 expect(subject.to_s).to eq('42') end end context 'when the argument is a non-ascii string' do it 'returns the correct number of bytes written' do expect(subject.write('é')).to eq(1) end end end context 'when initialized with a string argument' do subject { described_class.new('test') } describe '#read' do it 'reads +length+ bytes from the contained string' do expect(subject.read(2)).to eq('te') end context 'when there is nothing else to read' do it 'returns nil' do subject.read 4 expect(subject.read(1)).to be_nil end end context 'when the optional +buffer+ argument is provided' do it 'inserts the output in the buffer' do string = String.new subject.read(2, string) expect(string).to eq('te') end end end describe '#pos' do context 'when the string has not been read' do it 'returns 0' do expect(subject.pos).to eq(0) end end context 'when the string has been read' do it 'returns the current offset' do subject.read 2 expect(subject.pos).to eq(2) end end end describe '#close' do it 'responds to close' do expect(subject).to respond_to(:close) end end end end end end memfs-1.0.0/spec/memfs/fake/symlink_spec.rb0000644000004100000410000000647213126766224020665 0ustar www-datawww-datarequire 'spec_helper' module MemFs module Fake ::RSpec.describe Symlink do describe '#content' do it "returns the target's content" do MemFs::File.open('/test-file', 'w') { |f| f.puts 'test' } s = described_class.new('/test-link', '/test-file') expect(s.content).to be(s.dereferenced.content) end end describe '#dereferenced' do it "returns the target if it's not a symlink" do _fs.touch '/test-file' target = _fs.find!('/test-file') s = described_class.new('/test-link', '/test-file') expect(s.dereferenced).to eq(target) end it 'returns the last target of the chain' do _fs.touch '/test-file' target = _fs.find!('/test-file') _fs.symlink '/test-file', '/test-link' s = described_class.new('/test-link2', '/test-link') expect(s.dereferenced).to eq(target) end end describe '#dereferenced_name' do context "when the symlink's target exists" do it 'returns its target name' do _fs.touch('/test-file') symlink = described_class.new('/test-link', '/test-file') expect(symlink.dereferenced_name).to eq('test-file') end end context "when the symlink's target does not exist" do it 'returns its target name' do symlink = described_class.new('/test-link', '/no-file') expect(symlink.dereferenced_name).to eq('no-file') end end end describe '#dereferenced_path' do context "when the symlink's target exists" do it 'returns its target path' do _fs.touch('/test-file') symlink = described_class.new('/test-link', '/test-file') expect(symlink.dereferenced_path).to eq('/test-file') end end context "when the symlink's target does not exist" do it 'raises an exception' do symlink = described_class.new('/test-link', '/no-file') expect { symlink.dereferenced_path }.to raise_error Errno::ENOENT end end end describe '#find' do let(:file) { _fs.find!('/test-dir/test-file') } before :each do _fs.mkdir '/test-dir' _fs.touch '/test-dir/test-file' end context "when the symlink's target exists" do subject { described_class.new('/test-dir-link', '/test-dir') } it 'forwards the search to it' do entry = subject.find('test-file') expect(entry).to eq(file) end end context "when the symlink's target does not exist" do subject { described_class.new('/test-no-link', '/no-dir') } it 'returns nil' do entry = subject.find('test-file') expect(entry).to be_nil end end end describe '#target' do it 'returns the target of the symlink' do s = described_class.new('/test-link', '/test-file') expect(s.target).to eq('/test-file') end end describe '#type' do it "returns 'link'" do s = described_class.new('/test-link', '/test-file') expect(s.type).to eq('link') end end end end end memfs-1.0.0/spec/memfs/fake/directory_spec.rb0000644000004100000410000000751213126766224021177 0ustar www-datawww-datarequire 'spec_helper' module MemFs module Fake ::RSpec.describe Directory do subject(:directory) { described_class.new('test') } describe '.new' do it 'sets . in the entries list' do expect(directory.entries).to include('.' => directory) end it 'sets .. in the entries list' do expect(directory.entries).to have_key('..') end end describe '#add_entry' do let(:entry) { described_class.new('new_entry') } it 'adds the entry to the entries list' do directory.add_entry entry expect(directory.entries).to include('new_entry' => entry) end it 'sets the parent of the added entry' do directory.add_entry entry expect(entry.parent).to be(directory) end end describe 'empty?' do it 'returns true if the directory is empty' do expect(directory).to be_empty end it 'returns false if the directory is not empty' do directory.add_entry described_class.new('test') expect(directory).not_to be_empty end end describe '#entry_names' do it 'returns the list of the names of the entries in the directory' do 3.times do |n| directory.add_entry described_class.new("dir#{n}") end expect(directory.entry_names).to eq(%w[. .. dir0 dir1 dir2]) end end describe '#find' do let(:sub_directory) { described_class.new('sub_dir') } let(:file) { File.new('file') } before :each do sub_directory.add_entry file directory.add_entry sub_directory end it 'returns the named entry if it is one of the entries' do expect(directory.find('sub_dir')).to be(sub_directory) end it 'calls find on the next directory in the search chain' do expect(directory.find('sub_dir/file')).to be(file) end it 'should remove any leading / in the path' do expect(directory.find('/sub_dir/file')).to be(file) end it 'should remove any trailing / in the path' do expect(directory.find('sub_dir/file/')).to be(file) end end describe '#parent=' do let(:parent) { described_class.new('parent') } it 'sets the .. entry in entries list' do directory.parent = parent expect(directory.entries).to include('..' => parent) end it 'sets the parent directory' do directory.parent = parent expect(directory.parent).to be(parent) end end describe '#path' do let(:root) { described_class.new('/') } it 'returns the directory path' do directory.parent = root expect(directory.path).to eq('/test') end context 'when the directory is /' do it 'returns /' do expect(root.path).to eq('/') end end end describe '#paths' do before do subdir = described_class.new('subdir') directory.add_entry(subdir) subdir.add_entry File.new('file1') subdir.add_entry File.new('file2') end it 'returns the path of the directory and its entries recursively' do expect(directory.paths).to eq \ %w[test test/subdir test/subdir/file1 test/subdir/file2] end end describe '#remove_entry' do let(:file) { File.new('file') } it 'removes an entry from the entries list' do directory.add_entry file directory.remove_entry file expect(directory.entries).not_to have_value(file) end end describe '#type' do it "returns 'directory'" do expect(directory.type).to eq('directory') end end end end end memfs-1.0.0/spec/memfs/fake/entry_spec.rb0000644000004100000410000001063013126766224020327 0ustar www-datawww-datarequire 'spec_helper' module MemFs module Fake ::RSpec.describe Entry do let(:entry) { described_class.new('test') } let(:parent) { Directory.new('parent') } let(:time) { Time.now - 5000 } before(:each) do parent.parent = Directory.new('/') entry.parent = parent end shared_examples 'it has accessors for' do |attribute| let(:expected_value) { defined?(expected) ? expected : value } it attribute do entry.send(:"#{attribute}=", value) expect(entry.public_send(attribute)).to eq(expected_value) end end it_behaves_like 'it has accessors for', :name do let(:value) { 'test' } end it_behaves_like 'it has accessors for', :atime do let(:value) { time } end it_behaves_like 'it has accessors for', :block_device do let(:value) { true } end it_behaves_like 'it has accessors for', :character_device do let(:value) { true } end it_behaves_like 'it has accessors for', :ctime do let(:value) { time } end it_behaves_like 'it has accessors for', :mtime do let(:value) { time } end it_behaves_like 'it has accessors for', :uid do let(:value) { 42 } end it_behaves_like 'it has accessors for', :gid do let(:value) { 42 } end it_behaves_like 'it has accessors for', :mode do let(:value) { 0777 } let(:expected) { 0100777 } end it_behaves_like 'it has accessors for', :parent do let(:value) { parent } end describe '.new' do it "sets its default uid to the current user's uid" do expect(entry.uid).to eq(Process.euid) end it "sets its default gid to the current user's gid" do expect(entry.gid).to eq(Process.egid) end it 'extract its name from the path passed as argument' do expect(entry.name).to eq('test') end it 'sets an empty string as name if none is given' do expect(described_class.new.name).to be_empty end it 'sets the access time' do expect(described_class.new.atime).to be_a(Time) end it 'sets the modification time' do expect(entry.mtime).to be_a(Time) end it 'sets atime and mtime to the same value' do expect(entry.atime).to eq(entry.mtime) end end describe '#delete' do it 'removes the entry from its parent' do entry.delete expect(parent.entries).not_to have_value(entry) end end describe '#dereferenced' do it 'returns the entry itself' do expect(entry.dereferenced).to be(entry) end end describe '#dereferenced_name' do it 'returns the entry name' do expect(entry.dereferenced_name).to eq('test') end end describe '#dereferenced_path' do it 'returns the entry path' do expect(entry.dereferenced_path).to eq('/parent/test') end end describe '#find' do it 'raises an error' do expect { entry.find('test') }.to raise_error(Errno::ENOTDIR) end end describe '#dev' do it 'returns an integer representing the device on which the entry resides' do expect(entry.dev).to be_a(Integer) end end describe '#ino' do it 'Returns the inode number for the entry' do expect(entry.ino).to be_a(Integer) end end describe '#path' do it 'returns the complete path of the entry' do expect(entry.path).to eq('/parent/test') end end describe 'paths' do it 'returns an array containing the entry path' do expect(entry.paths).to eq ['/parent/test'] end end describe '#touch' do let(:time) { Time.now - 5000 } before :each do entry.atime = time entry.mtime = time end it 'sets the access time to now' do entry.touch expect(entry.atime).not_to eq(time) end it 'sets the modification time to now' do entry.touch expect(entry.mtime).not_to eq(time) end end describe '#type' do it "returns 'unknown" do expect(entry.type).to eq('unknown') end end end end end memfs-1.0.0/spec/memfs/fake/file_spec.rb0000644000004100000410000000266013126766224020111 0ustar www-datawww-datarequire 'spec_helper' module MemFs module Fake ::RSpec.describe File do let(:file) { _fs.find!('/test-file') } before do _fs.touch('/test-file') end it 'stores the modification made on its content' do file.content << 'test' expect(_fs.find!('/test-file').content.to_s).to eq('test') end describe '#close' do it 'sets the file as closed?' do file.close expect(file).to be_closed end end describe '#content' do it 'returns the file content' do expect(file.content).not_to be_nil end context 'when the file is empty' do it 'returns an empty string container' do expect(file.content.to_s).to be_empty end end end describe '#type' do context 'when the file is a regular file' do it "returns 'file'" do expect(file.type).to eq('file') end end context 'when the file is a block device' do it "returns 'blockSpecial'" do file.block_device = true expect(file.type).to eq('blockSpecial') end end context 'when the file is a character device' do it "returns 'characterSpecial'" do file.character_device = true expect(file.type).to eq('characterSpecial') end end end end end end memfs-1.0.0/spec/memfs/file_spec.rb0000644000004100000410000023636313126766224017214 0ustar www-datawww-data# encoding: UTF-8 require 'spec_helper' require 'pathname' module MemFs ::RSpec.describe File do subject { described_class.new('/test-file') } let(:write_subject) { described_class.new('/test-file', 'w') } let(:random_string) { ('a'..'z').to_a.sample(10).join } before do _fs.mkdir '/test-dir' _fs.touch '/test-file', '/test-file2' described_class.symlink '/test-file', '/test-link' described_class.symlink '/no-file', '/no-link' end it 'implements Enumerable' do expect(described_class.ancestors).to include Enumerable end describe 'constants' do it 'expose SEPARATOR' do expect(MemFs::File::SEPARATOR).to eq '/' end it 'expose ALT_SEPARATOR' do expect(MemFs::File::ALT_SEPARATOR).to be_nil end end describe '.absolute_path' do before { MemFs::Dir.chdir('/test-dir') } it 'converts a pathname to an absolute pathname' do path = described_class.absolute_path('./test-file') expect(path).to eq '/test-dir/test-file' end context 'when +dir_string+ is given' do it 'uses it as the starting point' do path = described_class.absolute_path('./test-file', '/no-dir') expect(path).to eq '/no-dir/test-file' end end context "when the given pathname starts with a '~'" do it 'does not expanded' do path = described_class.absolute_path('~/test-file') expect(path).to eq '/test-dir/~/test-file' end end end describe '.atime' do it 'returns the last access time for the named file as a Time object' do expect(described_class.atime('/test-file')).to be_a Time end it 'raises an error if the entry does not exist' do expect { described_class.atime('/no-file') }.to raise_error Errno::ENOENT end context 'when the entry is a symlink' do let(:time) { Time.now - 500_000 } it 'returns the last access time of the last target of the link chain' do _fs.find!('/test-file').atime = time described_class.symlink '/test-link', '/test-link2' expect(described_class.atime('/test-link2')).to eq time end end end describe '.blockdev?' do context 'when the name file exists' do context 'and it is a block device' do it 'returns true' do _fs.touch('/block-file') file = _fs.find('/block-file') file.block_device = true blockdev = described_class.blockdev?('/block-file') expect(blockdev).to be true end end context 'and it is not a block device' do it 'returns false' do blockdev = described_class.blockdev?('/test-file') expect(blockdev).to be false end end end context 'when the name file does not exist' do it 'returns false' do blockdev = described_class.blockdev?('/no-file') expect(blockdev).to be false end end end describe '.basename' do it 'returns the last component of the filename given in +file_name+' do basename = described_class.basename('/path/to/file.txt') expect(basename).to eq 'file.txt' end context 'when +suffix+ is given' do context 'when it is present at the end of +file_name+' do it 'removes the +suffix+ from the filename basename' do basename = described_class.basename('/path/to/file.txt', '.txt') expect(basename).to eq 'file' end end end end describe '.chardev?' do context 'when the name file exists' do context 'and it is a character device' do it 'returns true' do _fs.touch '/character-file' file = _fs.find('/character-file') file.character_device = true chardev = described_class.chardev?('/character-file') expect(chardev).to be true end end context 'and it is not a character device' do it 'returns false' do chardev = described_class.chardev?('/test-file') expect(chardev).to be false end end end context 'when the name file does not exist' do it 'returns false' do chardev = described_class.chardev?('/no-file') expect(chardev).to be false end end end describe '.chmod' do it 'changes permission bits on the named file' do described_class.chmod 0777, '/test-file' mode = described_class.stat('/test-file').mode expect(mode).to be 0100777 end it 'changes permission bits on the named files (in list)' do described_class.chmod 0777, '/test-file', '/test-file2' mode = described_class.stat('/test-file2').mode expect(mode).to be 0100777 end end describe '.chown' do it 'changes the owner of the named file to the given numeric owner id' do described_class.chown 42, nil, '/test-file' uid = described_class.stat('/test-file').uid expect(uid).to be 42 end it 'changes owner on the named files (in list)' do described_class.chown 42, nil, '/test-file', '/test-file2' uid = described_class.stat('/test-file2').uid expect(uid).to be 42 end it 'changes the group of the named file to the given numeric group id' do described_class.chown nil, 42, '/test-file' gid = described_class.stat('/test-file').gid expect(gid).to be 42 end it 'returns the number of files' do returned_value = described_class.chown(42, 42, '/test-file', '/test-file2') expect(returned_value).to be 2 end it 'ignores nil user id' do expect { described_class.chown nil, 42, '/test-file' }.to_not change { described_class.stat('/test-file').uid } end it 'ignores nil group id' do expect { described_class.chown 42, nil, '/test-file' }.to_not change { described_class.stat('/test-file').gid } end it 'ignores -1 user id' do expect { described_class.chown(-1, 42, '/test-file') }.to_not change { described_class.stat('/test-file').uid } end it 'ignores -1 group id' do expect { described_class.chown 42, -1, '/test-file' }.to_not change { described_class.stat('/test-file').gid } end context 'when the named entry is a symlink' do it 'changes the owner on the last target of the link chain' do described_class.chown 42, nil, '/test-link' uid = described_class.stat('/test-file').uid expect(uid).to be 42 end it 'changes the group on the last target of the link chain' do described_class.chown nil, 42, '/test-link' gid = described_class.stat('/test-file').gid expect(gid).to be 42 end it 'does not change the owner of the symlink' do described_class.chown(42, nil, '/test-link') uid = described_class.lstat('/test-link').uid expect(uid).not_to be 42 end it 'does not change the group of the symlink' do described_class.chown nil, 42, '/test-link' gid = described_class.lstat('/test-link').gid expect(gid).not_to be 42 end end end describe '.ctime' do it 'returns the change time for the named file as a Time object' do ctime = described_class.ctime('/test-file') expect(ctime).to be_a Time end it 'raises an error if the entry does not exist' do expect { described_class.ctime '/no-file' }.to raise_error Errno::ENOENT end context 'when the entry is a symlink' do let(:time) { Time.now - 500_000 } it 'returns the last access time of the last target of the link chain' do _fs.find!('/test-file').ctime = time described_class.symlink '/test-link', '/test-link2' ctime = described_class.ctime('/test-link2') expect(ctime).to eq time end end end describe '.delete' do subject { described_class } it_behaves_like 'aliased method', :delete, :unlink end describe '.directory?' do context 'when the named entry is a directory' do it 'returns true' do is_directory = described_class.directory?('/test-dir') expect(is_directory).to be true end end context 'when the named entry is not a directory' do it 'returns false' do is_directory = described_class.directory?('/test-file') expect(is_directory).to be false end end end describe '.dirname' do it 'returns all components of the filename given in +file_name+ except the last one' do dirname = described_class.dirname('/path/to/some/file.txt') expect(dirname).to eq '/path/to/some' end it 'returns / if file_name is /' do dirname = described_class.dirname('/') expect(dirname).to eq '/' end end describe '.executable?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before do described_class.chmod access, '/test-file' described_class.chown uid, gid, '/test-file' end context 'when the file is not executable by anyone' do it 'return false' do executable = described_class.executable?('/test-file') expect(executable).to be false end end context 'when the file is user executable' do let(:access) { MemFs::Fake::Entry::UEXEC } context 'and the current user owns the file' do before { described_class.chown uid, 0, '/test-file' } let(:uid) { Process.euid } it 'returns true' do executable = described_class.executable?('/test-file') expect(executable).to be true end end end context 'when the file is group executable' do let(:access) { MemFs::Fake::Entry::GEXEC } context 'and the current user is part of the owner group' do let(:gid) { Process.egid } it 'returns true' do executable = described_class.executable?('/test-file') expect(executable).to be true end end end context 'when the file is executable by anyone' do let(:access) { MemFs::Fake::Entry::OEXEC } context 'and the user has no specific right on it' do it 'returns true' do executable = described_class.executable?('/test-file') expect(executable).to be true end end end context 'when the file does not exist' do it 'returns false' do executable = described_class.executable?('/no-file') expect(executable).to be false end end end describe '.executable_real?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before do described_class.chmod access, '/test-file' described_class.chown uid, gid, '/test-file' end context 'when the file is not executable by anyone' do it 'return false' do executable_real = described_class.executable_real?('/test-file') expect(executable_real).to be false end end context 'when the file is user executable' do let(:access) { MemFs::Fake::Entry::UEXEC } context 'and the current user owns the file' do let(:uid) { Process.uid } before { described_class.chown uid, 0, '/test-file' } it 'returns true' do executable_real = described_class.executable_real?('/test-file') expect(executable_real).to be true end end end context 'when the file is group executable' do let(:access) { MemFs::Fake::Entry::GEXEC } context 'and the current user is part of the owner group' do let(:gid) { Process.gid } it 'returns true' do executable_real = described_class.executable_real?('/test-file') expect(executable_real).to be true end end end context 'when the file is executable by anyone' do let(:access) { MemFs::Fake::Entry::OEXEC } context 'and the user has no specific right on it' do it 'returns true' do executable_real = described_class.executable_real?('/test-file') expect(executable_real).to be true end end end context 'when the file does not exist' do it 'returns false' do executable_real = described_class.executable_real?('/no-file') expect(executable_real).to be false end end end describe '.exists?' do context 'when the file exists' do it 'returns true' do exists = described_class.exists?('/test-file') expect(exists).to be true end end context 'when the file does not exist' do it 'returns false' do exists = described_class.exists?('/no-file') expect(exists).to be false end end end describe '.exist?' do subject { described_class } it_behaves_like 'aliased method', :exist?, :exists? end describe '.expand_path' do it 'converts a pathname to an absolute pathname' do _fs.chdir '/' expanded_path = described_class.expand_path('test-file') expect(expanded_path).to eq '/test-file' end it 'references path from the current working directory' do _fs.chdir '/test-dir' expanded_path = described_class.expand_path('test-file') expect(expanded_path).to eq '/test-dir/test-file' end context 'when +dir_string+ is provided' do it 'uses +dir_string+ as the stating point' do expanded_path = described_class.expand_path('test-file', '/test') expect(expanded_path).to eq '/test/test-file' end end end describe '.extname' do it 'returns the extension of the given path' do extname = described_class.extname('test-file.txt') expect(extname).to eq '.txt' end context 'when the given path starts with a period' do context 'and the path has no extension' do it 'returns an empty string' do extname = described_class.extname('.test-file') expect(extname).to eq '' end end context 'and the path has an extension' do it 'returns the extension' do extname = described_class.extname('.test-file.txt') expect(extname).to eq '.txt' end end end context 'when the period is the last character in path' do it 'returns an empty string' do extname = described_class.extname('test-subject.') expect(extname).to eq '' end end end describe '.file?' do context 'when the named file exists' do context 'and it is a regular file' do it 'returns true' do is_file = described_class.file?('/test-file') expect(is_file).to be true end end context 'and it is not a regular file' do it 'returns false' do is_file = described_class.file?('/test-dir') expect(is_file).to be false end end end context 'when the named file does not exist' do it 'returns false' do is_file = described_class.file?('/no-file') expect(is_file).to be false end end end describe '.fnmatch' do context 'when the given path matches against the given pattern' do it 'returns true' do matching = described_class.fnmatch('c?t', 'cat') expect(matching).to be true end end context 'when the given path does not match against the given pattern' do it 'returns false' do matching = File.fnmatch('c?t', 'tac') expect(matching).to be false end end end describe '.fnmatch?' do subject { described_class } it_behaves_like 'aliased method', :fnmatch?, :fnmatch end describe '.ftype' do context 'when the named entry is a regular file' do it 'returns "file"' do ftype = described_class.ftype('/test-file') expect(ftype).to eq 'file' end end context 'when the named entry is a directory' do it 'returns "directory"' do ftype = described_class.ftype('/test-dir') expect(ftype).to eq 'directory' end end context 'when the named entry is a block device' do it 'returns "blockSpecial"' do _fs.touch '/block-file' file = _fs.find('/block-file') file.block_device = true ftype = described_class.ftype('/block-file') expect(ftype).to eq 'blockSpecial' end end context 'when the named entry is a character device' do it 'returns "characterSpecial"' do _fs.touch '/character-file' file = _fs.find('/character-file') file.character_device = true ftype = described_class.ftype('/character-file') expect(ftype).to eq 'characterSpecial' end end context 'when the named entry is a symlink' do it 'returns "link"' do ftype = described_class.ftype('/test-link') expect(ftype).to eq 'link' end end # fifo and socket not handled for now context 'when the named entry has no specific type' do it 'returns "unknown"' do root = _fs.find '/' root.add_entry Fake::Entry.new('test-entry') ftype = described_class.ftype('/test-entry') expect(ftype).to eq 'unknown' end end end describe '.grpowned?' do context 'when the named file exists' do context 'and the effective user group owns of the file' do it 'returns true' do described_class.chown 0, Process.egid, '/test-file' grpowned = File.grpowned?('/test-file') expect(grpowned).to be true end end context 'and the effective user group does not own of the file' do it 'returns false' do described_class.chown 0, 0, '/test-file' grpowned = File.grpowned?('/test-file') expect(grpowned).to be false end end end context 'when the named file does not exist' do it 'returns false' do grpowned = File.grpowned?('/no-file') expect(grpowned).to be false end end end describe '.identical?' do before do described_class.open('/test-file', 'w') { |f| f.puts 'test' } described_class.open('/test-file2', 'w') { |f| f.puts 'test' } described_class.symlink '/test-file', '/test-file-link' described_class.symlink '/test-file', '/test-file-link2' described_class.symlink '/test-file2', '/test-file2-link' end context 'when two paths represent the same path' do it 'returns true' do identical = described_class.identical?('/test-file', '/test-file') expect(identical).to be true end end context 'when two paths do not represent the same file' do it 'returns false' do identical = described_class.identical?('/test-file', '/test-file2') expect(identical).to be false end end context 'when one of the paths does not exist' do it 'returns false' do identical = described_class.identical?('/test-file', '/no-file') expect(identical).to be false end end context 'when a path is a symlink' do context 'and the linked file is the same as the other path' do it 'returns true' do identical = described_class.identical?('/test-file', '/test-file-link') expect(identical).to be true end end context 'and the linked file is different from the other path' do it 'returns false' do identical = described_class.identical?('/test-file2', '/test-file-link') expect(identical).to be false end end end context 'when the two paths are symlinks' do context 'and both links point to the same file' do it 'returns true' do identical = described_class.identical?('/test-file-link', '/test-file-link2') expect(identical).to be true end end context 'and both links do not point to the same file' do it 'returns false' do identical = described_class.identical?('/test-file-link', '/test-file2-link') expect(identical).to be false end end end end describe '.join' do it 'returns a new string formed by joining the strings using File::SEPARATOR' do returned_value = described_class.join('a', 'b', 'c') expect(returned_value).to eq 'a/b/c' end end describe '.lchmod' do context 'when the named file is a regular file' do it 'acts like chmod' do described_class.lchmod 0777, '/test-file' mode = described_class.stat('/test-file').mode expect(mode).to be 0100777 end end context 'when the named file is a symlink' do it 'changes permission bits on the symlink' do described_class.lchmod 0777, '/test-link' mode = described_class.lstat('/test-link').mode expect(mode).to be 0100777 end it 'does not change permission bits on the link’s target' do old_mode = described_class.stat('/test-file').mode described_class.lchmod 0777, '/test-link' mode = described_class.stat('/test-file').mode expect(mode).to eq old_mode end end end describe '.link' do before do described_class.open('/test-file', 'w') { |f| f.puts 'test' } end it 'creates a new name for an existing file using a hard link' do described_class.link '/test-file', '/new-file' original_content = described_class.read('/test-file') copy_content = described_class.read('/new-file') expect(copy_content).to eq original_content end it 'returns zero' do returned_value = described_class.link('/test-file', '/new-file') expect(returned_value).to be_zero end context 'when +old_name+ does not exist' do it 'raises an exception' do expect { described_class.link '/no-file', '/nowhere' }.to raise_error Errno::ENOENT end end context 'when +new_name+ already exists' do it 'raises an exception' do described_class.open('/test-file2', 'w') { |f| f.puts 'test2' } expect { described_class.link '/test-file', '/test-file2' }.to raise_error SystemCallError end end end describe '.lstat' do it 'returns a File::Stat object for the named file' do stat = described_class.lstat('/test-file') expect(stat).to be_a File::Stat end context 'when the named file does not exist' do it 'raises an exception' do expect { described_class.lstat '/no-file' }.to raise_error Errno::ENOENT end end context 'when the named file is a symlink' do it 'does not follow the last symbolic link' do is_symlink = described_class.lstat('/test-link').symlink? expect(is_symlink).to be true end end end describe '.new' do it 'resets the file position to the beginning' do described_class.open('/test-file', 'w') { |f| f.write 'hello' } contents = described_class.open('/test-file', 'r', &:read) expect(contents).to eq('hello') contents = described_class.open('/test-file', 'r', &:read) expect(contents).to eq('hello') end context 'when only the filename is provided' do context 'and the file exists' do it 'returns the open file' do file = described_class.new('/test-file') expect(file).to be_a(MemFs::File) end end context 'and the file does not exist' do it 'raises an exception' do expect { described_class.new('missing-file') }.to raise_error(Errno::ENOENT) end end context 'when the file is a symlink and its target does not exist' do it 'raises an exception' do expect { described_class.new('/no-link') }.to raise_error(Errno::ENOENT) end end end context 'when the mode is provided' do context 'and it is an integer' do subject { described_class.new('/test-file', File::RDWR) } it 'sets the mode to the integer value' do expect(subject.send(:opening_mode)).to eq File::RDWR end end context 'and it is a string' do it 'sets the read mode for "r"' do subject = described_class.new('/test-file', 'r') expect(subject.send(:opening_mode)).to eq File::RDONLY end it 'sets the write+create+truncate mode for "w"' do subject = described_class.new('/test-file', 'w') expect(subject.send(:opening_mode)).to \ eq File::CREAT | File::TRUNC | File::WRONLY end it 'sets the read+write mode for "r+"' do subject = described_class.new('/test-file', 'r+') expect(subject.send(:opening_mode)).to eq File::RDWR end it 'sets the read+write+create+truncate mode for "w+"' do subject = described_class.new('/test-file', 'w+') expect(subject.send(:opening_mode)).to \ eq File::CREAT | File::TRUNC | File::RDWR end it 'sets the write+create+append mode for "a"' do subject = described_class.new('/test-file', 'a') expect(subject.send(:opening_mode)).to \ eq File::CREAT | File::APPEND | File::WRONLY end it 'sets the read+write+create+append mode for "a+"' do subject = described_class.new('/test-file', 'a+') expect(subject.send(:opening_mode)).to \ eq File::CREAT | File::APPEND | File::RDWR end it 'handles the :bom option' do subject = described_class.new('/test-file', 'r:bom') expect(subject.send(:opening_mode)).to eq File::RDONLY end it 'handles the |utf-8 option' do subject = described_class.new('/test-file', 'r|utf-8') expect(subject.send(:opening_mode)).to eq File::RDONLY end it 'handles the :bom|utf-8 option' do subject = described_class.new('/test-file', 'r:bom|utf-8') expect(subject.send(:opening_mode)).to eq File::RDONLY end end context 'and it specifies that the file must be created' do context 'and the file already exists' do it 'changes the mtime of the file' do described_class.new '/test-file', 'w' described_class.exist?('/test-file') end end end context 'and it specifies that the file must be truncated' do context 'and the file already exists' do it 'truncates its content' do described_class.open('/test-file', 'w') { |f| f.puts 'hello' } file = described_class.new('/test-file', 'w') file.close expect(described_class.read('/test-file')).to eq '' end end end end context 'when no argument is given' do it 'raises an exception' do expect { described_class.new }.to raise_error ArgumentError end end context 'when too many arguments are given' do it 'raises an exception' do expect { described_class.new(1, 2, 3, 4) }.to raise_error(ArgumentError) end end end describe '.owned?' do context 'when the named file exists' do context 'and the effective user owns of the file' do it 'returns true' do described_class.chown Process.euid, 0, '/test-file' owned = File.owned?('/test-file') expect(owned).to be true end end context 'and the effective user does not own of the file' do it 'returns false' do described_class.chown 0, 0, '/test-file' owned = File.owned?('/test-file') expect(owned).to be false end end end context 'when the named file does not exist' do it 'returns false' do owned = File.owned?('/no-file') expect(owned).to be false end end end describe '.path' do context 'when the path is a string' do it 'returns the string representation of the path' do path = described_class.path('/some/path') expect(path).to eq '/some/path' end end context 'when the path is a Pathname' do it 'returns the string representation of the path' do path = described_class.path(Pathname.new('/some/path')) expect(path).to eq '/some/path' end end end describe '.pipe?' do # Pipes are not handled for now context 'when the named file is not a pipe' do it 'returns false' do is_pipe = File.pipe?('/test-file') expect(is_pipe).to be false end end end describe '.read' do before do described_class.open('/test-file', 'w') { |f| f.puts 'test' } end it 'reads the content of the given file' do read_content = described_class.read('/test-file') expect(read_content).to eq "test\n" end context 'when +lenght+ is provided' do it 'reads only +length+ characters' do read_content = described_class.read('/test-file', 2) expect(read_content).to eq 'te' end context 'when +length+ is bigger than the file size' do it 'reads until the end of the file' do read_content = described_class.read('/test-file', 1000) expect(read_content).to eq "test\n" end end end context 'when +offset+ is provided' do it 'starts reading from the offset' do read_content = described_class.read('/test-file', 2, 1) expect(read_content).to eq 'es' end it 'raises an error if offset is negative' do expect { described_class.read '/test-file', 2, -1 }.to raise_error Errno::EINVAL end end context 'when the last argument is a hash' do it 'passes the contained options to +open+' do expect(described_class).to \ receive(:open) .with('/test-file', File::RDONLY, encoding: 'UTF-8') .and_return(subject) described_class.read '/test-file', encoding: 'UTF-8' end context 'when it contains the +open_args+ key' do it 'takes precedence over the other options' do expect(described_class).to \ receive(:open).with('/test-file', 'r').and_return(subject) described_class.read '/test-file', mode: 'w', open_args: ['r'] end end end end describe '.readable?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before do described_class.chmod access, '/test-file' described_class.chown uid, gid, '/test-file' end context 'when the file is not readable by anyone' do it 'return false' do readable = described_class.readable?('/test-file') expect(readable).to be false end end context 'when the file is user readable' do let(:access) { MemFs::Fake::Entry::UREAD } context 'and the current user owns the file' do let(:uid) { Process.euid } before { described_class.chown uid, 0, '/test-file' } it 'returns true' do readable = described_class.readable?('/test-file') expect(readable).to be true end end end context 'when the file is group readable' do let(:access) { MemFs::Fake::Entry::GREAD } context 'and the current user is part of the owner group' do let(:gid) { Process.egid } it 'returns true' do readable = described_class.readable?('/test-file') expect(readable).to be true end end end context 'when the file is readable by anyone' do let(:access) { MemFs::Fake::Entry::OREAD } context 'and the user has no specific right on it' do it 'returns true' do readable = described_class.readable?('/test-file') expect(readable).to be true end end end context 'when the file does not exist' do it 'returns false' do readable = described_class.readable?('/no-file') expect(readable).to be false end end end describe '.readable_real?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before do described_class.chmod access, '/test-file' described_class.chown uid, gid, '/test-file' end context 'when the file is not readable by anyone' do it 'return false' do readable_real = described_class.readable_real?('/test-file') expect(readable_real).to be false end end context 'when the file is user readable' do let(:access) { MemFs::Fake::Entry::UREAD } context 'and the current user owns the file' do let(:uid) { Process.uid } before { described_class.chown uid, 0, '/test-file' } it 'returns true' do readable_real = described_class.readable_real?('/test-file') expect(readable_real).to be true end end end context 'when the file is group readable' do let(:access) { MemFs::Fake::Entry::GREAD } context 'and the current user is part of the owner group' do let(:gid) { Process.gid } it 'returns true' do readable_real = described_class.readable_real?('/test-file') expect(readable_real).to be true end end end context 'when the file is readable by anyone' do let(:access) { MemFs::Fake::Entry::OREAD } context 'and the user has no specific right on it' do it 'returns true' do readable_real = described_class.readable_real?('/test-file') expect(readable_real).to be true end end end context 'when the file does not exist' do it 'returns false' do readable_real = described_class.readable_real?('/no-file') expect(readable_real).to be false end end end describe '.readlink' do it 'returns the name of the file referenced by the given link' do expect(described_class.readlink('/test-link')).to eq '/test-file' end end describe '.realdirpath' do before do _fs.mkdir '/test-dir/sub-dir' _fs.symlink '/test-dir/sub-dir', '/test-dir/sub-dir-link' _fs.touch '/test-dir/sub-dir/test-file' end context 'when the path does not contain any symlink or useless dots' do it 'returns the path itself' do path = described_class.realdirpath('/test-file') expect(path).to eq '/test-file' end end context 'when the path contains a symlink' do context 'and the symlink is a middle part' do it 'returns the path with the symlink dereferrenced' do path = described_class.realdirpath('/test-dir/sub-dir-link/test-file') expect(path).to eq '/test-dir/sub-dir/test-file' end end context 'and the symlink is the last part' do it 'returns the path with the symlink dereferrenced' do path = described_class.realdirpath('/test-dir/sub-dir-link') expect(path).to eq '/test-dir/sub-dir' end end end context 'when the path contains useless dots' do it 'returns the path with the useless dots interpolated' do path = described_class.realdirpath('/test-dir/../test-dir/./sub-dir/test-file') expect(path).to eq '/test-dir/sub-dir/test-file' end end context 'when the given path is relative' do context 'and +dir_string+ is not provided' do it 'uses the current working directory has base directory' do _fs.chdir '/test-dir' path = described_class.realdirpath('../test-dir/./sub-dir/test-file') expect(path).to eq '/test-dir/sub-dir/test-file' end end context 'and +dir_string+ is provided' do it 'uses the given directory has base directory' do path = described_class.realdirpath('../test-dir/./sub-dir/test-file', '/test-dir') expect(path).to eq '/test-dir/sub-dir/test-file' end end end context 'when the last part of the given path is a symlink' do context 'and its target does not exist' do before do _fs.symlink '/test-dir/sub-dir/test', '/test-dir/sub-dir/test-link' end it 'uses the name of the target in the resulting path' do path = described_class.realdirpath('/test-dir/sub-dir/test-link') expect(path).to eq '/test-dir/sub-dir/test' end end end context 'when the last part of the given path does not exist' do it 'uses its name in the resulting path' do path = described_class.realdirpath('/test-dir/sub-dir/test') expect(path).to eq '/test-dir/sub-dir/test' end end context 'when a middle part of the given path does not exist' do it 'raises an exception' do expect { described_class.realdirpath '/no-dir/test-file' }.to raise_error Errno::ENOENT end end end describe '.realpath' do before do _fs.mkdir '/test-dir/sub-dir' _fs.symlink '/test-dir/sub-dir', '/test-dir/sub-dir-link' _fs.touch '/test-dir/sub-dir/test-file' end context 'when the path does not contain any symlink or useless dots' do it 'returns the path itself' do path = described_class.realpath('/test-file') expect(path).to eq '/test-file' end end context 'when the path contains a symlink' do context 'and the symlink is a middle part' do it 'returns the path with the symlink dereferrenced' do path = described_class.realpath('/test-dir/sub-dir-link/test-file') expect(path).to eq '/test-dir/sub-dir/test-file' end end context 'and the symlink is the last part' do it 'returns the path with the symlink dereferrenced' do path = described_class.realpath('/test-dir/sub-dir-link') expect(path).to eq '/test-dir/sub-dir' end end end context 'when the path contains useless dots' do it 'returns the path with the useless dots interpolated' do path = described_class.realpath('/test-dir/../test-dir/./sub-dir/test-file') expect(path).to eq '/test-dir/sub-dir/test-file' end end context 'when the given path is relative' do context 'and +dir_string+ is not provided' do it 'uses the current working directory has base directory' do _fs.chdir '/test-dir' path = described_class.realpath('../test-dir/./sub-dir/test-file') expect(path).to eq '/test-dir/sub-dir/test-file' end end context 'and +dir_string+ is provided' do it 'uses the given directory has base directory' do path = described_class.realpath('../test-dir/./sub-dir/test-file', '/test-dir') expect(path).to eq '/test-dir/sub-dir/test-file' end end end context 'when a part of the given path does not exist' do it 'raises an exception' do expect { described_class.realpath '/no-dir/test-file' }.to raise_error Errno::ENOENT end end end describe '.rename' do it 'renames the given file to the new name' do described_class.rename '/test-file', '/test-file2' exists = described_class.exists?('/test-file2') expect(exists).to be true end it 'returns zero' do returned_value = described_class.rename('/test-file', '/test-file2') expect(returned_value).to be_zero end end describe '.setgid?' do context 'when the named file exists' do context 'and the named file has the setgid bit set' do it 'returns true' do _fs.chmod 02000, '/test-file' setgid = File.setgid?('/test-file') expect(setgid).to be true end end context 'and the named file does not have the setgid bit set' do it 'returns false' do _fs.chmod 0644, '/test-file' setgid = File.setgid?('/test-file') expect(setgid).not_to be true end end end context 'when the named file does not exist' do it 'returns false' do setgid = File.setgid?('/no-file') expect(setgid).to be false end end end describe '.setuid?' do context 'when the named file exists' do context 'and the named file has the setuid bit set' do it 'returns true' do _fs.chmod 04000, '/test-file' setuid = File.setuid?('/test-file') expect(setuid).to be true end end context 'and the named file does not have the setuid bit set' do it 'returns false' do _fs.chmod 0644, '/test-file' setuid = File.setuid?('/test-file') expect(setuid).not_to be true end end end context 'when the named file does not exist' do it 'returns false' do setuid = File.setuid?('/no-file') expect(setuid).to be false end end end describe '.size' do it 'returns the size of the file' do described_class.open('/test-file', 'w') { |f| f.puts random_string } size = described_class.size('/test-file') expect(size).to eq random_string.size + 1 end end describe '.size?' do context 'when the named file exists' do context 'and it is empty' do it 'returns false' do size = File.size?('/empty-file') expect(size).to be false end end context 'and it is not empty' do it 'returns the size of the file' do File.open('/content-file', 'w') { |f| f.write 'test' } size = File.size?('/content-file') expect(size).to be 4 end end end context 'when the named file does not exist' do it 'returns false' do size = File.size?('/no-file') expect(size).to be false end end end describe '.socket?' do # Sockets are not handled for now context 'when the named file is not a socket' do it 'returns false' do is_socket = File.socket?('/test-file') expect(is_socket).to be false end end end describe '.split' do it 'splits the given string into a directory and a file component' do returned_value = described_class.split('/path/to/some-file') expect(returned_value).to eq ['/path/to', 'some-file'] end end describe '.stat' do it 'returns a File::Stat object for the named file' do stat = described_class.stat('/test-file') expect(stat).to be_a File::Stat end it 'follows the last symbolic link' do stat = described_class.stat('/test-link').symlink? expect(stat).to be false end context 'when the named file does not exist' do it 'raises an exception' do expect { described_class.stat('/no-file') }.to raise_error Errno::ENOENT end end context 'when the named file is a symlink' do context 'and its target does not exist' do it 'raises an exception' do expect { described_class.stat('/no-link') }.to raise_error Errno::ENOENT end end end it 'always returns a new object' do stat1 = described_class.stat('/test-file') stat2 = described_class.stat('/test-file') expect(stat2).not_to be stat1 end end describe '.sticky?' do context 'when the named file exists' do it 'returns true if the named file has the sticky bit set' do _fs.touch '/test-file' _fs.chmod 01777, '/test-file' sticky = File.sticky?('/test-file') expect(sticky).to be true end it 'returns false if the named file hasn’t the sticky bit set' do _fs.touch '/test-file' _fs.chmod 0666, '/test-file' sticky = File.sticky?('/test-file') expect(sticky).to be false end end context 'when the named file does not exist' do it 'returns false' do sticky = File.sticky?('/no-file') expect(sticky).to be false end end end describe '.symlink' do it 'creates a symbolic link named new_name' do is_symlink = described_class.symlink?('/test-link') expect(is_symlink).to be true end it 'creates a symbolic link that points to an entry named old_name' do target = _fs.find!('/test-link').target expect(target).to eq '/test-file' end context 'when the target does not exist' do it 'creates a symbolic link' do is_symlink = described_class.symlink?('/no-link') expect(is_symlink).to be true end end it 'returns 0' do returned_value = described_class.symlink('/test-file', '/new-link') expect(returned_value).to be_zero end end describe '.symlink?' do context 'when the named entry is a symlink' do it 'returns true' do is_symlink = described_class.symlink?('/test-link') expect(is_symlink).to be true end end context 'when the named entry is not a symlink' do it 'returns false' do is_symlink = described_class.symlink?('/test-file') expect(is_symlink).to be false end end context 'when the named entry does not exist' do it 'returns false' do is_symlink = described_class.symlink?('/no-file') expect(is_symlink).to be false end end end describe '.truncate' do before do described_class.open('/test-file', 'w') { |f| f.write 'x' * 50 } end it 'truncates the named file to the given size' do described_class.truncate('/test-file', 5) size = described_class.size('/test-file') expect(size).to be 5 end it 'returns zero' do returned_value = described_class.truncate('/test-file', 5) expect(returned_value).to be_zero end context 'when the named file does not exist' do it 'raises an exception' do expect { described_class.truncate '/no-file', 5 }.to raise_error Errno::ENOENT end end context 'when the given size is negative' do it 'it raises an exception' do expect { described_class.truncate '/test-file', -1 }.to raise_error TypeError end end end describe '.umask' do before { described_class.umask 0022 } it 'returns the current umask value for this process' do expect(described_class.umask).to be 0022 end context 'when the optional argument is given' do it 'sets the umask to that value' do described_class.umask 0777 expect(described_class.umask).to be 0777 end it 'return the previous value' do previous_umask = described_class.umask(0777) expect(previous_umask).to be 0022 end end end describe '.unlink' do it 'deletes the named file' do described_class.unlink('/test-file') exists = described_class.exists?('/test-file') expect(exists).to be false end it 'returns the number of names passed as arguments' do returned_value = described_class.unlink('/test-file', '/test-file2') expect(returned_value).to be 2 end context 'when multiple file names are given' do it 'deletes the named files' do described_class.unlink '/test-file', '/test-file2' exists = described_class.exists?('/test-file2') expect(exists).to be false end end context 'when the entry is a directory' do it 'raises an exception' do expect { described_class.unlink '/test-dir' }.to raise_error Errno::EPERM end end end describe '.utime' do let(:time) { Time.now - 500_000 } it 'sets the access time of each named file to the first argument' do described_class.utime time, time, '/test-file' atime = described_class.atime('/test-file') expect(atime).to eq time end it 'sets the modification time of each named file to the second argument' do described_class.utime time, time, '/test-file' mtime = described_class.mtime('/test-file') expect(mtime).to eq time end it 'returns the number of file names in the argument list' do utime = described_class.utime(time, time, '/test-file', '/test-file2') expect(utime).to be 2 end it 'raises en error if the entry does not exist' do expect { described_class.utime time, time, '/no-file' }.to raise_error Errno::ENOENT end end describe '.world_readable?' do before { described_class.chmod access, '/test-file' } context 'when file_name is readable by others' do let(:access) { MemFs::Fake::Entry::OREAD } it 'returns an integer representing the file permission bits' do world_readable = described_class.world_readable?('/test-file') expect(world_readable).to eq MemFs::Fake::Entry::OREAD end end context 'when file_name is not readable by others' do let(:access) { MemFs::Fake::Entry::UREAD } it 'returns nil' do world_readable = described_class.world_readable?('/test-file') expect(world_readable).to be_nil end end end describe '.world_writable?' do before { described_class.chmod access, '/test-file' } context 'when file_name is writable by others' do let(:access) { MemFs::Fake::Entry::OWRITE } it 'returns an integer representing the file permission bits' do world_writable = described_class.world_writable?('/test-file') expect(world_writable).to eq MemFs::Fake::Entry::OWRITE end end context 'when file_name is not writable by others' do let(:access) { MemFs::Fake::Entry::UWRITE } it 'returns nil' do world_writable = described_class.world_writable?('/test-file') expect(world_writable).to be_nil end end end describe '.writable?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before do described_class.chmod access, '/test-file' described_class.chown uid, gid, '/test-file' end context 'when the file is not writable by anyone' do it 'return false' do writable = described_class.writable?('/test-file') expect(writable).to be false end end context 'when the file is user writable' do let(:access) { MemFs::Fake::Entry::UWRITE } context 'and the current user owns the file' do before { described_class.chown uid, 0, '/test-file' } let(:uid) { Process.euid } it 'returns true' do writable = described_class.writable?('/test-file') expect(writable).to be true end end end context 'when the file is group writable' do let(:access) { MemFs::Fake::Entry::GWRITE } context 'and the current user is part of the owner group' do let(:gid) { Process.egid } it 'returns true' do writable = described_class.writable?('/test-file') expect(writable).to be true end end end context 'when the file is writable by anyone' do let(:access) { MemFs::Fake::Entry::OWRITE } context 'and the user has no specific right on it' do it 'returns true' do writable = described_class.writable?('/test-file') expect(writable).to be true end end end context 'when the file does not exist' do it 'returns false' do writable = described_class.writable?('/no-file') expect(writable).to be false end end end describe '.writable_real?' do let(:access) { 0 } let(:gid) { 0 } let(:uid) { 0 } before do described_class.chmod access, '/test-file' described_class.chown uid, gid, '/test-file' end context 'when the file is not writable by anyone' do it 'return false' do writable_real = described_class.writable_real?('/test-file') expect(writable_real).to be false end end context 'when the file is user writable' do let(:access) { MemFs::Fake::Entry::UWRITE } context 'and the current user owns the file' do let(:uid) { Process.uid } before { described_class.chown uid, 0, '/test-file' } it 'returns true' do writable_real = described_class.writable_real?('/test-file') expect(writable_real).to be true end end end context 'when the file is group writable' do let(:access) { MemFs::Fake::Entry::GWRITE } context 'and the current user is part of the owner group' do let(:gid) { Process.gid } it 'returns true' do writable_real = described_class.writable_real?('/test-file') expect(writable_real).to be true end end end context 'when the file is writable by anyone' do let(:access) { MemFs::Fake::Entry::OWRITE } context 'and the user has no specific right on it' do it 'returns true' do writable_real = described_class.writable_real?('/test-file') expect(writable_real).to be true end end end context 'when the file does not exist' do it 'returns false' do writable_real = described_class.writable_real?('/no-file') expect(writable_real).to be false end end end describe '.write' do it 'writes the string to the given file' do described_class.write('/test-file', 'test') read_content = described_class.read('/test-file') expect(read_content).to eq 'test' end context 'when +offset+ is provided' do it 'writes the string to the given file when offset is 0' do described_class.write('/test-file', 'test', 0) read_content = described_class.read('/test-file') expect(read_content).to eq 'test' end it 'writes the string to the given file when offset is nil' do described_class.write('/test-file', 'test', nil) read_content = described_class.read('/test-file') expect(read_content).to eq 'test' end it 'starts writing from the offset' do skip('Offsets not yet implemented in IO.write') described_class.write('/test-file', 'test') described_class.write('/test-file', 'test', 2) read_content = described_class.read('/test-file') expect(read_content).to eq 'tetest' end it 'raises an error if offset is negative' do expect { described_class.write('/test-file', 'foo', -1) }.to raise_error Errno::EINVAL end it 'raises an error if offset is a boolean' do expect { described_class.write '/test-file', 'foo', false }.to raise_error TypeError end it 'raises an error if offset is a string' do expect { described_class.write '/test-file', 'foo', 'offset' }.to raise_error TypeError end end end describe '.zero?' do context 'when the named file exists' do context 'and has a zero size' do it 'returns true' do zero = described_class.zero?('/test-file') expect(zero).to be true end end context 'and does not have a zero size' do before do File.open('/test-file', 'w') { |f| f.puts 'test' } end it 'returns false' do zero = described_class.zero?('/test-file') expect(zero).to be false end end end context 'when the named file does not exist' do it 'returns false' do zero = described_class.zero?('/no-file') expect(zero).to be false end end end describe '#<<' do it 'writes the given string in the file' do write_subject << 'Hello' content = write_subject.send(:content) expect(content).to eq 'Hello' end it 'can be chained' do write_subject << 'Hello ' << "World\n" content = write_subject.send(:content) expect(content).to eq "Hello World\n" end context 'when the given object is not a string' do it 'converts the object to a string with to_s' do write_subject << 42 content = write_subject.send(:content) expect(content).to eq '42' end end context 'when the file is not opened for writing' do it 'raises an exception' do expect { subject << 'Hello' }.to raise_error IOError end end end describe '#advise' do it 'returns nil' do returned_value = subject.advise(:normal) expect(returned_value).to be_nil end shared_examples 'advise working' do |advise_type| context "when the #{advise_type.inspect} advise type is given" do it 'does not raise an error ' do expect { subject.advise(advise_type) }.not_to raise_error end end end it_behaves_like 'advise working', :normal it_behaves_like 'advise working', :sequential it_behaves_like 'advise working', :random it_behaves_like 'advise working', :willneed it_behaves_like 'advise working', :dontneed it_behaves_like 'advise working', :noreuse context 'when a wrong advise type is given' do it 'raises an exception' do expect { subject.advise(:wrong) }.to raise_error NotImplementedError end end end describe '#atime' do it 'returns a Time object' do expect(subject.atime).to be_a Time end end describe '#autoclose=' do it 'sets the autoclose flag' do subject.autoclose = false expect(subject.autoclose?).to be false end end describe '#autoclose?' do it 'returns true by default' do expect(subject.autoclose?).to be true end context 'when the file will be automatically closed' do before { subject.autoclose = true } it 'returns true' do expect(subject.autoclose?).to be true end end context 'when the file will not be automatically closed' do before { subject.autoclose = false } it 'returns false' do expect(subject.autoclose?).to be false end end end describe '#binmode' do it 'returns the file itself' do returned_value = subject.binmode expect(returned_value).to be subject end it 'sets the binmode flag for the file' do subject.binmode expect(subject.binmode?).to be true end it 'sets the file encoding to ASCII-8BIT' do subject.binmode encoding = subject.external_encoding expect(encoding).to be Encoding::ASCII_8BIT end end describe '#binmode?' do it 'returns false by default' do expect(subject.binmode?).to be false end context 'when the file is in binmode' do before { subject.binmode } it 'returns true' do expect(subject.binmode?).to be true end end end describe '#bytes' do it_behaves_like 'aliased method', :bytes, :each_byte end describe '#chars' do it_behaves_like 'aliased method', :chars, :each_char end describe '#chmod' do it 'changes permission bits on the file' do subject.chmod 0777 mode = subject.stat.mode expect(mode).to be 0100777 end it 'returns zero' do returned_value = subject.chmod(0777) expect(returned_value).to be_zero end end describe '#chown' do it 'changes the owner of the named file to the given numeric owner id' do subject.chown 42, nil uid = subject.stat.uid expect(uid).to be 42 end it 'changes owner on the named files (in list)' do subject.chown 42 uid = subject.stat.uid expect(uid).to be(42) end it 'changes the group of the named file to the given numeric group id' do subject.chown nil, 42 gid = subject.stat.gid expect(gid).to be 42 end it 'returns zero' do returned_value = subject.chown(42, 42) expect(returned_value).to be_zero end it 'ignores nil user id' do expect { subject.chown nil, 42 }.to_not change { subject.stat.uid } end it 'ignores nil group id' do expect { subject.chown 42, nil }.to_not change { subject.stat.gid } end it 'ignores -1 user id' do expect { subject.chown(-1, 42) }.to_not change { subject.stat.uid } end it 'ignores -1 group id' do expect { subject.chown 42, -1 }.to_not change { subject.stat.gid } end context 'when the named entry is a symlink' do let(:symlink) { described_class.new('/test-link') } it 'changes the owner on the last target of the link chain' do symlink.chown 42, nil uid = subject.stat.uid expect(uid).to be 42 end it 'changes the group on the last target of the link chain' do symlink.chown nil, 42 gid = subject.stat.gid expect(gid).to be 42 end it 'does not change the owner of the symlink' do symlink.chown 42, nil uid = symlink.lstat.uid expect(uid).not_to be 42 end it 'does not change the group of the symlink' do symlink.chown nil, 42 gid = symlink.lstat.gid expect(gid).not_to be 42 end end end describe '#close' do it 'closes the file stream' do subject.close expect(subject).to be_closed end end describe '#closed?' do it 'returns true when the file is closed' do subject.close expect(subject.closed?).to be true end it 'returns false when the file is open' do expect(subject.closed?).to be false end end describe '#close_on_exec=' do it 'sets the close-on-exec flag on the file' do subject.close_on_exec = false expect(subject.close_on_exec?).to be false end end describe '#close_on_exec?' do it 'returns true by default' do expect(subject.close_on_exec?).to be true end context 'when the close-on-exec flag is set to false' do before { subject.close_on_exec = false } it 'returns false' do expect(subject.close_on_exec?).to be false end end end describe '#ctime' do it 'returns a Time object' do expect(subject.ctime).to be_a Time end end describe '#each' do let(:lines) do ["Hello this is a file\n", "with some lines\n", "for test purpose\n"] end before do File.open('/test-file', 'w') do |f| lines.each { |line| f.puts line } end end it 'calls the block for every line in the file' do expect { |blk| subject.each(&blk) }.to yield_successive_args(*lines) end it 'returns the file itself' do returned_value = subject.each {} expect(returned_value).to be subject end context 'when a separator is given' do it 'uses this separator to split lines' do expected_lines = [ 'Hello this is a f', "ile\nwith some lines\nf", "or test purpose\n" ] expect { |blk| subject.each('f', &blk) }.to \ yield_successive_args(*expected_lines) end end context 'when the file is not open for reading' do it 'raises an exception' do expect { write_subject.each { |l| puts l } }.to raise_error IOError end context 'when no block is given' do it 'does not raise an exception' do expect { write_subject.each }.not_to raise_error end end end context 'when no block is given' do it 'returns an enumerator' do expect(subject.each.next).to eq "Hello this is a file\n" end end end describe '#each_byte' do before do described_class.open('/test-file', 'w') { |f| f << 'test' } end it 'calls the given block once for each byte of the file' do expect { |blk| subject.each_byte(&blk) }.to yield_successive_args 116, 101, 115, 116 end it 'returns the file itself' do returned_value = subject.each_byte {} expect(returned_value).to be subject end context 'when the file is not open for reading' do it 'raises an exception' do expect { write_subject.each_byte {} }.to raise_error IOError end context 'when no block is given' do it 'does not raise an exception' do expect { write_subject.each_byte }.not_to raise_error end end end context 'when no block is given' do it 'returns an enumerator' do expect(subject.each_byte.next).to eq 116 end end end describe '#each_char' do before do described_class.open('/test-file', 'w') { |f| f << 'test' } end it 'calls the given block once for each byte of the file' do expect { |blk| subject.each_char(&blk) }.to yield_successive_args 't', 'e', 's', 't' end it 'returns the file itself' do returned_value = subject.each_char {} expect(returned_value).to be subject end context 'when the file is not open for reading' do it 'raises an exception' do expect { write_subject.each_char {} }.to raise_error IOError end context 'when no block is given' do it 'does not raise an exception' do expect { write_subject.each_char }.not_to raise_error end end end context 'when no block is given' do it 'returns an enumerator' do expect(subject.each_char.next).to eq 't' end end end describe '#eof' do it_behaves_like 'aliased method', :eof, :eof? end describe '#eof?' do context 'when the file is not empty' do before do File.open('/test-file', 'w') { |f| f.puts 'test' } end context 'and the file is not yet read' do it 'returns false' do expect(subject.eof?).to be false end end context 'and the file is partly read' do before { subject.read(2) } it 'returns false' do expect(subject.eof?).to be false end end context 'and the file is read' do before { subject.read } it 'returns true' do expect(subject.eof?).to be true end end end context 'when the file is not empty' do context 'and the file is not yet read' do it 'returns true' do expect(subject.eof?).to be true end end context 'and the file is read' do before { subject.read } it 'returns true' do expect(subject.eof?).to be true end end end end describe '#external_encoding' do it 'returns the Encoding object representing the file encoding' do expect(subject.external_encoding).to be_an Encoding end context 'when the file is open in write mode' do context 'and no encoding has been specified' do it 'returns nil' do expect(write_subject.external_encoding).to be_nil end end context 'and an encoding has been specified' do subject { File.open('/test-file', 'w', external_encoding: 'UTF-8') } it 'returns the Encoding' do expect(subject.external_encoding).to be_an Encoding end end end end describe '#flock' do it 'returns zero' do returned_value = subject.flock(File::LOCK_EX) expect(returned_value).to be_zero end end describe '#lstat' do it 'returns the File::Stat object of the file' do expect(subject.lstat).to be_a File::Stat end context 'when the given file is a symlink' do subject { described_class.new('/test-link') } it 'does not follow the last symbolic link' do expect(subject.lstat).to be_symlink end end end describe '#mtime' do it 'returns a Time object' do expect(subject.mtime).to be_a Time end end describe '#pos' do before do File.open('/test-file', 'w') { |f| f.puts 'test' } end it 'returns zero when the file was just opened' do expect(subject.pos).to be_zero end it 'returns the reading offset when some of the file has been read' do subject.read 2 expect(subject.pos).to be 2 end end describe '#print' do it 'appends the given object to the file' do write_subject.print 'test ' write_subject.print 'object' content = write_subject.send(:content) expect(content).to eq 'test object' end it 'converts any given object to string with to_s' do write_subject.print 42 content = write_subject.send(:content) expect(content).to eq '42' end it 'returns nil' do return_value = write_subject.print('test') expect(return_value).to be nil end context 'when multiple objects are given' do it 'appends the given objects to the file' do write_subject.print 'this ', 'is a', ' test' content = write_subject.send(:content) expect(content).to eq 'this is a test' end end context 'when the is not opened for writing' do it 'raises an exception' do expect { subject.print 'test' }.to raise_error IOError end end context 'when the output field separator is nil' do around do |example| old_value = $, $, = nil example.run $, = old_value end it 'inserts nothing between the objects' do write_subject.print 'a', 'b', 'c' content = write_subject.send(:content) expect(content).to eq 'abc' end end context 'when the output field separator is not nil' do around do |example| old_value = $, $, = '-' example.run $, = old_value end it 'inserts it between the objects' do write_subject.print 'a', 'b', 'c' content = write_subject.send(:content) expect(content).to eq 'a-b-c' end end context 'when the output record separator is nil' do around do |example| old_value = $\ $\ = nil example.run $\ = old_value end it 'inserts nothing at the end of the output' do write_subject.print 'a', 'b', 'c' content = write_subject.send(:content) expect(content).to eq 'abc' end end context 'when the output record separator is not nil' do around do |example| old_value = $\ $\ = '-' example.run $\ = old_value end it 'inserts it at the end of the output' do write_subject.print 'a', 'b', 'c' content = write_subject.send(:content) expect(content).to eq 'abc-' end end context 'when no argument is given' do it 'prints $_' do skip "I don't know how to test with \$_" $_ = 'test' write_subject.print content = write_subject.send(:content) expect(content).to eq 'test' end end end describe '#printf' do it 'appends the string in the file' do write_subject.print 'test ' write_subject.printf 'Hello' content = write_subject.send(:content) expect(content).to eq 'test Hello' end it 'converts parameters under control of the format string' do write_subject.printf 'Hello %d %05d', 42, 43 content = write_subject.send(:content) expect(content).to eq 'Hello 42 00043' end it 'returns nil' do returned_value = write_subject.printf('Hello') expect(returned_value).to be nil end end describe '#puts' do it 'appends content to the file' do write_subject.puts 'test' write_subject.close content = write_subject.send(:content) expect(content).to eq "test\n" end it 'does not override the file’s content' do write_subject.puts 'test' write_subject.puts 'test' write_subject.close content = write_subject.send(:content) expect(content).to eq "test\ntest\n" end context 'when the file is not writable' do it 'raises an exception' do expect { subject.puts 'test' }.to raise_error IOError end end end describe '#path' do it 'returns the path of the file' do file = described_class.new('/test-file') expect(file.path).to eq '/test-file' end end describe '#read' do before do MemFs::File.open('/test-file', 'w') { |f| f.puts random_string } end context 'when no length is given' do it 'returns the content of the named file' do expect(subject.read).to eq random_string + "\n" end it 'returns an empty string if called a second time' do subject.read expect(subject.read).to be_empty end end context 'when a length is given' do it 'returns a string of the given length' do read = subject.read(2) expect(read).to eq random_string[0, 2] end it 'returns nil when there is nothing more to read' do subject.read 1000 second_read = subject.read(1000) expect(second_read).to be_nil end end context 'when a buffer is given' do it 'fills the buffer with the read content' do buffer = String.new subject.read 2, buffer expect(buffer).to eq random_string[0, 2] end end end describe '#seek' do before do File.open('/test-file', 'w') { |f| f.puts 'test' } end it 'returns zero' do returned_value = subject.seek(1) expect(returned_value).to be_zero end context 'when +whence+ is not provided' do it 'seeks to the absolute location given by +amount+' do subject.seek 3 expect(subject.pos).to be 3 end end context 'when +whence+ is IO::SEEK_CUR' do it 'seeks to +amount+ plus current position' do subject.read 1 subject.seek 1, ::IO::SEEK_CUR expect(subject.pos).to be 2 end end context 'when +whence+ is IO::SEEK_END' do it 'seeks to +amount+ plus end of stream' do subject.seek(-1, ::IO::SEEK_END) expect(subject.pos).to be 4 end end context 'when +whence+ is IO::SEEK_SET' do it 'seeks to the absolute location given by +amount+' do subject.seek 3, ::IO::SEEK_SET expect(subject.pos).to be 3 end end context 'when +whence+ is invalid' do it 'raises an exception' do expect { subject.seek 0, 42 }.to raise_error Errno::EINVAL end end context 'if the position ends up to be less than zero' do it 'raises an exception' do expect { subject.seek(-1) }.to raise_error Errno::EINVAL end end end describe '#size' do it 'returns the size of the file' do described_class.open('/test-file', 'w') { |f| f.puts random_string } size = described_class.new('/test-file').size expect(size).to eq random_string.size + 1 end end describe '#stat' do it 'returns the +Stat+ object of the file' do expect(subject.stat).to be_a(File::Stat) end end describe '#truncate' do it 'truncates the given file to be at most integer bytes long' do described_class.open('/test-file', 'w') do |f| f.puts 'this is a 24-char string' f.truncate 10 f.close end size = described_class.size('/test-file') expect(size).to eq 10 end it 'returns zero' do described_class.open('/test-file', 'w') do |f| returned_value = f.truncate(42) expect(returned_value).to be_zero end end end describe '#write' do it 'writes the given string to file' do write_subject.write 'test' content = File.read('/test-file') expect(content).to eq 'test' end it 'returns the number of bytes written' do returned_value = write_subject.write('test') expect(returned_value).to be 4 end context 'when the file is not opened for writing' do it 'raises an exception' do expect { subject.write 'test' }.to raise_error IOError end end context 'when the argument is not a string' do it 'will be converted to a string using to_s' do write_subject.write 42 content = File.read('/test-file') expect(content).to eq '42' end end end end end memfs-1.0.0/spec/memfs/dir_spec.rb0000644000004100000410000003651413126766224017047 0ustar www-datawww-datarequire 'pathname' module MemFs ::RSpec.describe Dir do subject { described_class.new('/test') } before { described_class.mkdir '/test' } it 'is Enumerable' do expect(subject).to be_an(Enumerable) end describe '[]' do context 'when a string is given' do it 'acts like calling glob' do expect(described_class['/*']).to eq %w[/tmp /test] end end context 'when a list of strings is given' do it 'acts like calling glob' do expect(described_class['/tm*', '/te*']).to eq %w[/tmp /test] end end end describe '.chdir' do it 'changes the current working directory' do described_class.chdir '/test' expect(described_class.getwd).to eq('/test') end it 'returns zero' do expect(described_class.chdir('/test')).to be_zero end it 'raises an error when the folder does not exist' do expect { described_class.chdir('/nowhere') }.to raise_error(Errno::ENOENT) end context 'when a block is given' do it 'changes current working directory for the block' do described_class.chdir '/test' do expect(described_class.pwd).to eq('/test') end end it 'gets back to previous directory once the block is finished' do described_class.chdir '/' expect { described_class.chdir('/test') {} }.to_not change { described_class.pwd } end end end describe '.chroot' do before { allow(Process).to receive_messages(uid: 0) } it "changes the process's idea of the file system root" do described_class.mkdir('/test/subdir') described_class.chroot('/test') expect(File.exist?('/subdir')).to be true end it 'returns zero' do expect(described_class.chroot('/test')).to eq 0 end context 'when the given path is a file' do before { _fs.touch('/test/test-file') } it 'raises an exception' do expect { described_class.chroot('/test/test-file') }.to raise_error(Errno::ENOTDIR) end end context "when the given path doesn't exist" do it 'raises an exception' do expect { described_class.chroot('/no-dir') }.to raise_error(Errno::ENOENT) end end context 'when the user is not root' do before { allow(Process).to receive_messages(uid: 42) } it 'raises an exception' do expect { described_class.chroot('/no-dir') }.to raise_error(Errno::EPERM) end end end describe '.delete' do subject { described_class } it_behaves_like 'aliased method', :delete, :rmdir end describe '.entries' do it 'returns an array containing all of the filenames in the given directory' do %w[/test/dir1 /test/dir2].each { |dir| described_class.mkdir dir } _fs.touch '/test/file1', '/test/file2' expect(described_class.entries('/test')).to eq(%w[. .. dir1 dir2 file1 file2]) end end describe '.exist?' do subject { described_class } it_behaves_like 'aliased method', :exist?, :exists? end describe '.exists?' do it 'returns true if the given +path+ exists and is a directory' do described_class.mkdir('/test-dir') expect(described_class.exists?('/test-dir')).to be true end it 'returns false if the given +path+ does not exist' do expect(described_class.exists?('/test-dir')).to be false end it 'returns false if the given +path+ is not a directory' do _fs.touch('/test-file') expect(described_class.exists?('/test-file')).to be false end end describe '.foreach' do before :each do _fs.touch('/test/test-file', '/test/test-file2') end context 'when a block is given' do it 'calls the block once for each entry in the named directory' do expect { |blk| described_class.foreach('/test', &blk) }.to yield_control.exactly(4).times end it 'passes each entry as a parameter to the block' do expect { |blk| described_class.foreach('/test', &blk) }.to yield_successive_args('.', '..', 'test-file', 'test-file2') end context "and the directory doesn't exist" do it 'raises an exception' do expect { described_class.foreach('/no-dir') {} }.to raise_error Errno::ENOENT end end context 'and the given path is not a directory' do it 'raises an exception' do expect { described_class.foreach('/test/test-file') {} }.to raise_error Errno::ENOTDIR end end end context 'when no block is given' do it 'returns an enumerator' do list = described_class.foreach('/test-dir') expect(list).to be_an(Enumerator) end context "and the directory doesn't exist" do it 'returns an enumerator' do list = described_class.foreach('/no-dir') expect(list).to be_an(Enumerator) end end context 'and the given path is not a directory' do it 'returns an enumerator' do list = described_class.foreach('/test-dir/test-file') expect(list).to be_an(Enumerator) end end end end describe '.getwd' do it 'returns the path to the current working directory' do expect(described_class.getwd).to eq(FileSystem.instance.getwd) end end describe '.glob' do before do _fs.clear! 3.times do |dirnum| _fs.mkdir "/test#{dirnum}" _fs.mkdir "/test#{dirnum}/subdir" 3.times do |filenum| _fs.touch "/test#{dirnum}/subdir/file#{filenum}" end end end shared_examples 'returning matching filenames' do |pattern, filenames| it "with #{pattern}" do expect(described_class.glob(pattern)).to eq filenames end end it_behaves_like 'returning matching filenames', '/', %w[/] it_behaves_like 'returning matching filenames', '/test0', %w[/test0] it_behaves_like 'returning matching filenames', '/*', %w[/tmp /test0 /test1 /test2] it_behaves_like 'returning matching filenames', '/test*', %w[/test0 /test1 /test2] it_behaves_like 'returning matching filenames', '/*0', %w[/test0] it_behaves_like 'returning matching filenames', '/*es*', %w[/test0 /test1 /test2] it_behaves_like 'returning matching filenames', '/**/file0', %w[/test0/subdir/file0 /test1/subdir/file0 /test2/subdir/file0] it_behaves_like 'returning matching filenames', '/test?', %w[/test0 /test1 /test2] it_behaves_like 'returning matching filenames', '/test[01]', %w[/test0 /test1] it_behaves_like 'returning matching filenames', '/test[^2]', %w[/test0 /test1] it_behaves_like 'returning matching filenames', Pathname.new('/'), %w[/] it_behaves_like 'returning matching filenames', Pathname.new('/*'), %w[/tmp /test0 /test1 /test2] if defined?(File::FNM_EXTGLOB) it_behaves_like 'returning matching filenames', '/test{1,2}', %w[/test1 /test2] end context 'when a flag is given' do it 'uses it to compare filenames' do expect(described_class.glob('/TEST*', File::FNM_CASEFOLD)).to eq \ %w[/test0 /test1 /test2] end end context 'when a block is given' do it 'calls the block with every matching filenames' do expect { |blk| described_class.glob('/test*', &blk) }.to \ yield_successive_args('/test0', '/test1', '/test2') end it 'returns nil' do expect(described_class.glob('/*') {}).to be nil end end context 'when pattern is an array of patterns' do it 'returns the list of files matching any pattern' do expect(described_class.glob(['/*0', '/*1'])).to eq %w[/test0 /test1] end end end describe '.home' do it 'returns the home directory of the current user' do expect(described_class.home).to eq ENV['HOME'] end context 'when a username is given' do it 'returns the home directory of the given user' do home_dir = described_class.home(ENV['USER']) expect(home_dir).to eq ENV['HOME'] end end end describe '.mkdir' do it 'creates a directory' do described_class.mkdir '/new-folder' expect(File.directory?('/new-folder')).to be true end it 'sets directory permissions to default 0777' do described_class.mkdir '/new-folder' expect(File.stat('/new-folder').mode).to eq(0100777) end context 'when permissions are specified' do it 'sets directory permissions to specified value' do described_class.mkdir '/new-folder', 0644 expect(File.stat('/new-folder').mode).to eq(0100644) end end context 'when the directory already exist' do it 'raises an exception' do expect { described_class.mkdir('/') }.to raise_error(Errno::EEXIST) end end end describe '.open' do context 'when no block is given' do it 'returns the opened directory' do expect(described_class.open('/test')).to be_a(Dir) end end context 'when a block is given' do it 'calls the block with the opened directory as argument' do expect { |blk| described_class.open('/test', &blk) }.to yield_with_args(Dir) end it 'returns nil' do expect(described_class.open('/test') {}).to be_nil end it 'ensures the directory is closed' do dir = nil described_class.open('/test') { |d| dir = d } expect { dir.close }.to raise_error(IOError) end end context "when the given directory doesn't exist" do it 'raises an exception' do expect { described_class.open('/no-dir') }.to raise_error Errno::ENOENT end end context 'when the given path is not a directory' do before { _fs.touch('/test/test-file') } it 'raises an exception' do expect { described_class.open('/test/test-file') }.to raise_error Errno::ENOTDIR end end end describe '.new' do context "when the given directory doesn't exist" do it 'raises an exception' do expect { described_class.new('/no-dir') }.to raise_error Errno::ENOENT end end context 'when the given path is not a directory' do before { _fs.touch('/test/test-file') } it 'raises an exception' do expect { described_class.new('/test/test-file') }.to raise_error Errno::ENOTDIR end end end describe '.pwd' do subject { described_class } it_behaves_like 'aliased method', :pwd, :getwd end describe '.rmdir' do it 'deletes the named directory' do described_class.mkdir('/test-dir') described_class.rmdir('/test-dir') expect(described_class.exists?('/test-dir')).to be false end context 'when the directory is not empty' do it 'raises an exception' do described_class.mkdir('/test-dir') described_class.mkdir('/test-dir/test-sub-dir') expect { described_class.rmdir('/test-dir') }.to raise_error(Errno::ENOTEMPTY) end end end describe '.tmpdir' do it 'returns /tmp' do expect(described_class.tmpdir).to eq '/tmp' end end describe '.unlink' do subject { described_class } it_behaves_like 'aliased method', :unlink, :rmdir end describe '#close' do it 'closes the directory' do dir = described_class.open('/test') dir.close expect { dir.close }.to raise_error(IOError) end end describe '#each' do before { _fs.touch('/test/test-file', '/test/test-file2') } it 'calls the block once for each entry in this directory' do expect { |blk| subject.each(&blk) }.to yield_control.exactly(4).times end it 'passes the filename of each entry as a parameter to the block' do expect { |blk| subject.each(&blk) }.to yield_successive_args('.', '..', 'test-file', 'test-file2') end context 'when no block is given' do it 'returns an enumerator' do expect(subject.each).to be_an(Enumerator) end end end describe '#path' do it "returns the path parameter passed to dir's constructor" do expect(subject.path).to eq '/test' end end describe '#pos' do it 'returns the current position in dir' do 3.times { subject.read } expect(subject.pos).to eq 3 end end describe '#pos=' do before { 3.times { subject.read } } it 'seeks to a particular location in dir' do subject.pos = 1 expect(subject.pos).to eq 1 end it 'returns the given position' do expect(subject.pos = 2).to eq 2 end context 'when the location has not been seeked yet' do it "doesn't change the location" do subject.pos = 42 expect(subject.pos).to eq 3 end end context 'when the location is negative' do it "doesn't change the location" do subject.pos = -1 expect(subject.pos).to eq 3 end end end describe '#read' do before do _fs.touch('/test/a') _fs.touch('/test/b') end it 'reads the next entry from dir and returns it' do expect(subject.read).to eq '.' end context 'when calling several times' do it 'returns the next entry each time' do 2.times { subject.read } expect(subject.read).to eq 'a' end end context 'when there are no entries left' do it 'returns nil' do 4.times { subject.read } expect(subject.read).to be_nil end end end describe '#rewind' do it 'repositions dir to the first entry' do 3.times { subject.read } subject.rewind expect(subject.read).to eq '.' end it 'returns the dir itself' do expect(subject.rewind).to be subject end end describe '#seek' do before { 3.times { subject.read } } it 'seeks to a particular location in dir' do subject.seek(1) expect(subject.pos).to eq 1 end it 'returns the dir itself' do expect(subject.seek(2)).to be subject end context 'when the location has not been seeked yet' do it "doesn't change the location" do subject.seek(42) expect(subject.pos).to eq 3 end end context 'when the location is negative' do it "doesn't change the location" do subject.seek(-1) expect(subject.pos).to eq 3 end end end describe '#tell' do it 'returns the current position in dir' do 3.times { subject.read } expect(subject.tell).to eq 3 end end describe '#to_path' do it "returns the path parameter passed to dir's constructor" do expect(subject.to_path).to eq '/test' end end end end memfs-1.0.0/.travis.yml0000644000004100000410000000017313126766224014772 0ustar www-datawww-data# before_install: # - gem update --system '2.4.5' language: ruby rvm: - 2.0.0 - 2.1.10 - 2.2.5 - 2.3.3 - 2.4.0 memfs-1.0.0/lib/0000755000004100000410000000000013126766224013426 5ustar www-datawww-datamemfs-1.0.0/lib/memfs.rb0000644000004100000410000000675013126766224015072 0ustar www-datawww-datarequire 'memfs/version' require 'fileutils' # Provides a clean way to interact with a fake file system. # # @example Calling activate with a block. # MemFs.activate do # Dir.mkdir '/hello_world' # # /hello_world exists here, in memory # end # # /hello_world doesn't exist and has never been on the real FS # # @example Calling activate! and deactivate!. # MemFs.activate! # # The fake file system is running here # MemFs.deactivate! # # Everything back to normal module MemFs # Keeps track of the original Ruby Dir class. OriginalDir = ::Dir # Keeps track of the original Ruby File class. OriginalFile = ::File # Keeps track of the original Ruby IO class. OriginalIO = ::IO require 'memfs/file_system' require 'memfs/dir' require 'memfs/file' require 'memfs/file/stat' # Calls the given block with MemFs activated. # # The advantage of using {#activate} against {#activate!} is that, in case an # exception occurs, MemFs is deactivated. # # @yield with no argument. # # @example # MemFs.activate do # Dir.mkdir '/hello_world' # # /hello_world exists here, in memory # end # # /hello_world doesn't exist and has never been on the real FS # # @example Exception in activate block. # MemFs.activate do # raise "Some Error" # end # # Still back to the original Ruby classes # # @return nothing. def activate activate! yield ensure deactivate! end module_function :activate # Activates the fake file system. # # @note Don't forget to call {#deactivate!} to disable the fake file system, # you may have some issues in your scripts or tests otherwise. # # @example # MemFs.activate! # Dir.mkdir '/hello_world' # # /hello_world exists here, in memory # MemFs.deactivate! # # /hello_world doesn't exist and has never been on the real FS # # @see #deactivate! # @return nothing. def activate!(clear: true) Object.class_eval do remove_const :Dir remove_const :File remove_const :IO const_set :Dir, MemFs::Dir const_set :IO, MemFs::IO const_set :File, MemFs::File end MemFs::FileSystem.instance.clear! if clear end module_function :activate! # Deactivates the fake file system. # # @note This method should always be called when using activate! # # @see #activate! # @return nothing. def deactivate! Object.class_eval do remove_const :Dir remove_const :File remove_const :IO const_set :Dir, MemFs::OriginalDir const_set :IO, MemFs::OriginalIO const_set :File, MemFs::OriginalFile end end module_function :deactivate! # Switches back to the original file system, calls the given block (if any), # and switches back afterwards. # # If a block is given, all file & dir operations (like reading dir contents or # requiring files) will operate on the original fs. # # @example # MemFs.halt do # puts Dir.getwd # end # @return nothing def halt deactivate! yield if block_given? ensure activate!(clear: false) end module_function :halt # Creates a file and all its parent directories. # # @param path: The path of the file to create. # # @return nothing. def touch(*paths) if ::File != MemFs::File fail 'Always call MemFs.touch inside a MemFs active context.' end paths.each do |path| FileUtils.mkdir_p File.dirname(path) FileUtils.touch path end end module_function :touch end memfs-1.0.0/lib/memfs/0000755000004100000410000000000013126766224014535 5ustar www-datawww-datamemfs-1.0.0/lib/memfs/file/0000755000004100000410000000000013126766224015454 5ustar www-datawww-datamemfs-1.0.0/lib/memfs/file/stat.rb0000644000004100000410000000723413126766224016762 0ustar www-datawww-datarequire 'forwardable' require 'memfs/filesystem_access' module MemFs class File class Stat extend Forwardable include FilesystemAccess attr_reader :entry def_delegators :entry, :atime, :blksize, :ctime, :dev, :gid, :ino, :mode, :mtime, :uid def blockdev? !!entry.block_device end def chardev? !!entry.character_device end def directory? entry.is_a?(Fake::Directory) end def executable? user_executable? || group_executable? || !!world_executable? end def executable_real? user_executable_real? || group_executable_real? || !!world_executable? end def file? entry.is_a?(Fake::File) end def ftype entry.type end def grpowned? gid == Process.egid end def initialize(path, dereference = false) entry = fs.find!(path) @entry = dereference ? entry.dereferenced : entry end def owned? uid == Process.euid end def pipe? false end def readable? user_readable? || group_readable? || !!world_readable? end def readable_real? user_readable_real? || group_readable_real? || !!world_readable? end def setgid? !!(entry.mode & Fake::Entry::SETGID).nonzero? end def setuid? !!(entry.mode & Fake::Entry::SETUID).nonzero? end def socket? false end def sticky? !!(entry.mode & Fake::Entry::USTICK).nonzero? end def symlink? entry.is_a?(Fake::Symlink) end def world_readable? entry.mode - 0100000 if (entry.mode & Fake::Entry::OREAD).nonzero? end def world_writable? entry.mode - 0100000 if (entry.mode & Fake::Entry::OWRITE).nonzero? end def writable? user_writable? || group_writable? || !!world_writable? end def writable_real? user_writable_real? || group_writable_real? || !!world_writable? end def zero? !!(entry.content && entry.content.empty?) end private def group_executable? grpowned? && !!(mode & Fake::Entry::GEXEC).nonzero? end def group_executable_real? Process.gid == gid && !!(mode & Fake::Entry::GEXEC).nonzero? end def group_readable? grpowned? && !!(mode & Fake::Entry::GREAD).nonzero? end def group_readable_real? Process.gid == gid && !!(mode & Fake::Entry::GREAD).nonzero? end def group_writable? grpowned? && !!(mode & Fake::Entry::GWRITE).nonzero? end def group_writable_real? Process.gid == gid && !!(mode & Fake::Entry::GWRITE).nonzero? end def user_executable? owned? && !!(mode & Fake::Entry::UEXEC).nonzero? end def user_executable_real? Process.uid == uid && !!(mode & Fake::Entry::UEXEC).nonzero? end def user_readable? owned? && !!(mode & Fake::Entry::UREAD).nonzero? end def user_readable_real? Process.uid == uid && !!(mode & Fake::Entry::UREAD).nonzero? end def user_writable? owned? && !!(mode & Fake::Entry::UWRITE).nonzero? end def user_writable_real? Process.uid == uid && !!(mode & Fake::Entry::UWRITE).nonzero? end def world_executable? entry.mode - 0100000 if (entry.mode & Fake::Entry::OEXEC).nonzero? end end end end memfs-1.0.0/lib/memfs/file.rb0000644000004100000410000001612213126766224016003 0ustar www-datawww-datarequire 'forwardable' require 'memfs/filesystem_access' require 'memfs/io' module MemFs class File < IO extend FilesystemAccess extend SingleForwardable include Enumerable include FilesystemAccess ALT_SEPARATOR = nil MODE_MAP = { 'r' => RDONLY, 'r+' => RDWR, 'w' => CREAT | TRUNC | WRONLY, 'w+' => CREAT | TRUNC | RDWR, 'a' => CREAT | APPEND | WRONLY, 'a+' => CREAT | APPEND | RDWR }.freeze SEPARATOR = '/'.freeze SUCCESS = 0 @umask = nil def_delegators :original_file_class, :basename, :dirname, :extname, :fnmatch, :join, :path, :split [ :blockdev?, :chardev?, :directory?, :executable?, :executable_real?, :file?, :grpowned?, :owned?, :pipe?, :readable?, :readable_real?, :setgid?, :setuid?, :socket?, :sticky?, :writable?, :writable_real?, :zero? ].each do |query_method| # def directory?(path) # stat_query(path, :directory?) # end define_singleton_method(query_method) do |path| stat_query(path, query_method) end end [ :world_readable?, :world_writable? ].each do |query_method| # def directory?(path) # stat_query(path, :directory?, false) # end define_singleton_method(query_method) do |path| stat_query(path, query_method, false) end end def self.absolute_path(path, dir_string = fs.pwd) original_file_class.absolute_path(path, dir_string) end def self.atime(path) stat(path).atime end def self.chmod(mode_int, *paths) paths.each do |path| fs.chmod mode_int, path end end def self.chown(uid, gid, *paths) paths.each do |path| fs.chown(uid, gid, path) end paths.size end def self.ctime(path) stat(path).ctime end def self.exists?(path) !!fs.find(path) end class << self; alias_method :exist?, :exists?; end def self.expand_path(file_name, dir_string = fs.pwd) original_file_class.expand_path(file_name, dir_string) end def self.ftype(path) fs.find!(path) && lstat(path).ftype end class << self; alias_method :fnmatch?, :fnmatch; end def self.identical?(path1, path2) fs.find!(path1).dereferenced === fs.find!(path2).dereferenced rescue Errno::ENOENT false end def self.lchmod(mode_int, *file_names) file_names.each do |file_name| fs.chmod mode_int, file_name end end def self.lchown(uid, gid, *paths) chown uid, gid, *paths end def self.link(old_name, new_name) fs.link old_name, new_name SUCCESS end def self.lstat(path) Stat.new(path) end def self.mtime(path) stat(path).mtime end def self.open(filename, mode = RDONLY, *perm_and_opt) file = new(filename, mode, *perm_and_opt) if block_given? yield file else file end ensure file.close if file && block_given? end def self.readlink(path) fs.find!(path).target end def self.realdirpath(path, dir_string = fs.pwd) loose_dereference_path(absolute_path(path, dir_string)) end def self.realpath(path, dir_string = fs.pwd) dereference_path(absolute_path(path, dir_string)) end def self.rename(old_name, new_name) fs.rename(old_name, new_name) SUCCESS end def self.reset! @umask = original_file_class.umask end def self.size(path) fs.find!(path).size end def self.size?(path) file = fs.find(path) if file && file.size > 0 file.size else false end end def self.stat(path) Stat.new(path, true) end def self.symlink(old_name, new_name) fs.symlink old_name, new_name SUCCESS end def self.symlink?(path) lstat_query(path, :symlink?) end def self.truncate(path, length) fs.find!(path).content.truncate(length) SUCCESS end def self.umask(integer = nil) old_value = @umask || original_file_class.umask @umask = integer if integer old_value end def self.unlink(*paths) paths.each do |path| fs.unlink(path) end paths.size end class << self; alias_method :delete, :unlink; end def self.utime(atime, mtime, *file_names) file_names.each do |file_name| fs.find!(file_name).atime = atime fs.find!(file_name).mtime = mtime end file_names.size end attr_reader :path def initialize(filename, mode = File::RDONLY, *perm_and_or_opt) opt = perm_and_or_opt.last.is_a?(Hash) ? perm_and_or_opt.pop : {} perm_and_or_opt.shift if perm_and_or_opt.any? fail ArgumentError, 'wrong number of arguments (4 for 1..3)' end @path = filename @external_encoding = opt[:external_encoding] && Encoding.find(opt[:external_encoding]) self.closed = false self.opening_mode = str_to_mode_int(mode) fs.touch(filename) if create_file? self.entry = fs.find!(filename) # FIXME: this is an ugly way to ensure a symlink has a target entry.dereferenced if entry.respond_to?(:pos=) entry.pos = 0 end entry.content.clear if truncate_file? end def atime File.atime(path) end def chmod(mode_int) fs.chmod(mode_int, path) SUCCESS end def chown(uid, gid = nil) fs.chown(uid, gid, path) SUCCESS end def ctime File.ctime(path) end def flock(*) SUCCESS end def mtime File.mtime(path) end def lstat File.lstat(path) end def size entry.size end def truncate(integer) File.truncate(path, integer) end def self.dereference_name(path) entry = fs.find(path) if entry entry.dereferenced_name else basename(path) end end private_class_method :dereference_name def self.dereference_dir_path(path) dereference_path(dirname(path)) end private_class_method :dereference_dir_path def self.dereference_path(path) fs.find!(path).dereferenced_path end private_class_method :dereference_path def self.loose_dereference_path(path) join(dereference_dir_path(path), dereference_name(path)) end private_class_method :loose_dereference_path def self.original_file_class MemFs::OriginalFile end private_class_method :original_file_class def self.stat_query(path, query, force_boolean = true) response = fs.find(path) && stat(path).public_send(query) force_boolean ? !!response : response end private_class_method :stat_query def self.lstat_query(path, query) response = fs.find(path) && lstat(path).public_send(query) !!response end private_class_method :lstat_query end end memfs-1.0.0/lib/memfs/filesystem_access.rb0000644000004100000410000000015313126766224020566 0ustar www-datawww-datamodule MemFs module FilesystemAccess private def fs FileSystem.instance end end end memfs-1.0.0/lib/memfs/fake/0000755000004100000410000000000013126766224015443 5ustar www-datawww-datamemfs-1.0.0/lib/memfs/fake/directory.rb0000644000004100000410000000224413126766224017776 0ustar www-datawww-datarequire 'memfs/fake/entry' module MemFs module Fake class Directory < Entry attr_accessor :entries def add_entry(entry) entries[entry.name] = entry entry.parent = self end def empty? (entries.keys - %w[. ..]).empty? end def entry_names entries.keys end def find(path) path = path.sub(%r{\A/+}, '').sub(%r{/+\z}, '') parts = path.split('/', 2) if entry_names.include?(path) entries[path] elsif entry_names.include?(parts.first) entries[parts.first].find(parts.last) end end def initialize(*args) super self.entries = { '.' => self, '..' => nil } end def parent=(parent) super entries['..'] = parent end def path name == '/' ? '/' : super end def paths [path] + entries.reject { |p| %w[. ..].include?(p) } .values .map(&:paths) .flatten end def remove_entry(entry) entries.delete(entry.name) end def type 'directory' end end end end memfs-1.0.0/lib/memfs/fake/file/0000755000004100000410000000000013126766224016362 5ustar www-datawww-datamemfs-1.0.0/lib/memfs/fake/file/content.rb0000644000004100000410000000171013126766224020360 0ustar www-datawww-datarequire 'delegate' module MemFs module Fake class File < Entry class Content < SimpleDelegator attr_accessor :pos def close end def initialize(obj = '') @string = obj.to_s.dup @pos = 0 __setobj__ @string end def puts(*strings) strings.each do |str| @string << str next if str.end_with?($/) @string << $/ end end def read(length = nil, buffer = '') length ||= @string.length - @pos buffer.replace @string[@pos, length] @pos += buffer.bytesize buffer.empty? ? nil : buffer end def truncate(length) @string.replace @string[0, length] end def to_s @string end def write(string) text = string.to_s @string << text text.size end end end end end memfs-1.0.0/lib/memfs/fake/file.rb0000644000004100000410000000122113126766224016703 0ustar www-datawww-datarequire 'memfs/fake/entry' require 'memfs/fake/file/content' module MemFs module Fake class File < Entry attr_accessor :content def close @closed = true end def closed? @closed end def initialize(*args) super @content = Content.new @closed = false end def pos content.pos end def pos=(value) content.pos = value end def size content.size end def type return 'blockSpecial' if block_device return 'characterSpecial' if character_device 'file' end end end end memfs-1.0.0/lib/memfs/fake/symlink.rb0000644000004100000410000000205513126766224017460 0ustar www-datawww-datarequire 'memfs/filesystem_access' module MemFs module Fake class Symlink < Entry include MemFs::FilesystemAccess attr_reader :target def dereferenced @dereferenced ||= fs.find!(target).dereferenced end def dereferenced_name real_target.dereferenced_name end def dereferenced_path dereferenced.dereferenced_path end def find(path) dereferenced.find(path) rescue Errno::ENOENT nil end def initialize(path, target) super(path) @target = target end def method_missing(meth, *args, &block) if dereferenced.respond_to?(meth) dereferenced.public_send(meth, *args, &block) else super end end def respond_to_missing?(meth, include_private) dereferenced.respond_to?(meth, include_private) || super end def type 'link' end private def real_target fs.find(target) || Entry.new(target) end end end end memfs-1.0.0/lib/memfs/fake/entry.rb0000644000004100000410000000324313126766224017133 0ustar www-datawww-datamodule MemFs module Fake class Entry UREAD = 00100 UWRITE = 00200 UEXEC = 00400 GREAD = 00010 GWRITE = 00020 GEXEC = 00040 OREAD = 00001 OWRITE = 00002 OEXEC = 00004 RSTICK = 01000 USTICK = 05000 SETUID = 04000 SETGID = 02000 attr_accessor :atime, :block_device, :character_device, :ctime, :gid, :mtime, :name, :parent, :uid attr_reader :mode def blksize 4096 end def delete parent.remove_entry self end def dereferenced self end def dereferenced_name name end def dereferenced_path path end def dev @dev ||= rand(1000) end def find(_path) fail Errno::ENOTDIR, path end def initialize(path = nil) time = Time.now self.atime = time self.ctime = time self.gid = Process.egid self.mode = 0666 - MemFs::File.umask self.mtime = time self.name = MemFs::File.basename(path || '') self.uid = Process.euid end def ino @ino ||= rand(1000) end def mode=(mode_int) @mode = 0100000 | mode_int end def path parts = [parent && parent.path, name].compact MemFs::File.join(parts) end def paths [path] end def touch self.atime = self.mtime = Time.now end def type 'unknown' end end end end memfs-1.0.0/lib/memfs/version.rb0000644000004100000410000000005413126766224016546 0ustar www-datawww-datamodule MemFs VERSION = '1.0.0'.freeze end memfs-1.0.0/lib/memfs/file_system.rb0000644000004100000410000000603413126766224017410 0ustar www-datawww-datarequire 'singleton' require 'memfs/fake/directory' require 'memfs/fake/file' require 'memfs/fake/symlink' module MemFs class FileSystem include Singleton attr_accessor :working_directory attr_accessor :registred_entries attr_accessor :root def basename(path) File.basename(path) end def chdir(path) destination = find_directory!(path) previous_directory = working_directory self.working_directory = destination yield if block_given? ensure self.working_directory = previous_directory if block_given? end def clear! self.root = Fake::Directory.new('/') mkdir '/tmp' chdir '/' end def chmod(mode_int, file_name) find!(file_name).mode = mode_int end def chown(uid, gid, path) entry = find!(path).dereferenced entry.uid = uid if uid && uid != -1 entry.gid = gid if gid && gid != -1 end def dirname(path) File.dirname(path) end def entries(path) find_directory!(path).entry_names end def find(path) if path == '/' root elsif dirname(path) == '.' working_directory.find(path) else root.find(path) end end def find!(path) find(path) || fail(Errno::ENOENT, path) end def find_directory!(path) entry = find!(path).dereferenced fail Errno::ENOTDIR, path unless entry.is_a?(Fake::Directory) entry end def find_parent!(path) parent_path = dirname(path) find_directory!(parent_path) end def getwd working_directory.path end alias_method :pwd, :getwd def initialize clear! end def link(old_name, new_name) file = find!(old_name) fail Errno::EEXIST, "(#{old_name}, #{new_name})" if find(new_name) link = file.dup link.name = basename(new_name) find_parent!(new_name).add_entry link end def mkdir(path, mode = 0777) fail Errno::EEXIST, path if find(path) directory = Fake::Directory.new(path) directory.mode = mode find_parent!(path).add_entry directory end def paths root.paths end def rename(old_name, new_name) file = find!(old_name) file.delete file.name = basename(new_name) find_parent!(new_name).add_entry(file) end def rmdir(path) directory = find!(path) fail Errno::ENOTEMPTY, path unless directory.empty? directory.delete end def symlink(old_name, new_name) fail Errno::EEXIST, new_name if find(new_name) find_parent!(new_name).add_entry Fake::Symlink.new(new_name, old_name) end def touch(*paths) paths.each do |path| entry = find(path) unless entry entry = Fake::File.new(path) parent_dir = find_parent!(path) parent_dir.add_entry entry end entry.touch end end def unlink(path) entry = find!(path) fail Errno::EPERM, path if entry.is_a?(Fake::Directory) entry.delete end end end memfs-1.0.0/lib/memfs/dir.rb0000644000004100000410000000567313126766224015653 0ustar www-datawww-datarequire 'memfs/filesystem_access' module MemFs class Dir extend FilesystemAccess include Enumerable include FilesystemAccess attr_reader :pos def self.[](*patterns) glob(patterns) end def self.chdir(path, &block) fs.chdir path, &block 0 end def self.chroot(path) fail Errno::EPERM, path unless Process.uid.zero? dir = fs.find_directory!(path) dir.name = '/' fs.root = dir 0 end def self.entries(dirname, _opts = {}) fs.entries(dirname) end def self.exists?(path) File.directory?(path) end class << self; alias_method :exist?, :exists?; end def self.foreach(dirname, &block) return to_enum(__callee__, dirname) unless block entries(dirname).each(&block) end def self.getwd fs.getwd end class << self; alias_method :pwd, :getwd; end def self.glob(patterns, flags = 0) patterns = [*patterns].map(&:to_s) list = fs.paths.select do |path| patterns.any? do |pattern| File.fnmatch?(pattern, path, flags | GLOB_FLAGS) end end # FIXME: ugly special case for /* and / list.delete('/') if patterns.first == '/*' return list unless block_given? list.each { |path| yield path } nil end def self.home(*args) original_dir_class.home(*args) end def self.mkdir(path, mode = 0777) fs.mkdir path, mode end def self.open(dirname) dir = new(dirname) if block_given? yield dir else dir end ensure dir && dir.close if block_given? end def self.rmdir(path) fs.rmdir path end def self.tmpdir '/tmp' end class << self alias_method :delete, :rmdir alias_method :unlink, :rmdir end def initialize(path) self.entry = fs.find_directory!(path) self.state = :open @pos = 0 self.max_seek = 0 end def close fail IOError, 'closed directory' if state == :closed self.state = :closed end def each(&block) return to_enum(__callee__) unless block entry.entry_names.each(&block) end def path entry.path end alias_method :to_path, :path def pos=(position) seek(position) position end def read name = entries[pos] @pos += 1 self.max_seek = pos name end def rewind @pos = 0 self end def seek(position) @pos = position if (0..max_seek).cover?(position) self end def tell @pos end private GLOB_FLAGS = if defined?(File::FNM_EXTGLOB) File::FNM_EXTGLOB | File::FNM_PATHNAME else File::FNM_PATHNAME end attr_accessor :entry, :max_seek, :state def self.original_dir_class MemFs::OriginalDir end private_class_method :original_dir_class end end memfs-1.0.0/lib/memfs/io.rb0000644000004100000410000001225713126766224015500 0ustar www-datawww-datarequire 'forwardable' require 'memfs/filesystem_access' module MemFs class IO extend SingleForwardable include OriginalFile::Constants (OriginalIO.constants - OriginalFile::Constants.constants) .each do |const_name| self.const_set(const_name, OriginalIO.const_get(const_name)) end def_delegators :original_io_class, :copy_stream def self.read(path, *args) options = args.last.is_a?(Hash) ? args.pop : {} options = { mode: File::RDONLY, encoding: nil, open_args: nil }.merge(options) open_args = options[:open_args] || [options[:mode], encoding: options[:encoding]] length, offset = args file = open(path, *open_args) file.seek(offset || 0) file.read(length) ensure file.close if file end def self.write(path, string, offset = 0, open_args = nil) open_args ||= [File::WRONLY, encoding: nil] offset = 0 if offset.nil? unless offset.respond_to?(:to_int) fail TypeError, "no implicit conversion from #{offset.class}" end offset = offset.to_int if offset > 0 fail NotImplementedError, 'MemFs::IO.write with offset not yet supported.' end file = open(path, *open_args) file.seek(offset) file.write(string) ensure file.close if file end def self.original_io_class MemFs::OriginalIO end private_class_method :original_io_class attr_writer :autoclose, :close_on_exec def <<(object) fail IOError, 'not opened for writing' unless writable? content << object.to_s end def advise(advice_type, _offset = 0, _len = 0) advice_types = [ :dontneed, :noreuse, :normal, :random, :sequential, :willneed ] unless advice_types.include?(advice_type) fail NotImplementedError, "Unsupported advice: #{advice_type.inspect}" end nil end def autoclose? defined?(@autoclose) ? !!@autoclose : true end def binmode @binmode = true @external_encoding = Encoding::ASCII_8BIT self end def binmode? defined?(@binmode) ? @binmode : false end def close self.closed = true end def closed? closed end def close_on_exec? defined?(@close_on_exec) ? !!@close_on_exec : true end def eof? pos >= content.size end alias_method :eof, :eof? def external_encoding if writable? @external_encoding else @external_encoding ||= Encoding.default_external end end def each(sep = $/) return to_enum(__callee__) unless block_given? fail IOError, 'not opened for reading' unless readable? content.each_line(sep) { |line| yield(line) } self end def each_byte return to_enum(__callee__) unless block_given? fail IOError, 'not opened for reading' unless readable? content.each_byte { |byte| yield(byte) } self end alias_method :bytes, :each_byte def each_char return to_enum(__callee__) unless block_given? fail IOError, 'not opened for reading' unless readable? content.each_char { |char| yield(char) } self end alias_method :chars, :each_char def pos entry.pos end def print(*objs) objs << $_ if objs.empty? self << objs.join($,) << $\.to_s nil end def printf(format_string, *objs) print format_string % objs end def puts(text) fail IOError, 'not opened for writing' unless writable? content.puts text end def read(length = nil, buffer = '') unless entry fail(Errno::ENOENT, path) end default = length ? nil : '' content.read(length, buffer) || default end def seek(amount, whence = ::IO::SEEK_SET) new_pos = case whence when ::IO::SEEK_CUR then entry.pos + amount when ::IO::SEEK_END then content.to_s.length + amount when ::IO::SEEK_SET then amount end fail Errno::EINVAL, path if new_pos.nil? || new_pos < 0 entry.pos = new_pos 0 end def stat File.stat(path) end def write(string) fail IOError, 'not opened for writing' unless writable? content.write(string.to_s) end private attr_accessor :closed, :entry, :opening_mode attr_reader :path def content entry.content end def create_file? (opening_mode & File::CREAT).nonzero? end def readable? (opening_mode & File::RDWR).nonzero? || (opening_mode | File::RDONLY).zero? end def str_to_mode_int(mode) return mode unless mode.is_a?(String) unless mode =~ /\A([rwa]\+?)([bt])?(:bom)?(\|.+)?\z/ fail ArgumentError, "invalid access mode #{mode}" end mode_str = $~[1] File::MODE_MAP[mode_str] end def truncate_file? (opening_mode & File::TRUNC).nonzero? end def writable? (opening_mode & File::WRONLY).nonzero? || (opening_mode & File::RDWR).nonzero? end end end memfs-1.0.0/.rubocop.yml0000777000004100000410000000000013126766224020204 2./.ruby-style.ymlustar www-datawww-datamemfs-1.0.0/.gitignore0000644000004100000410000000027513126766224014654 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp lib/fileutils.rb spec/examples.txt memfs-1.0.0/memfs.gemspec0000644000004100000410000000255713126766224015345 0ustar www-datawww-data# -*- encoding: utf-8 -*- lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'memfs/version' Gem::Specification.new do |gem| gem.name = 'memfs' gem.version = MemFs::VERSION gem.authors = ['Simon COURTOIS'] gem.email = ['scourtois@cubyx.fr'] gem.description = 'MemFs provides a fake file system that can be used ' \ 'for tests. Strongly inspired by FakeFS.' gem.summary = "memfs-#{MemFs::VERSION}" gem.homepage = 'http://github.com/simonc/memfs' gem.license = 'MIT' gem.files = `git ls-files`.split($/) gem.executables = gem.files.grep(/^bin\//).map { |f| File.basename(f) } gem.test_files = gem.files.grep(/^(test|spec|features)\//) gem.require_paths = ['lib'] gem.add_development_dependency 'coveralls', '~> 0.6' gem.add_development_dependency 'rake', '~> 12.0' gem.add_development_dependency 'rspec', '~> 3.0' gem.add_development_dependency 'guard', '~> 2.6' gem.add_development_dependency 'guard-rspec', '~> 4.3' gem.add_development_dependency 'rb-inotify', '~> 0.8' gem.add_development_dependency 'rb-fsevent', '~> 0.9' gem.add_development_dependency 'rb-fchange', '~> 0.0' listen_version = RUBY_VERSION >= '2.2.3' ? '~> 3.1' : '~> 3.0.7' gem.add_development_dependency 'listen', listen_version end memfs-1.0.0/CHANGELOG.md0000644000004100000410000000335213126766224014474 0ustar www-datawww-data# Changelog ## 1.0.0 :warning: This version drops support for Ruby 1.9. * ADD: Support for Ruby 2.4.0 * ADD: Support for _Pathname_ in `Dir.glob` (PR #21 by @craigw) * ADD: `MemFs.halt` to switch back to the real file-system (PR #24 by @thsur) * ADD: Basic support for `IO.write` (PR #20 by @rmm5t) * FIX: Reset the file position when reopened (PR #23 by @jimpo) * FIX: Ignore trailing slashes when searching an entry (issue #26) * FIX: Making `File` inherit from `IO` to fix 3rd-party related issues * FIX: Ensure `File.new` on a symlink raises if target is absent ## 0.5.0 * ADD: Support for _mode_ to `Dir.mkdir`, `FileUtils.mkdir` and `FileUtils.mkdir_p` (@raeno) * ADD: Support for Ruby 2.2 (@raeno) ## 0.4.3 * ADD: `File::SEPARATOR` and `File::ALT_SEPARATOR` * FIX: Support `YAML.load_file` by handling `r:bom|utf-8` open mode ## 0.4.2 * ADD: `File#external_encoding` * FIX: Undefined local variable or method `fs' for MemFs::File ## 0.4.1 * FIX: Support for 1.9.3 broken by File::FNM_EXTGLOB ## 0.4.0 * ADD: `Dir.chroot` * ADD: `Dir.glob` and `Dir[]` * ADD: `Dir.open` * ADD: `Dir.tmpdir` * ADD: `Dir#close` * ADD: `Dir#path` * ADD: `Dir#pos=` * ADD: `Dir#pos` * ADD: `Dir#read` * ADD: `Dir#rewind` * ADD: `Dir#seek` * ADD: `Dir#tell` * ADD: `Dir#to_path` * FIX: Internal implementation methods are now private ## 0.3.0 * FIX: The gem is now Ruby 1.9 compatible ## 0.2.0 * ADD: Allowing magic creation of files with `MemFs.touch` * ADD: `Dir#each` * ADD: `Dir.delete` * ADD: `Dir.exist?` * ADD: `Dir.foreach` * ADD: `Dir.home` * ADD: `Dir.new` * ADD: `Dir.unlink` * FIX: File.new now truncates a file when opening mode says so ## 0.1.0 * ADD: Adding `File` missing methods - #3 ## 0.0.2 * ADD: Adding the MIT license to the gemspec file - #2 memfs-1.0.0/README.md0000644000004100000410000001162413126766224014143 0ustar www-datawww-data![MemFs Logo](https://raw.github.com/simonc/memfs/master/memfs.png) [![Gem Version](https://badge.fury.io/rb/memfs.svg)](https://badge.fury.io/rb/memfs) [![Build Status](https://api.travis-ci.org/simonc/memfs.svg?branch=master)](http://travis-ci.org/simonc/memfs) [![Code Climate](https://codeclimate.com/github/simonc/memfs/badges/gpa.svg)](https://codeclimate.com/github/simonc/memfs) [![Coverage Status](https://coveralls.io/repos/github/simonc/memfs/badge.svg?branch=master)](https://coveralls.io/github/simonc/memfs?branch=master) MemFs is an in-memory filesystem that can be used for your tests. When you're writing code that manipulates files, directories, symlinks, you need to be able to test it without touching your hard drive. MemFs is made for it. MemFs is intended for tests but you can use it for any other scenario needing in memory file system. MemFs is greatly inspired by the awesome [FakeFs](https://github.com/defunkt/fakefs). The main goal of MemFs is to be 100% compatible with the Ruby libraries like FileUtils. For French people, the answer is yes, the joke in the name is intended ;) ## Take a look Here is a simple example of MemFs usage: ``` ruby MemFs.activate! File.open('/test-file', 'w') { |f| f.puts "hello world" } File.read('/test-file') #=> "hello world\n" MemFs.deactivate! File.exists?('/test-file') #=> false # Or with the block syntax MemFs.activate do FileUtils.touch('/test-file', verbose: true, noop: true) File.exists?('/test-file') #=> true end File.exists?('/test-file') #=> false ``` ## Why you may prefer MemFs over FakeFS? While FakeFS is pretty cool it overrides classes like `FileUtils`. This kind of override is problematic when you rely on real behavior from this kind of tool. For instance, trying to test the following with FakeFS will not work, the `noop` option will be ignored: ``` ruby FileUtils.touch('somefile.txt', noop: true) ``` MemFs tries to be **compliant with the Ruby API** by overriding only the low level classes (C classes) like File, Dir or File::Stat leaving the stdlib classes untouched and still working, being less intrusive that way. Some stdlib classes may be overriden at some point if they don't use `File` or `Dir`, like `Pathname`, etc. Another key point is that MemFs **aims to implement every single method provided by Ruby classes** (when possible) and to behave and return **exactly** the same way as the original classes. ## Installation Add this line to your application's Gemfile: gem 'memfs' And then execute: $ bundle Or install it yourself as: $ gem install memfs ## Usage in tests ### Global activation Add the following to your `spec_helper.rb`: ``` ruby RSpec.configure do |config| config.before do MemFs.activate! end config.after do MemFs.deactivate! end end ``` All the spec will be sandboxed in MemFs. If you want to set it globally with flag activation, you can do the following in you `spec_helper.rb` file: ``` ruby Rspec.configure do |c| c.around(:each, memfs: true) do |example| MemFs.activate { example.run } end end ``` And then write your specs like this: ``` ruby it "creates a file", memfs: true do subject.create_file('test.rb') expect(File.exists?('test.rb')).to be true end ``` ### Local activation You can choose to activate MemFs only for a specific test: ``` ruby describe FileCreator do describe '.create_file' do it "creates a file" do MemFs.activate do subject.create_file('test.rb') expect(File.exists?('test.rb')).to be true end end end end ``` No real file will be created during the test. You can also use it for a specific `describe` block: ``` ruby describe FileCreator do before { MemFs.activate! } after { MemFs.deactivate! } describe '.create_file' do it "creates a file" do subject.create_file('test.rb') expect(File.exists?('test.rb')).to be true end end end ``` ### Utilities You can use `MemFs.touch` to quickly create a file and its parent directories: ``` ruby MemFs.activate do MemFs.touch('/path/to/some/file.rb') File.exist?('/path/to/some/file.rb') # => true end ``` ## Requirements * Ruby 2.0 or newer ## Known issues * MemFs doesn't implement IO so methods like `FileUtils.copy_stream` and `IO.write` are still the originals. * Similarly, MemFs doesn't implement Kernel, so don't use a naked `open()` call. This uses the `Kernel` class via `method_missing`, which MemFs will not intercept. * Pipes and Sockets are not handled for now. * ~`require "pp"` will raise a _superclass mismatch_ exception since MemFs::File does not inherit from IO. The best thing to do is to require pp _before_ MemFs.~ ## TODO * Implement missing methods from `File`, `Dir` and `Stat` ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request memfs-1.0.0/Guardfile0000644000004100000410000000044513126766224014510 0ustar www-datawww-dataguard :rspec, cmd: 'bundle exec rspec', all_after_pass: true, all_on_start: true do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch('lib/memfs/io.rb') { |m| 'spec/memfs/file_spec.rb' } watch('spec/spec_helper.rb') { 'spec' } end memfs-1.0.0/.ruby-style.yml0000644000004100000410000002344513126766224015610 0ustar www-datawww-dataAllCops: Exclude: - Guardfile - lib/fileutils.rb - "*.gemspec" - "db/schema.rb" - "vendor/**/*" UseCache: false Metrics/LineLength: Exclude: - Rakefile - "spec/**/*" Style/BlockDelimiters: Description: abc StyleGuide: https://github.com/bbatsov/ruby-style-guide#single-line-blocks Enabled: true EnforcedStyle: braces_for_chaining SupportedStyles: - braces_for_chaining - line_count_based - semantic ProceduralMethods: - around - before - benchmark - bm - bmbm - create - each_with_object - measure - new - realtime - tap - with_object FunctionalMethods: - let - let! - subject - watch IgnoredMethods: - context - describe - it - lambda - proc Style/CaseEquality: Description: Avoid explicit use of the case equality operator(===). StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-case-equality Enabled: false Style/CollectionMethods: Description: Preferred collection methods. StyleGuide: https://github.com/bbatsov/ruby-style-guide#map-find-select-reduce-size Enabled: true PreferredMethods: collect: map collect!: map! find: detect find_all: select reduce: inject Style/DotPosition: Description: Checks the position of the dot in multi-line method calls. StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains Enabled: true EnforcedStyle: leading SupportedStyles: - leading - trailing Style/FileName: Description: Use snake_case for source file names. StyleGuide: https://github.com/bbatsov/ruby-style-guide#snake-case-files Enabled: false Exclude: [] Style/GuardClause: Description: Check for conditionals that can be replaced with guard clauses StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals Enabled: true MinBodyLength: 1 Style/IfUnlessModifier: Description: Favor modifier if/unless usage when you have a single-line body. StyleGuide: https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier Enabled: false MaxLineLength: 80 Style/OptionHash: Description: Don't use option hashes when you can use keyword arguments. Enabled: false Style/NumericLiteralPrefix: Enabled: false Style/PercentLiteralDelimiters: Description: Use `%`-literal delimiters consistently StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-literal-braces Enabled: false PreferredDelimiters: "%": "()" "%i": "[]" "%q": "()" "%Q": "()" "%r": "{}" "%s": "()" "%w": "[]" "%W": "[]" "%x": "()" Style/PredicateName: Description: Check the names of predicate methods. StyleGuide: https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark Enabled: true NamePrefix: - is_ - has_ - have_ NamePrefixBlacklist: - is_ Exclude: - spec/**/* Style/RaiseArgs: Description: Checks the arguments passed to raise/fail. StyleGuide: https://github.com/bbatsov/ruby-style-guide#exception-class-messages Enabled: false EnforcedStyle: exploded SupportedStyles: - compact - exploded Style/RegexpLiteral: Description: Checks if uses of regexps match the configured preference. StyleGuide: https://github.com/bbatsov/ruby-style-guide#percent-r Enabled: true EnforcedStyle: mixed SupportedStyles: - mixed - percent_r - slashes AllowInnerSlashes: false Style/SignalException: Description: Checks for proper usage of fail and raise. StyleGuide: https://github.com/bbatsov/ruby-style-guide#fail-method Enabled: false EnforcedStyle: semantic SupportedStyles: - only_raise - only_fail - semantic Style/SingleLineBlockParams: Description: Enforces the names of some block params. StyleGuide: https://github.com/bbatsov/ruby-style-guide#reduce-blocks Enabled: false Methods: - reduce: - a - e - inject: - a - e Style/SingleLineMethods: Description: Avoid single-line methods. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-single-line-methods Enabled: false AllowIfMethodIsEmpty: true Style/StringLiterals: Description: Checks if uses of quotes match the configured preference. StyleGuide: https://github.com/bbatsov/ruby-style-guide#consistent-string-literals Enabled: true EnforcedStyle: single_quotes SupportedStyles: - single_quotes - double_quotes Style/StringLiteralsInInterpolation: Description: Checks if uses of quotes inside expressions in interpolated strings match the configured preference. Enabled: true EnforcedStyle: single_quotes SupportedStyles: - single_quotes - double_quotes Style/TrailingCommaInLiteral: Description: Checks for trailing comma in parameter lists and literals. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas Enabled: false EnforcedStyleForMultiline: no_comma SupportedStyles: - comma - no_comma Style/TrailingCommaInArguments: Description: Checks for trailing comma in parameter lists and literals. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas Enabled: false EnforcedStyleForMultiline: no_comma SupportedStyles: - comma - no_comma Style/TrivialAccessors: Description: Prefer attr_* methods to trivial readers/writers. StyleGuide: https://github.com/bbatsov/ruby-style-guide#attr_family Enabled: false ExactNameMatch: true AllowPredicates: false AllowDSLWriters: false IgnoreClassMethods: false Whitelist: - to_ary - to_a - to_c - to_enum - to_h - to_hash - to_i - to_int - to_io - to_open - to_path - to_proc - to_r - to_regexp - to_str - to_s - to_sym Style/ZeroLengthPredicate: Exclude: - lib/memfs/file.rb Metrics/AbcSize: Description: A calculated magnitude based on number of assignments, branches, and conditions. Enabled: false Max: 15 Metrics/ClassLength: Description: Avoid classes longer than 100 lines of code. Enabled: false CountComments: false Max: 100 Metrics/ModuleLength: CountComments: false Max: 100 Description: Avoid modules longer than 100 lines of code. Enabled: false Metrics/CyclomaticComplexity: Description: A complexity metric that is strongly correlated to the number of test cases needed to validate a method. Enabled: false Max: 6 Metrics/MethodLength: Description: Avoid methods longer than 10 lines of code. StyleGuide: https://github.com/bbatsov/ruby-style-guide#short-methods Enabled: false CountComments: false Max: 10 Metrics/ParameterLists: Description: Avoid parameter lists longer than three or four parameters. StyleGuide: https://github.com/bbatsov/ruby-style-guide#too-many-params Enabled: false Max: 5 CountKeywordArgs: true Metrics/PerceivedComplexity: Description: A complexity metric geared towards measuring complexity for a human reader. Enabled: false Max: 7 Lint/AssignmentInCondition: Description: Don't use assignment in conditions. StyleGuide: https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition Enabled: false AllowSafeAssignment: true Style/InlineComment: Description: Avoid inline comments. Enabled: false Style/AccessorMethodName: Description: Check the naming of accessor methods for get_/set_. Enabled: false Style/Alias: Description: Use alias_method instead of alias. StyleGuide: https://github.com/bbatsov/ruby-style-guide#alias-method Enabled: false Style/Documentation: Description: Document classes and non-namespace modules. Enabled: false Style/DoubleNegation: Description: Checks for uses of double negation (!!). StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-bang-bang Enabled: false Style/EachWithObject: Description: Prefer `each_with_object` over `inject` or `reduce`. Enabled: false Style/EmptyLiteral: Description: Prefer literals to Array.new/Hash.new/String.new. StyleGuide: https://github.com/bbatsov/ruby-style-guide#literal-array-hash Enabled: false Style/ModuleFunction: Description: Checks for usage of `extend self` in modules. StyleGuide: https://github.com/bbatsov/ruby-style-guide#module-function Enabled: false Style/MultilineOperationIndentation: Description: Checks indentation of binary operations that span more than one line. Enabled: false EnforcedStyle: aligned SupportedStyles: - aligned - indented Style/OneLineConditional: Description: Favor the ternary operator(?:) over if/then/else/end constructs. StyleGuide: https://github.com/bbatsov/ruby-style-guide#ternary-operator Enabled: false Style/PerlBackrefs: Description: Avoid Perl-style regex back references. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers Enabled: false Style/Send: Description: Prefer `Object#__send__` or `Object#public_send` to `send`, as `send` may overlap with existing methods. StyleGuide: https://github.com/bbatsov/ruby-style-guide#prefer-public-send Enabled: false Style/SpecialGlobalVars: Description: Avoid Perl-style global variables. StyleGuide: https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms Enabled: false Style/VariableInterpolation: Description: Don't interpolate global, instance and class variables directly in strings. StyleGuide: https://github.com/bbatsov/ruby-style-guide#curlies-interpolate Enabled: false Style/WhenThen: Description: Use when x then ... for one-line cases. StyleGuide: https://github.com/bbatsov/ruby-style-guide#one-line-cases Enabled: false Lint/EachWithObjectArgument: Description: Check for immutable argument given to each_with_object. Enabled: true Lint/HandleExceptions: Description: Don't suppress exception. StyleGuide: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions Enabled: false Lint/LiteralInCondition: Description: Checks of literals used in conditions. Enabled: false Lint/LiteralInInterpolation: Description: Checks for literals used in interpolation. Enabled: false