From 0230cf96ce2fb15eb7c9538b14d75f7b50f3e505 Mon Sep 17 00:00:00 2001 From: James Stocks Date: Fri, 20 Oct 2017 11:39:09 +0100 Subject: [PATCH 1/4] Use CreateProcessW to spawn a process on Windows Support wide characters on Windows. This allows for non-ASCII characters in the command line. --- lib/childprocess/windows/lib.rb | 4 ++-- lib/childprocess/windows/process_builder.rb | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/childprocess/windows/lib.rb b/lib/childprocess/windows/lib.rb index d275f3b..fd7f478 100644 --- a/lib/childprocess/windows/lib.rb +++ b/lib/childprocess/windows/lib.rb @@ -55,9 +55,9 @@ module Lib # ); # - attach_function :create_process, :CreateProcessA, [ - :pointer, + attach_function :create_process, :CreateProcessW, [ :pointer, + :buffer_inout, :pointer, :pointer, :bool, diff --git a/lib/childprocess/windows/process_builder.rb b/lib/childprocess/windows/process_builder.rb index 1b30b0a..1e849d7 100644 --- a/lib/childprocess/windows/process_builder.rb +++ b/lib/childprocess/windows/process_builder.rb @@ -39,9 +39,14 @@ def start private + def to_wide_string(str) + newstr = str + "\0".encode(str.encoding) + newstr.encode!('UTF-16LE') + end + def create_command_pointer - string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join ' ' - @cmd_ptr = FFI::MemoryPointer.from_string string + string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join(' ') + @cmd_ptr = to_wide_string(string) end def create_environment_pointer @@ -59,15 +64,12 @@ def create_environment_pointer strings << "#{key}=#{val}\0" end - strings << "\0" # terminate the env block - env_str = strings.join - - @env_ptr = FFI::MemoryPointer.new(:long, env_str.bytesize) - @env_ptr.put_bytes 0, env_str, 0, env_str.bytesize + env_str = to_wide_string(strings.join) + @env_ptr = FFI::MemoryPointer.from_string(env_str) end def create_cwd_pointer - @cwd_ptr = FFI::MemoryPointer.from_string(@cwd || Dir.pwd) + @cwd_ptr = FFI::MemoryPointer.from_string(to_wide_string(@cwd || Dir.pwd)) end def create_process @@ -98,6 +100,7 @@ def process_info end def setup_flags + @flags |= CREATE_UNICODE_ENVIRONMENT @flags |= DETACHED_PROCESS if @detach @flags |= CREATE_BREAKAWAY_FROM_JOB if @leader end From aa93f424352ac76ad1fe8501a5d2a2bb9e6c3c08 Mon Sep 17 00:00:00 2001 From: James Stocks Date: Tue, 24 Oct 2017 14:58:38 +0100 Subject: [PATCH 2/4] Spec test for unicode character in process environment --- spec/childprocess_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/childprocess_spec.rb b/spec/childprocess_spec.rb index b382122..65c287d 100644 --- a/spec/childprocess_spec.rb +++ b/spec/childprocess_spec.rb @@ -107,6 +107,19 @@ expect(child_env['CHILD_ONLY']).to eql '1' end end + + it 'allows unicode characters in the environment' do + Tempfile.open("env-spec") do |file| + process = write_env(file.path) + process.environment['FOO'] = 'baör' + process.start + process.wait + + child_env = eval rewind_and_read(file) + + expect(child_env['FOO'].force_encoding('UTF-8')).to eql 'baör' + end + end it "inherits the parent's env vars also when some are overridden" do Tempfile.open("env-spec") do |file| From 83c2a88104193c8ec5e6a53b4284600fb10d6227 Mon Sep 17 00:00:00 2001 From: James Stocks Date: Mon, 30 Oct 2017 12:09:24 +0000 Subject: [PATCH 3/4] Skip character encoding test on Windows and Ruby < 2.3 This test will not currently work as the Ruby ENV hash has encoding limitations. This is fixed in Ruby 2.3 (https://github.com/ruby/ruby/commit/5e3467c4414df815b3b00d2b0372026b069e7f7d) --- spec/childprocess_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/childprocess_spec.rb b/spec/childprocess_spec.rb index 65c287d..117ed54 100644 --- a/spec/childprocess_spec.rb +++ b/spec/childprocess_spec.rb @@ -109,6 +109,10 @@ end it 'allows unicode characters in the environment' do + # This test does not work on Windows for Ruby < 2.3 because ENV values will not be properly decoded + # This was fixed in Ruby 2.3 here: https://github.com/ruby/ruby/commit/5e3467c4414df815b3b00d2b0372026b069e7f7d + # TODO: Write an alternate test that does not rely on the Ruby ENV hash + skip 'Test does not work on Windows for Ruby < 2.3' if Gem.win_platform? && RUBY_VERSION =~ /^1\.|^2\.[0-2]/ Tempfile.open("env-spec") do |file| process = write_env(file.path) process.environment['FOO'] = 'baör' From 69e62a59eec4f94ded6274b1adbeb73db0ca9126 Mon Sep 17 00:00:00 2001 From: Dave Armstrong Date: Tue, 30 Jan 2018 16:18:26 +0000 Subject: [PATCH 4/4] Enable testing of Unicode Environment variables on Windows. Uses a Powershell script to read Environment on Windows hosts so that unicode characters can be properly tested on Ruby versions < 2.3 --- spec/childprocess_spec.rb | 26 +++++++++++++++++--------- spec/get_env.ps1 | 13 +++++++++++++ spec/spec_helper.rb | 23 ++++++++++++++++------- 3 files changed, 46 insertions(+), 16 deletions(-) create mode 100644 spec/get_env.ps1 diff --git a/spec/childprocess_spec.rb b/spec/childprocess_spec.rb index 117ed54..e060191 100644 --- a/spec/childprocess_spec.rb +++ b/spec/childprocess_spec.rb @@ -83,11 +83,13 @@ it "lets child process inherit the environment of the current process" do Tempfile.open("env-spec") do |file| + file.close with_env('INHERITED' => 'yes') do process = write_env(file.path).start process.wait end + file.open child_env = eval rewind_and_read(file) expect(child_env['INHERITED']).to eql 'yes' end @@ -95,6 +97,7 @@ it "can override env vars only for the current process" do Tempfile.open("env-spec") do |file| + file.close process = write_env(file.path) process.environment['CHILD_ONLY'] = '1' process.start @@ -103,30 +106,30 @@ process.wait + file.open child_env = eval rewind_and_read(file) expect(child_env['CHILD_ONLY']).to eql '1' end end - + it 'allows unicode characters in the environment' do - # This test does not work on Windows for Ruby < 2.3 because ENV values will not be properly decoded - # This was fixed in Ruby 2.3 here: https://github.com/ruby/ruby/commit/5e3467c4414df815b3b00d2b0372026b069e7f7d - # TODO: Write an alternate test that does not rely on the Ruby ENV hash - skip 'Test does not work on Windows for Ruby < 2.3' if Gem.win_platform? && RUBY_VERSION =~ /^1\.|^2\.[0-2]/ Tempfile.open("env-spec") do |file| + file.close process = write_env(file.path) - process.environment['FOO'] = 'baör' + process.environment['FOö'] = 'baör' process.start process.wait - + + file.open child_env = eval rewind_and_read(file) - - expect(child_env['FOO'].force_encoding('UTF-8')).to eql 'baör' + + expect(child_env['FOö']).to eql 'baör' end end it "inherits the parent's env vars also when some are overridden" do Tempfile.open("env-spec") do |file| + file.close with_env('INHERITED' => 'yes', 'CHILD_ONLY' => 'no') do process = write_env(file.path) process.environment['CHILD_ONLY'] = 'yes' @@ -134,6 +137,7 @@ process.start process.wait + file.open child_env = eval rewind_and_read(file) expect(child_env['INHERITED']).to eq 'yes' @@ -144,6 +148,7 @@ it "can unset env vars" do Tempfile.open("env-spec") do |file| + file.close ENV['CHILDPROCESS_UNSET'] = '1' process = write_env(file.path) process.environment['CHILDPROCESS_UNSET'] = nil @@ -151,6 +156,7 @@ process.wait + file.open child_env = eval rewind_and_read(file) expect(child_env).to_not have_key('CHILDPROCESS_UNSET') end @@ -158,12 +164,14 @@ it 'does not see env vars unset in parent' do Tempfile.open('env-spec') do |file| + file.close ENV['CHILDPROCESS_UNSET'] = nil process = write_env(file.path) process.start process.wait + file.open child_env = eval rewind_and_read(file) expect(child_env).to_not have_key('CHILDPROCESS_UNSET') end diff --git a/spec/get_env.ps1 b/spec/get_env.ps1 new file mode 100644 index 0000000..f3f7de7 --- /dev/null +++ b/spec/get_env.ps1 @@ -0,0 +1,13 @@ +param($p1) +$env_list = Get-ChildItem Env: + +# Builds a ruby hash compatible string +$hash_string = "{" + +foreach ($item in $env_list) +{ + $hash_string += "`"" + $item.Name + "`" => `"" + $item.value.replace('\','\\').replace('"','\"') + "`"," +} +$hash_string += "}" + +$hash_string | out-File -Encoding "UTF8" $p1 diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f34d77b..127c456 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,6 +20,10 @@ def ruby_process(*args) @process = ChildProcess.build(RUBY , *args) end + def windows_process(*args) + @process = ChildProcess.build("powershell", *args) + end + def sleeping_ruby(seconds = nil) if seconds ruby_process("-e", "sleep #{seconds}") @@ -42,11 +46,16 @@ def ignored(signal) end def write_env(path) - code = <<-RUBY - File.open(#{path.inspect}, "w") { |f| f << ENV.inspect } - RUBY - - ruby_process tmp_script(code) + if ChildProcess.os == :windows + ps_env_file_path = File.expand_path(File.dirname(__FILE__)) + args = ['-File', "#{ps_env_file_path}/get_env.ps1", path] + windows_process(*args) + else + code = <<-RUBY + File.open(#{path.inspect}, "w") { |f| f << ENV.inspect } + RUBY + ruby_process tmp_script(code) + end end def write_argv(path, *args) @@ -102,7 +111,7 @@ def cat ruby(<<-CODE) STDIN.sync = STDOUT.sync = true IO.copy_stream(STDIN, STDOUT) - CODE + CODE else ChildProcess.build("cat") end @@ -115,7 +124,7 @@ def echo STDOUT.sync = true puts "hello" - CODE + CODE else ChildProcess.build("echo", "hello") end