forked from crystal-lang/crystal
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix
Process.find_executable
once and for all (crystal-lang#9365)
* Fix `Process.find_executable` once and for all Make it work on Windows. Make it actually check that the found path is an *executable* *file*. Fix a lot of edge cases. Add specs and also verify that these expectations exactly match what Process.new would do. * fixup! Fix `Process.find_executable` once and for all * Expand the comment in the manual spec * Factor out a function listing possibilities of a path to check * Address review comments + refactor * Simplify compilation of test executables, doesn't have to be parallel * Add spec involving `..` * Resolve review comments
- Loading branch information
Showing
4 changed files
with
242 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
# Verifies that find_executable's specs match the behavior of Process.run. | ||
# This doesn't actually test find_executable, only takes all the test cases | ||
# directly from spec/std/process/find_executable_spec.cr and checks that | ||
# *they* match what the OS actually does when finding an executable for the | ||
# purpose of running it. | ||
|
||
require "spec" | ||
require "digest/sha1" | ||
require "../support/env" | ||
require "../support/tempfile" | ||
require "../std/process/find_executable_spec" | ||
|
||
describe "Process.run" do | ||
test_dir = Path[SPEC_TEMPFILE_PATH] / "manual_find_executable" | ||
base_dir = Path[test_dir] / "base" | ||
path_dir = Path[test_dir] / "path" | ||
|
||
around_all do |all| | ||
Dir.mkdir_p(test_dir) | ||
|
||
exe_names, non_exe_names = FIND_EXECUTABLE_TEST_FILES | ||
exe_names.each do |name| | ||
src_fn = test_dir / "self_printer.cr" | ||
exe_fn = test_dir / "self_printer.exe" | ||
File.write(src_fn, "print #{name.inspect}") | ||
Process.run("bin/crystal", ["build", "-o", exe_fn.to_s, src_fn.to_s]) | ||
Dir.mkdir_p((base_dir / name).parent) | ||
File.rename(exe_fn, base_dir / name) | ||
end | ||
non_exe_names.each do |name| | ||
File.write(base_dir / name, "") | ||
end | ||
|
||
with_env "PATH": {ENV["PATH"], path_dir}.join(Process::PATH_DELIMITER) do | ||
Dir.cd(base_dir) do | ||
all.run | ||
end | ||
end | ||
|
||
FileUtils.rm_r(test_dir.to_s) | ||
end | ||
|
||
find_executable_test_cases(base_dir).each do |(command, exp)| | ||
if exp | ||
it "runs '#{command}' as '#{exp}'" do | ||
output = Process.run command, &.output.gets_to_end | ||
$?.success?.should be_true | ||
output.should eq exp | ||
end | ||
else | ||
it "fails to run '#{command}'" do | ||
expect_raises IO::Error do | ||
Process.run(command) | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
require "spec" | ||
require "../../support/tempfile" | ||
|
||
{% if flag?(:win32) %} | ||
FIND_EXECUTABLE_TEST_FILES = { | ||
[ | ||
"inbase.exe", | ||
"not_exe", | ||
".exe", | ||
"inboth.exe", | ||
|
||
"inbasebat.bat", | ||
"inbase.foo.exe", | ||
".inbase.exe", | ||
|
||
"sub/insub.exe", | ||
"sub/not_exe", | ||
"sub/.exe", | ||
|
||
"../path/inpath.exe", | ||
"../path/not_exe", | ||
"../path/.exe", | ||
"../path/inboth.exe", | ||
], [] of String, | ||
} | ||
|
||
def find_executable_test_cases(pwd) | ||
pwd_nodrive = "\\#{pwd.relative_to(pwd.anchor.not_nil!)}" | ||
{ | ||
"inbase.exe" => "inbase.exe", | ||
"inbase" => "inbase.exe", | ||
"sub\\insub.exe" => "sub/insub.exe", | ||
"sub/insub" => "sub/insub.exe", | ||
"inpath.exe" => "../path/inpath.exe", | ||
"inpath" => "../path/inpath.exe", | ||
"sub/.exe" => "sub/.exe", | ||
"sub\\" => "sub/.exe", | ||
"sub/" => "sub/.exe", | ||
".exe" => ".exe", | ||
"not_exe" => nil, | ||
"sub\\not_exe" => nil, | ||
"inbasebat" => nil, | ||
"inbase.foo.exe" => "inbase.foo.exe", | ||
"inbase.foo" => nil, | ||
".inbase.exe" => ".inbase.exe", | ||
".inbase" => nil, | ||
"" => nil, | ||
"." => nil, | ||
"inboth.exe" => "inboth.exe", | ||
"inboth" => "inboth.exe", | ||
"./inbase" => "inbase.exe", | ||
"../base/inbase" => "inbase.exe", | ||
"./inpath" => nil, | ||
"sub" => nil, | ||
"#{pwd}\\sub" => nil, | ||
"#{pwd}\\sub\\" => nil, | ||
# 'C:\Temp\base\inbase', 'C:\Temp\base\.exe', 'C:\Temp\base\' | ||
"#{pwd}\\inbase" => "inbase.exe", | ||
"#{pwd}\\.exe" => ".exe", | ||
"#{pwd}\\" => nil, | ||
# 'C:inbase', 'C:.exe', 'C:' | ||
"#{pwd.drive}inbase" => "inbase.exe", | ||
"#{pwd.drive}.exe" => ".exe", | ||
"#{pwd.drive}" => nil, | ||
"#{pwd.drive}sub\\" => nil, | ||
# '\Temp\base\inbase', '\Temp\base\.exe', '\Temp\base\' | ||
"#{pwd_nodrive}\\inbase" => "inbase.exe", | ||
"#{pwd_nodrive}\\.exe" => ".exe", | ||
"#{pwd_nodrive}\\" => nil, | ||
} | ||
end | ||
{% else %} | ||
FIND_EXECUTABLE_TEST_FILES = { | ||
[ | ||
"inbase", | ||
"sub/insub", | ||
"../path/inpath", | ||
], [ | ||
"not_exe", | ||
"sub/not_exe", | ||
"../path/not_exe", | ||
], | ||
} | ||
|
||
def find_executable_test_cases(pwd) | ||
{ | ||
"./inbase" => "inbase", | ||
"../base/inbase" => "inbase", | ||
"inbase" => nil, | ||
"sub/insub" => "sub/insub", | ||
"inpath" => "../path/inpath", | ||
"./inpath" => nil, | ||
"inbase/" => nil, | ||
"sub/insub/" => nil, | ||
"./not_exe" => nil, | ||
"not_exe" => nil, | ||
"sub/not_exe" => nil, | ||
"" => nil, | ||
"." => nil, | ||
"#{pwd}/inbase" => "inbase", | ||
"#{pwd}/inbase/" => nil, | ||
"#{pwd}/sub" => nil, | ||
"./sub" => nil, | ||
"sub" => nil, | ||
} | ||
end | ||
{% end %} | ||
|
||
describe "Process.find_executable" do | ||
test_dir = Path[SPEC_TEMPFILE_PATH] / "find_executable" | ||
base_dir = Path[test_dir] / "base" | ||
path_dir = Path[test_dir] / "path" | ||
|
||
around_all do |all| | ||
exe_names, non_exe_names = FIND_EXECUTABLE_TEST_FILES | ||
(exe_names + non_exe_names).each do |name| | ||
Dir.mkdir_p((base_dir / name).parent) | ||
File.write(base_dir / name, "") | ||
end | ||
exe_names.each do |name| | ||
File.chmod(base_dir / name, 0o755) | ||
end | ||
|
||
all.run | ||
|
||
FileUtils.rm_r(test_dir.to_s) | ||
end | ||
|
||
find_executable_test_cases(base_dir).each do |(command, exp)| | ||
if exp | ||
exp_path = File.expand_path(exp, base_dir) | ||
it "finds '#{command}' as '#{exp}'" do | ||
Process.find_executable(command, path: path_dir.to_s, pwd: base_dir).should eq exp_path | ||
end | ||
else | ||
it "fails to find '#{command}'" do | ||
Process.find_executable(command, path: path_dir.to_s, pwd: base_dir).should be_nil | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters