diff --git a/spec/std/dir_spec.cr b/spec/std/dir_spec.cr index 3772ff073b17..c521c2faa4a0 100644 --- a/spec/std/dir_spec.cr +++ b/spec/std/dir_spec.cr @@ -1,4 +1,5 @@ require "./spec_helper" +require "../support/env" private def unset_tempdir {% if flag?(:windows) %} @@ -507,8 +508,41 @@ describe "Dir" do end end - it ".current" do - Dir.current.should eq(`#{{{ flag?(:win32) ? "cmd /c cd" : "pwd" }}}`.chomp) + describe ".current" do + it "matches shell" do + Dir.current.should eq(`#{{{ flag?(:win32) ? "cmd /c cd" : "pwd" }}}`.chomp) + end + + # Skip spec on Windows due to weak support for symlinks and $PWD. + {% unless flag?(:win32) %} + it "follows $PWD" do + with_tempfile "current-pwd" do |path| + Dir.mkdir_p path + # Resolve any symbolic links in path caused by tmpdir being a link. + # For example on macOS, /tmp is a symlink to /private/tmp. + path = File.real_path(path) + + target_path = File.join(path, "target") + link_path = File.join(path, "link") + Dir.mkdir_p target_path + File.symlink(target_path, link_path) + + Dir.cd(link_path) do + with_env({"PWD" => nil}) do + Dir.current.should eq target_path + end + + with_env({"PWD" => link_path}) do + Dir.current.should eq link_path + end + + with_env({"PWD" => "/some/other/path"}) do + Dir.current.should eq target_path + end + end + end + end + {% end %} end describe ".tempdir" do diff --git a/src/crystal/system/unix/dir.cr b/src/crystal/system/unix/dir.cr index 79d5202dc9a5..e2faa53b4482 100644 --- a/src/crystal/system/unix/dir.cr +++ b/src/crystal/system/unix/dir.cr @@ -42,6 +42,15 @@ module Crystal::System::Dir end def self.current : String + # If $PWD is set and it matches the current path, use that. + # This helps telling apart symlinked paths. + if (pwd = ENV["PWD"]?) && pwd.starts_with?("/") && + (pwd_info = ::Crystal::System::File.info?(pwd, follow_symlinks: true)) && + (dot_info = ::Crystal::System::File.info?(".", follow_symlinks: true)) && + pwd_info.same_file?(dot_info) + return pwd + end + unless dir = LibC.getcwd(nil, 0) raise ::File::Error.from_errno("Error getting current directory", file: "./") end diff --git a/src/dir.cr b/src/dir.cr index 62c9e3c83b85..e69b147baf9d 100644 --- a/src/dir.cr +++ b/src/dir.cr @@ -176,7 +176,12 @@ class Dir @closed = true end - # Returns the current working directory. + # Returns an absolute path to the current working directory. + # + # The result is similar to the shell commands `pwd` (POSIX) and `cd` (Windows). + # + # On POSIX systems, it respects the environment value `$PWD` if available and + # if it points to the current working directory. def self.current : String Crystal::System::Dir.current end