diff --git a/spec/bundled_gems_spec.rb b/spec/bundled_gems_spec.rb index 45ababa9ed6588..dc0e7dde6c7bfc 100644 --- a/spec/bundled_gems_spec.rb +++ b/spec/bundled_gems_spec.rb @@ -388,6 +388,12 @@ def my end context "with bundle environment" do + # Windows has no executable bit or shebang dispatch, so running the + # script directly is rejected by bundler as "not executable". Invoke it + # through ruby there. What matters here is force_activate's behavior under + # the bundle environment, not shebang execution (covered by another spec). + let(:exec_command) { Gem.win_platform? ? "exec ruby ./script.rb" : "exec ./script.rb" } + before do code = <<-RUBY #!/usr/bin/env ruby @@ -400,13 +406,13 @@ def my it "lockfile is available" do bundle "install" - bundle "exec ./script.rb" + bundle exec_command expect(err).to include("gem install csv") end it "lockfile is not available" do - bundle "exec ./script.rb" + bundle exec_command expect(err).to include("gem install csv") end diff --git a/spec/ruby/core/file/atime_spec.rb b/spec/ruby/core/file/atime_spec.rb index 5c6c110eec43f3..af9393bef496b0 100644 --- a/spec/ruby/core/file/atime_spec.rb +++ b/spec/ruby/core/file/atime_spec.rb @@ -19,7 +19,7 @@ unless ENV.key?('TRAVIS') # https://bugs.ruby-lang.org/issues/17926 ## NOTE also that some Linux systems disable atime (e.g. via mount params) for better filesystem speed. it "returns the last access time for the named file with microseconds" do - supports_subseconds = Integer(`stat -c%x '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10) + supports_subseconds = Integer(`stat -c%x #{__FILE__}`[/\.(\d{1,6})/, 1], 10) if supports_subseconds != 0 expected_time = Time.at(Time.now.to_i + 0.123456) File.utime expected_time, 0, @file diff --git a/spec/ruby/core/file/ctime_spec.rb b/spec/ruby/core/file/ctime_spec.rb index cf37d1f4eeca96..25058fe6820a24 100644 --- a/spec/ruby/core/file/ctime_spec.rb +++ b/spec/ruby/core/file/ctime_spec.rb @@ -16,7 +16,7 @@ platform_is :linux, :windows do it "returns the change time for the named file (the time at which directory information about the file was changed, not the file itself) with microseconds." do - supports_subseconds = Integer(`stat -c%z '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10) + supports_subseconds = Integer(`stat -c%z #{__FILE__}`[/\.(\d{1,6})/, 1], 10) if supports_subseconds != 0 File.ctime(__FILE__).usec.should > 0 else diff --git a/spec/ruby/core/file/mtime_spec.rb b/spec/ruby/core/file/mtime_spec.rb index d83725e25d9ecc..2e28695d977ab1 100644 --- a/spec/ruby/core/file/mtime_spec.rb +++ b/spec/ruby/core/file/mtime_spec.rb @@ -18,7 +18,7 @@ platform_is :linux, :windows do unless ENV.key?('TRAVIS') # https://bugs.ruby-lang.org/issues/17926 it "returns the modification Time of the file with microseconds" do - supports_subseconds = Integer(`stat -c%y '#{__FILE__}'`[/\.(\d{1,6})/, 1], 10) + supports_subseconds = Integer(`stat -c%y #{__FILE__}`[/\.(\d{1,6})/, 1], 10) if supports_subseconds != 0 expected_time = Time.at(Time.now.to_i + 0.123456) File.utime 0, expected_time, @filename diff --git a/test/ruby/test_file_exhaustive.rb b/test/ruby/test_file_exhaustive.rb index 6e7973897c7960..0040e8d9f00a5e 100644 --- a/test/ruby/test_file_exhaustive.rb +++ b/test/ruby/test_file_exhaustive.rb @@ -715,6 +715,28 @@ def test_symlink assert_raise(Errno::EEXIST) { File.symlink(utf8_file, utf8_file) } end + def test_symlink_to_relative_directory + # A relative target is interpreted relative to the link's directory, not the + # current directory. A relative target pointing at a directory must produce + # a directory symlink even when the current directory differs from the link's + # directory; otherwise Dir operations on the link fail (Windows). + Dir.mktmpdir(__method__.to_s) do |tmpdir| + Dir.chdir(tmpdir) do + Dir.mkdir("subdir") + Dir.mkdir(File.join("subdir", "target")) + link = File.join("subdir", "link") + begin + File.symlink("target", link) + rescue NotImplementedError, Errno::EACCES, Errno::EPERM => e + omit e.message + end + assert_file.symlink?(link) + assert_file.directory?(link) + assert(Dir.exist?(link), "relative directory symlink should be a directory") + end + end + end + def test_utime t = Time.local(2000) File.utime(t + 1, t + 2, zerofile) @@ -805,10 +827,11 @@ def test_readlink_junction def test_realpath_mount_point vol = IO.popen(["mountvol", DRIVE, "/l"], &:read).strip Dir.mkdir(mnt = File.join(@dir, mntpnt = "mntpnt")) - system("mountvol", mntpnt, vol, chdir: @dir) + err = IO.popen(%W"mountvol #{mntpnt} #{vol}", chdir: @dir, err: %i[child out], &:read) + omit err unless $?.success? assert_equal(mnt, File.realpath(mnt)) ensure - system("mountvol", mntpnt, "/d", chdir: @dir) + system("mountvol", mntpnt, "/d", chdir: @dir, out: IO::NULL, err: IO::NULL) end end diff --git a/test/ruby/test_io_m17n.rb b/test/ruby/test_io_m17n.rb index 83d4fb0c7b525e..1736d01f78e3fd 100644 --- a/test/ruby/test_io_m17n.rb +++ b/test/ruby/test_io_m17n.rb @@ -404,8 +404,17 @@ def test_dup_undef end def test_stdin - assert_equal(Encoding.default_external, STDIN.external_encoding) - assert_equal(nil, STDIN.internal_encoding) + encoding = Encoding.default_external + internal = nil + if /mswin|mingw/ =~ RUBY_PLATFORM and STDIN.tty? + # Interactive console input on Windows is read in the locale (console + # code page) encoding and transcoded to the default external encoding. + encoding = Encoding.find("locale") + internal = Encoding.default_internal || Encoding.default_external + internal = nil if internal == encoding + end + assert_equal(encoding, STDIN.external_encoding) + assert_equal(internal, STDIN.internal_encoding) end def test_stdout diff --git a/tool/outdate-bundled-gems.rb b/tool/outdate-bundled-gems.rb index b272c448c6608b..ec8762f5e0138c 100755 --- a/tool/outdate-bundled-gems.rb +++ b/tool/outdate-bundled-gems.rb @@ -101,7 +101,7 @@ def default_gem?(spec) (@defaults ||= {}).fetch(spec) do File.open(prefixed(spec)) do |f| if /^# default: (\S+) (\d+\.\d+)/ =~ f.gets("") - File.mtime(prefixed($1)) <= Time.at(Rational($2)) + File.mtime($1) <= Time.at(Rational($2)) else false end diff --git a/tool/test-bundled-gems.rb b/tool/test-bundled-gems.rb index b603cc09d7a530..706f665b95de51 100644 --- a/tool/test-bundled-gems.rb +++ b/tool/test-bundled-gems.rb @@ -16,6 +16,35 @@ 'irb', 'csv', ] : [] + +# minitest's assertion tests compare against unified diff output produced by +# the `diff` command, so they fail spuriously when it is not available. +diff_available = ENV["PATH"].to_s.split(File::PATH_SEPARATOR).any? do |dir| + next false if dir.empty? + exe = File.join(dir, "diff") + File.executable?(exe) || (/mswin|mingw/ =~ RUBY_PLATFORM && File.file?("#{exe}.exe")) +end +DEFAULT_ALLOWED_FAILURES << 'minitest' unless diff_available + +# rake's TestBacktraceSuppression#test_system_dir_suppressed expects rake to +# suppress RbConfig's rubylibprefix from backtraces. In an uninstalled +# out-of-tree build it is a POSIX "/usr"-style prefix that File.expand_path +# turns into a drive-prefixed path on Windows, which no longer matches rake's +# suppression pattern, so the test fails. +if /mswin|mingw/ =~ RUBY_PLATFORM && RbConfig::CONFIG["rubylibprefix"] !~ /\A[a-zA-Z]:/ + DEFAULT_ALLOWED_FAILURES << 'rake' +end + +# rbs's stdlib Resolv tests need to resolve "localhost"; allow its failures on +# hosts where the Resolv library cannot resolve it. +begin + require 'resolv' + Resolv.getaddress('localhost') +rescue LoadError +rescue Resolv::ResolvError + DEFAULT_ALLOWED_FAILURES << 'rbs' +end + allowed_failures = ENV['TEST_BUNDLED_GEMS_ALLOW_FAILURES'] || '' allowed_failures = allowed_failures.split(',').concat(DEFAULT_ALLOWED_FAILURES).uniq.reject(&:empty?) diff --git a/win32/win32.c b/win32/win32.c index e3a3df71f6ce34..00a87ff226d60c 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -5284,8 +5284,33 @@ w32_symlink(UINT cp, const char *src, const char *link) MultiByteToWideChar(cp, 0, src, -1, wsrc, len1); MultiByteToWideChar(cp, 0, link, -1, wlink, len2); translate_wchar(wsrc, L'/', L'\\'); + translate_wchar(wlink, L'/', L'\\'); - atts = GetFileAttributesW(wsrc); + /* A relative target is interpreted relative to the directory of the link, + not the current directory. Resolve it there to decide whether to create + a directory symlink; otherwise a relative target pointing at a directory + would wrongly become a file symlink when the current directory differs + from the link's directory. */ + { + WCHAR *sep; + int independent = + (((wsrc[0] >= L'A' && wsrc[0] <= L'Z') || + (wsrc[0] >= L'a' && wsrc[0] <= L'z')) && wsrc[1] == L':') || + wsrc[0] == L'\\'; + if (!independent && (sep = wcsrchr(wlink, L'\\')) != NULL) { + VALUE buf2; + size_t dirlen = sep - wlink + 1; + size_t srclen = wcslen(wsrc) + 1; + WCHAR *fullsrc = ALLOCV_N(WCHAR, buf2, dirlen + srclen); + MEMCPY(fullsrc, wlink, WCHAR, dirlen); + MEMCPY(fullsrc + dirlen, wsrc, WCHAR, srclen); + atts = GetFileAttributesW(fullsrc); + ALLOCV_END(buf2); + } + else { + atts = GetFileAttributesW(wsrc); + } + } if (atts != -1 && atts & FILE_ATTRIBUTE_DIRECTORY) flag = SYMBOLIC_LINK_FLAG_DIRECTORY; ret = CreateSymbolicLinkW(wlink, wsrc, flag |= create_flag);