Skip to content

Commit

Permalink
Output result in a temporary file instead of stdout
Browse files Browse the repository at this point in the history
  • Loading branch information
Nu-hin committed Dec 25, 2024
1 parent f73363a commit debe665
Show file tree
Hide file tree
Showing 14 changed files with 148 additions and 78 deletions.
19 changes: 12 additions & 7 deletions lib/remote_ruby/code_templates/compiler/main.rb.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require 'bundler/setup' if File.exist?('Gemfile')

require 'base64'
require 'tempfile'

__marshalled_locals_names__ = []

Expand All @@ -17,6 +18,13 @@ rescue ArgumentError
end
<% end %>


__result_file__ = Tempfile.create('remote_ruby_result.dat')

$stdout.sync = true
$stdout.sync = true
$stdout.puts __result_file__.path

__return_val__ = begin

<% if code_headers.any? %>
Expand All @@ -25,8 +33,6 @@ __return_val__ = begin
# End of flavour-added code
<% end %>

STDOUT.sync = true
STDERR.sync = true

# Start of client code
<%= ruby_code %>
Expand All @@ -36,12 +42,11 @@ end
__marshalled_locals_names__ << :__return_val__

# Marshalling local variables and result

$stdout.puts "%%%MARSHAL"

__marshalled_locals_names__.each do |lv|
data = Marshal.dump(eval(lv.to_s))
data_length = data.size
$stdout.puts "#{lv}:#{data_length}"
$stdout.write(data)
__result_file__.puts "#{lv}:#{data_length}"
__result_file__.write(data)
end

__result_file__.close
9 changes: 9 additions & 0 deletions lib/remote_ruby/connection_adapter/cache_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,19 @@ def open(_code)
stdout.close unless stdout.closed?
end

def with_result_stream(&block)
File.open(result_file_path, 'r', &block)
end

private

attr_reader :cache_path

def result_file_path
fp = "#{cache_path}.result"
File.exist?(fp) ? fp : File::NULL
end

def stdout_file_path
fp = "#{cache_path}.stdout"
File.exist?(fp) ? fp : File::NULL
Expand Down
12 changes: 12 additions & 0 deletions lib/remote_ruby/connection_adapter/caching_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ def open(code)
end
end

def with_result_stream
File.open(result_file_path, 'w') do |result_cache|
adapter.with_result_stream do |stream|
yield ::RemoteRuby::StreamCacher.new(stream, result_cache)
end
end
end

private

attr_reader :cache_path, :adapter
Expand All @@ -35,6 +43,10 @@ def with_cache
stderr_cache.close
end

def result_file_path
"#{cache_path}.result"
end

def stdout_file_path
"#{cache_path}.stdout"
end
Expand Down
7 changes: 7 additions & 0 deletions lib/remote_ruby/connection_adapter/eval_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,18 @@ def open(code)
err_write.close
end

@result_fname = out_read.readline.chomp
yield in_write, out_read, err_read
t.join
end
end

def with_result_stream(&block)
File.open(@result_fname, 'r', &block)
ensure
File.unlink(@result_fname)
end

private

def run_code(code)
Expand Down
92 changes: 53 additions & 39 deletions lib/remote_ruby/connection_adapter/ssh_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,58 +14,72 @@ def initialize(host:, user: nil, working_dir: nil)

def open(code)
Net::SSH.start(host, user, config) do |ssh|
fname = ''
code_channel = ssh.open_channel do |channel|
channel.exec('f=$(mktemp remote_ruby.rb.XXXXXX) && cat > $f && echo $f') do |ch, success|
raise 'Could not execute command' unless success
with_temp_file(code, ssh) do |fname|
stdout_r, stdout_w = IO.pipe
stderr_r, stderr_w = IO.pipe
stdin_r, stdin_w = IO.pipe

ch.on_data do |_, data|
fname << data
end
t = Thread.new do
ssh.open_channel do |channel|
channel.exec("cd #{working_dir} && ruby #{fname}") do |ch, success|
raise 'Could not execute command' unless success

ch.send_data(code)
ch.eof!
end
end
ssh.listen_to(stdin_r) do |io|
data = io.read_nonblock(4096)
ch.send_data(data)
rescue EOFError
ch.eof!
end

code_channel.wait
ch.on_data do |_, data|
stdout_w << data
end

stdout_r, stdout_w = IO.pipe
stderr_r, stderr_w = IO.pipe
stdin_r, stdin_w = IO.pipe
ch.on_extended_data do |_, _, data|
stderr_w << data
end

execution_channel = ssh.open_channel do |channel|
channel.exec("cd #{working_dir} && ruby #{fname}") do |ch, success|
raise 'Could not execute command' unless success
ch.on_close do |_|
stdout_w.close
stderr_w.close
end
end
end.wait
end

ssh.listen_to(stdin_r) do |io|
data = io.read_nonblock(4096)
ch.send_data(data)
rescue EOFError
ch.eof!
end
@result_fname = stdout_r.readline.chomp
yield stdin_w, stdout_r, stderr_r
t.join
@result = ssh.exec!("cat '#{@result_fname}'")
ssh.exec!("rm #{@result_fname}")
end
end
end

ch.on_data do |_, data|
stdout_w << data
end
def with_result_stream
yield StringIO.new(@result)
end

ch.on_extended_data do |_, _, data|
stderr_w << data
end
def with_temp_file(code, ssh)
fname = ''
code_channel = ssh.open_channel do |channel|
channel.exec('f=$(mktemp remote_ruby.rb.XXXXXX) && cat > $f && echo $f') do |ch, success|
raise 'Could not execute command' unless success

ch.on_close do |_|
stdout_w.close
stderr_w.close
end
ch.on_data do |_, data|
fname << data
end
end

Thread.new { yield stdin_w, stdout_r, stderr_r }
ch.send_data(code)
ch.eof!
end
end

execution_channel.wait
code_channel.wait

ssh.exec!("rm #{fname}")
end
yield fname
ensure
ssh.exec!("rm #{fname}")
end
end
end
8 changes: 8 additions & 0 deletions lib/remote_ruby/connection_adapter/tmp_file_adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def open(code)
result = nil

popen3(command(filename)) do |stdin, stdout, stderr, wait_thr|
@result_fname = stdout.readline.chomp

yield stdin, stdout, stderr

result = wait_thr.value
Expand All @@ -32,6 +34,12 @@ def open(code)
end
end

def with_result_stream(&block)
File.open(@result_fname, 'r', &block)
ensure
File.unlink(@result_fname)
end

protected

def with_temp_file(code)
Expand Down
15 changes: 5 additions & 10 deletions lib/remote_ruby/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ def run
err_thread = read_stream(stderr, err_stream)
[out_thread, err_thread].compact.each(&:join)
stdin&.close
locals = out_thread[:locals]
end

adapter.with_result_stream do |result_stream|
locals = unmarshal(result_stream)
end

{ result: locals[:__return_val__], locals: locals }
Expand All @@ -36,15 +39,7 @@ def run

def read_stream(read_from, write_to)
Thread.new do
until read_from.eof?
line = read_from.readline

if line.start_with?('%%%MARSHAL')
Thread.current[:locals] ||= unmarshal(read_from)
else
write_to.puts line
end
end
IO.copy_stream(read_from, write_to)
end
end

Expand Down
12 changes: 10 additions & 2 deletions spec/remote_ruby/compiler_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,16 @@
expect { eval("lambda { #{compiled_code} }") }.not_to raise_error
end

it 'outputs marshal values' do
expect { eval(compiled_code) }.to output(/%%%MARSHAL/).to_stdout
it 'creates a temp file for result' do
fname, = with_capture do
eval(compiled_code)
end

expect(Pathname.new(fname.chomp)).to exist
end

it 'outputs result file name' do
expect { eval(compiled_code) }.to output(%r{^/tmp/remote_ruby_result\.dat.+$}).to_stdout
end
# rubocop:enable Security/Eval, Style/EvalWithLocation
end
Expand Down
4 changes: 2 additions & 2 deletions spec/remote_ruby/connection_adapter/caching_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ def run(code)
end

it 'saves output to file' do
run("print 'text'")
run("puts; print 'text'")
expect(File.read(stdout_cache_path)).to eq('text')
end

it 'saved errors to file' do
run("$stderr.print 'text'")
run("puts; $stderr.print 'text'")
expect(File.read(stderr_cache_path)).to eq('text')
end

Expand Down
8 changes: 4 additions & 4 deletions spec/remote_ruby/connection_adapter/eval_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
it 'is launched in the same process' do
new_pid = nil

adapter.open('puts Process.pid') do |_stdin, stdout, _stderr|
adapter.open('puts; puts Process.pid') do |_stdin, stdout, _stderr|
new_pid = stdout.read.to_i
end

Expand All @@ -26,7 +26,7 @@
end

it 'restores $stdout and $stderr variables' do
adapter.open('puts Process.pid') do |_stdin, stdout, stderr|
adapter.open('puts; puts Process.pid') do |_stdin, stdout, stderr|
expect(stdout).not_to eq(STDOUT)
expect(stderr).not_to eq(STDERR)
end
Expand All @@ -39,7 +39,7 @@
pwd = nil
old_dir = Dir.pwd

adapter.open('puts Dir.pwd') do |_stdin, stdout, _stderr|
adapter.open('puts; puts Dir.pwd') do |_stdin, stdout, _stderr|
pwd = stdout.read
pwd.strip!
end
Expand All @@ -51,7 +51,7 @@
it 'is launched in a different thread' do
new_thread_id = nil

adapter.open('puts Thread.current.object_id') do |_stdin, stdout, _stderr|
adapter.open('puts; puts Thread.current.object_id') do |_stdin, stdout, _stderr|
new_thread_id = stdout.read.to_i
end

Expand Down
6 changes: 3 additions & 3 deletions spec/remote_ruby/connection_adapter/tmp_file_adapter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@
end

it 'runs the script from a file' do
script = "puts __FILE__\n"
script = "puts; puts __FILE__\n"
adapter.open(script) do |_stdin, stdout, _stderr|
fname = stdout.read.strip
expect(fname).to match(%r{/remote_ruby\.rb$})
end
end

it 'reads the input from stdin' do
input = 'puts gets'
input = 'puts "mbon"; $stdout.flush; puts gets'
adapter.open(input) do |stdin, stdout, _stderr|
stdin.puts 'Hello, world!'
stdin.close
Expand All @@ -51,7 +51,7 @@
before(:example) do
allow(adapter).to receive(:popen3).and_yield(
fake_stdin,
StringIO.new(output_content),
StringIO.new("\n#{output_content}"),
StringIO.new(error_content),
wait_thr
)
Expand Down
4 changes: 2 additions & 2 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@

Bundler.require(:development, :test)

require_relative 'support/stdin_helper'
require_relative 'support/stream_helper'

RSpec.configure do |config|
config.include StdinHelper
config.include StreamHelper

config.expect_with :rspec do |expectations|
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
Expand Down
9 changes: 0 additions & 9 deletions spec/support/stdin_helper.rb

This file was deleted.

Loading

0 comments on commit debe665

Please sign in to comment.