# frozen_string_literal: true require 'spec_helper' require 'image_optim/cache_path' require 'tempfile' describe ImageOptim::CachePath do before do stub_const('Path', ImageOptim::Path) stub_const('CachePath', ImageOptim::CachePath) end describe '#replace' do let(:src_dir){ Path.temp_dir } let(:src){ CachePath.temp_file_path(nil, src_dir) } let(:dst){ Path.temp_file_path } shared_examples 'replaces file' do it 'moves data to destination' do src.write('src') src.replace(dst) expect(dst.read).to eq('src') end it 'does not remove original file' do src.replace(dst) expect(src).to exist end it 'preserves attributes of destination file', skip: SkipConditions[:any_file_mode_allowed] do mode = 0o666 dst.chmod(mode) src.replace(dst) got = dst.stat.mode & 0o777 expect(got).to eq(mode), format('expected %04o, got %04o', mode, got) end it 'does not preserve mtime of destination file' do time = src.mtime - 1000 dst.utime(time, time) time = dst.mtime src.replace(dst) expect(dst.mtime).to_not eq(time) end it 'changes inode of destination', skip: SkipConditions[:inodes_support] do expect{ src.replace(dst) }.to change{ dst.stat.ino } end it 'is using temporary file with .tmp extension' do expect(src).to receive(:copy).with(having_attributes(extname: '.tmp')).at_least(:once) src.replace(dst) end end context 'when src and dst are on same device' do before do allow_any_instance_of(File::Stat).to receive(:dev).and_return(0) end include_examples 'replaces file' end context 'when src and dst are on different devices' do before do allow_any_instance_of(File::Stat).to receive(:dev, &:__id__) end include_examples 'replaces file' end context 'when src and dst are on same device, but rename causes Errno::EXDEV' do before do allow_any_instance_of(File::Stat).to receive(:dev).and_return(0) allow(described_class).to receive(:temp_file_path).and_call_original expect(described_class).to receive(:temp_file_path). with([dst.basename.to_s, '.tmp'], src.dirname). and_wrap_original do |m, *args, &block| m.call(*args) do |tmp| expect(tmp).to receive(:rename).with(dst.to_s).and_raise(Errno::EXDEV) block.call(tmp) end end end include_examples 'replaces file' end end end