Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 34 additions & 14 deletions lib/execjs/external_runtime.rb
Original file line number Diff line number Diff line change
Expand Up @@ -194,36 +194,56 @@ def shell_escape(*args)
require 'shellwords'

def exec_runtime(filename)
command = "#{Shellwords.join(binary.split(' ') << filename)}"
io = IO.popen(command, **@popen_options)
output = io.read
io.close
stderr_path = Dir::Tmpname.create(['execjs', 'stderr']) {}
begin
command = "#{Shellwords.join(binary.split(' ') << filename)}"
io = IO.popen(command, err: stderr_path, **@popen_options)
output = io.read
io.close
status = $?
stderr = File.file?(stderr_path) ? File.read(stderr_path) : ""
ensure
File.unlink(stderr_path) if stderr_path && File.exist?(stderr_path)
end

if $?.success?
if status.success?
output
else
raise exec_runtime_error(output)
raise exec_runtime_error(output, stderr)
end
end
else
def exec_runtime(filename)
io = IO.popen(binary.split(' ') << filename, **@popen_options)
output = io.read
io.close
stderr_path = Dir::Tmpname.create(['execjs', 'stderr']) {}
begin
io = IO.popen(binary.split(' ') << filename, err: stderr_path, **@popen_options)
output = io.read
io.close
status = $?
stderr = File.file?(stderr_path) ? File.read(stderr_path) : ""
ensure
File.unlink(stderr_path) if stderr_path && File.exist?(stderr_path)
end

if $?.success?
if status.success?
output
else
raise exec_runtime_error(output)
raise exec_runtime_error(output, stderr)
end
end
end
# Internally exposed for Context.
public :exec_runtime

def exec_runtime_error(output)
error = RuntimeError.new(output)
lines = output.split("\n")
def exec_runtime_error(output, stderr = nil)
message = output.to_s
unless stderr.nil? || stderr.empty?
extra = stderr.dup.force_encoding(message.encoding)
extra = extra.scrub if extra.respond_to?(:scrub) && !extra.valid_encoding?
message = message.empty? ? extra : "#{message}\n#{extra}"
end
error = RuntimeError.new(message)
lines = message.split("\n")
lineno = lines[0][/:(\d+)$/, 1] if lines[0]
lineno ||= 1
error.set_backtrace(["(execjs):#{lineno}"] + caller)
Expand Down
12 changes: 12 additions & 0 deletions test/test_execjs.rb
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,18 @@ def test_call_with_env_file
end
end

def test_runtime_error_includes_stderr
# Regression for #142: when the runtime exits non-zero it writes its
# diagnostic to stderr (here, a parse error). 5cce03a stopped capturing
# stderr, so the raised error lost that trace; it should be surfaced again.
# The diagnostic text is Node-style, so guard to Node-family runtimes.
skip unless ExecJS.runtime.name.to_s =~ /Node|Bun|Deno/i
err = assert_raises(ExecJS::RuntimeError) do
ExecJS.exec("?? not valid javascript ??")
end
assert err.message =~ /SyntaxError/, err.message
end

def test_call_with_this
# Known bug: https://github.com/cowboyd/therubyrhino/issues/39
skip if ExecJS.runtime.is_a?(ExecJS::RubyRhinoRuntime)
Expand Down
Loading