From aa8f16e583edd92c9898f6fc3562cf78a7b45c24 Mon Sep 17 00:00:00 2001 From: Dana Sherson Date: Sun, 19 Nov 2023 06:39:48 +1300 Subject: [PATCH] Use ftype --- .github/workflows/ci.yml | 2 +- lib/path_list/candidate.rb | 45 ++++++++------------ lib/path_list/candidate/jruby_windows_fix.rb | 26 +++++++++++ spec/candidate_spec.rb | 27 ++++++------ spec/path_list_spec.rb | 4 +- 5 files changed, 59 insertions(+), 45 deletions(-) create mode 100644 lib/path_list/candidate/jruby_windows_fix.rb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d6ff45f..3539cac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ jobs: strategy: fail-fast: false matrix: - ruby: [2.7, '3.0', 3.1, 3.2, 3.3, ruby-head, jruby-9.4, jruby-head] + ruby: [2.7, '3.0', 3.1, 3.2, ruby-head, jruby-9.4, jruby-head] platform: [ubuntu, windows, macos] continue-on-error: ${{ endsWith(matrix.ruby, 'head') }} runs-on: ${{matrix.platform}}-latest diff --git a/lib/path_list/candidate.rb b/lib/path_list/candidate.rb index bf1588e..ae796ae 100644 --- a/lib/path_list/candidate.rb +++ b/lib/path_list/candidate.rb @@ -4,6 +4,13 @@ class PathList # @api private # The object that gets passed to all {PathList::Matcher} subclasses #match class Candidate + # :nocov: + if ::RUBY_PLATFORM == 'java' && ::RbConfig::CONFIG['host_os'].match?(/mswin|mingw/) + Autoloader.autoload(self) + include JrubyWindowsFix + end + # :nocov: + attr_reader :full_path # @param full_path [String] resolved absolute path @@ -18,6 +25,7 @@ def initialize(full_path, directory = nil, shebang = nil) @child_candidates = nil @children = nil + @ftype = nil end # @return [String] full path downcased @@ -54,33 +62,16 @@ def children end end - # :nocov: - if ::RUBY_PLATFORM == 'java' && ::RbConfig::CONFIG['host_os'].match?(/mswin|mingw/) - # @return [Boolean] whether this path is a directory (false for symlinks to directories) - def directory? - return @directory unless @directory.nil? - - @directory = if ::File.symlink?(@full_path) - warn 'Symlink lstat' - warn lstat.inspect - false - else - lstat&.directory? || false - end - end - # :nocov: - else - # @return [Boolean] whether this path is a directory (false for symlinks to directories) - def directory? - return @directory unless @directory.nil? + # @return [Boolean] whether this path is a directory (false for symlinks to directories) + def directory? + return @directory unless @directory.nil? - @directory = lstat&.directory? || false - end + @directory = ftype == 'directory' end # @return [Boolean] whether this path exists def exists? - lstat ? true : false + ftype != 'error' end alias_method :original_inspect, :inspect # leftovers:keep @@ -112,7 +103,7 @@ def shebang '' end rescue ::IOError, ::SystemCallError - @lstat ||= nil + @ftype ||= 'error' '' ensure file&.close @@ -121,12 +112,12 @@ def shebang private - def lstat - return @lstat if defined?(@lstat) + def ftype + return @ftype if @ftype - @lstat = ::File.lstat(@full_path) + @ftype = ::File.ftype(@full_path) rescue ::SystemCallError - @lstat = nil + @ftype = 'error' end end end diff --git a/lib/path_list/candidate/jruby_windows_fix.rb b/lib/path_list/candidate/jruby_windows_fix.rb new file mode 100644 index 0000000..a4a4e02 --- /dev/null +++ b/lib/path_list/candidate/jruby_windows_fix.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# :nocov: +class PathList + class Candidate + # @api private + module JrubyWindowsFix + private + + # https://github.com/jruby/jruby/issues/8018 + # ftype follows symlinks on jruby on windows. it shouldn't. + def ftype + return @ftype if @ftype + + @ftype = if ::File.symlink?(@full_path) + 'link' + else + ::File.ftype(@full_path) + end + rescue ::SystemCallError + @ftype = 'error' + end + end + end +end +# :nocov: diff --git a/spec/candidate_spec.rb b/spec/candidate_spec.rb index 98c9d56..d98b601 100644 --- a/spec/candidate_spec.rb +++ b/spec/candidate_spec.rb @@ -25,7 +25,7 @@ end describe '#parent' do - before { allow(File).to receive_messages(exist?: nil, lstat: nil, directory?: nil) } + before { allow(File).to receive_messages(exist?: nil, ftype: nil, directory?: nil) } it 'returns a candidate for the parent with preset directory value' do expect(candidate.parent).to be_like described_class.new('/path/from/root', true) @@ -33,7 +33,7 @@ directory?: true ) expect(File).not_to have_received(:directory?) - expect(File).not_to have_received(:lstat) + expect(File).not_to have_received(:ftype) end context 'when the path is /' do @@ -54,12 +54,12 @@ before { create_file_list 'foo' } it 'is memoized when true' do - allow(File).to receive(:lstat).and_call_original + allow(File).to receive(:ftype).and_call_original expect(candidate.exists?).to be true - expect(File).to have_received(:lstat).once + expect(File).to have_received(:ftype).once expect(candidate.exists?).to be true - expect(File).to have_received(:lstat).once + expect(File).to have_received(:ftype).once end end @@ -67,22 +67,22 @@ let(:full_path) { './foo' } it 'is memoized when false' do - allow(File).to receive(:lstat).and_call_original + allow(File).to receive(:ftype).and_call_original expect(candidate.exists?).to be false - expect(File).to have_received(:lstat).with('./foo').once + expect(File).to have_received(:ftype).with('./foo').once expect(candidate.exists?).to be false - expect(File).to have_received(:lstat).with('./foo').once + expect(File).to have_received(:ftype).with('./foo').once end it 'is false when there is an error' do - allow(File).to receive(:lstat).and_call_original - allow(File).to receive(:lstat).with(full_path).and_raise(Errno::EACCES) + allow(File).to receive(:ftype).and_call_original + allow(File).to receive(:ftype).with(full_path).and_raise(Errno::EACCES) expect(candidate.exists?).to be false - expect(File).to have_received(:lstat).with('./foo').once + expect(File).to have_received(:ftype).with('./foo').once expect(candidate.exists?).to be false - expect(File).to have_received(:lstat).with('./foo').once + expect(File).to have_received(:ftype).with('./foo').once end end end @@ -139,9 +139,6 @@ create_symlink('foo' => 'foo_target') candidate = described_class.new(File.expand_path('foo')) - expect(File.symlink?('./foo')).to be true - expect(File.stat('./foo')).to have_attributes(directory?: false, symlink?: true) - expect(candidate.send(:lstat)).to have_attributes(directory?: false, symlink?: true) expect(candidate).not_to be_directory end end diff --git a/spec/path_list_spec.rb b/spec/path_list_spec.rb index b1f6252..5e15bba 100644 --- a/spec/path_list_spec.rb +++ b/spec/path_list_spec.rb @@ -40,8 +40,8 @@ it 'copes with being given fs root' do whatever_file_we_get = subject.each('/').first expect(whatever_file_we_get).not_to start_with('/') - # use lstat because it could be a symlink to nowhere and File.exist? will be sad - expect(File.lstat("/#{whatever_file_we_get}")).to be_a File::Stat + # use symlink? because it could be a symlink to nowhere and File.exist? would return false + expect { File.symlink?("/#{whatever_file_we_get}") || File.exist?("/#{whatever_file_we_get}") }.not_to raise_error end it 'copes with being given nonsense root' do