1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
|
require 'mspec/guards/platform'
require 'mspec/helpers/tmp'
# The ruby_exe helper provides a wrapper for invoking the
# same Ruby interpreter with the same flags as the one running
# the specs and getting the output from running the code.
#
# If +code+ is a file that exists, it will be run.
# Otherwise, +code+ will be written to a temporary file and be run.
# For example:
#
# ruby_exe('path/to/some/file.rb')
#
# will be executed as
#
# `#{RUBY_EXE} 'path/to/some/file.rb'`
#
# The ruby_exe helper also accepts an options hash with four
# keys: :options, :args :env and :exception.
#
# For example:
#
# ruby_exe('file.rb', :options => "-w",
# :args => "arg1 arg2",
# :env => { :FOO => "bar" })
#
# will be executed as
#
# `#{RUBY_EXE} -w file.rb arg1 arg2`
#
# with access to ENV["FOO"] with value "bar".
#
# When `exception: false` and Ruby command fails then exception will not be
# raised.
#
# If +nil+ is passed for the first argument, the command line
# will be built only from the options hash.
#
# If no arguments are passed to ruby_exe, it returns an Array
# containing the interpreter executable and the flags:
#
# spawn(*ruby_exe, "-e", "puts :hello")
#
# This avoids spawning an extra shell, and ensure the pid returned by spawn
# corresponds to the ruby process and not the shell.
#
# The RUBY_EXE constant is setup by mspec automatically
# and is used by ruby_exe and ruby_cmd. The mspec runner script
# will set ENV['RUBY_EXE'] to the name of the executable used
# to invoke the mspec-run script.
#
# The value will only be used if the file exists and is executable.
# The flags will then be appended to the resulting value, such that
# the RUBY_EXE constant contains both the executable and the flags.
#
# Additionally, the flags passed to mspec
# (with -T on the command line or in the config with set :flags)
# will be appended to RUBY_EXE so that the interpreter
# is always called with those flags.
#
# Failure of a Ruby command leads to raising exception by default.
def ruby_exe_options(option)
case option
when :env
ENV['RUBY_EXE']
when :engine
case RUBY_ENGINE
when 'rbx'
"bin/rbx"
when 'jruby'
"bin/jruby"
when 'maglev'
"maglev-ruby"
when 'topaz'
"topaz"
when 'ironruby'
"ir"
end
when :name
require 'rbconfig'
bin = RUBY_ENGINE + (RbConfig::CONFIG['EXEEXT'] || '')
File.join(".", bin)
when :install_name
require 'rbconfig'
bin = RbConfig::CONFIG["RUBY_INSTALL_NAME"] || RbConfig::CONFIG["ruby_install_name"]
bin << (RbConfig::CONFIG['EXEEXT'] || '')
File.join(RbConfig::CONFIG['bindir'], bin)
end
end
def resolve_ruby_exe
[:env, :engine, :name, :install_name].each do |option|
next unless exe = ruby_exe_options(option)
if File.file?(exe) and File.executable?(exe)
exe = File.expand_path(exe)
exe = exe.tr('/', '\\') if PlatformGuard.windows?
flags = ENV['RUBY_FLAGS']
if flags and !flags.empty?
return exe + ' ' + flags
else
return exe
end
end
end
raise Exception, "Unable to find a suitable ruby executable."
end
unless Object.const_defined?(:RUBY_EXE) and RUBY_EXE
RUBY_EXE = resolve_ruby_exe
end
def ruby_exe(code = :not_given, opts = {})
skip "WASI doesn't provide subprocess" if PlatformGuard.wasi?
if opts[:dir]
raise "ruby_exe(..., dir: dir) is no longer supported, use Dir.chdir"
end
if code == :not_given
return RUBY_EXE.split(' ')
end
env = opts[:env] || {}
saved_env = {}
env.each do |key, value|
key = key.to_s
saved_env[key] = ENV[key] if ENV.key? key
ENV[key] = value
end
escape = opts.delete(:escape)
if code and !File.exist?(code) and escape != false
tmpfile = tmp("rubyexe.rb")
File.open(tmpfile, "w") { |f| f.write(code) }
code = tmpfile
end
expected_status = opts.fetch(:exit_status, 0)
begin
command = ruby_cmd(code, opts)
# Try to avoid the extra shell for 2>&1
# This is notably useful for TimeoutAction which can then signal the ruby subprocess and not the shell
popen_options = []
if command.end_with?(' 2>&1')
command = command[0...-5]
popen_options = [{ err: [:child, :out] }]
end
output = IO.popen(command, *popen_options) do |io|
pid = io.pid
MSpec.subprocesses << pid
begin
io.read
ensure
MSpec.subprocesses.delete(pid)
end
end
status = Process.last_status
exit_status = if status.exited?
status.exitstatus
elsif status.signaled?
signame = Signal.signame status.termsig
raise "No signal name?" unless signame
:"SIG#{signame}"
else
raise SpecExpectationNotMetError, "#{exit_status.inspect} is neither exited? nor signaled?"
end
if exit_status != expected_status
formatted_output = output.lines.map { |line| " #{line}" }.join
raise SpecExpectationNotMetError,
"Expected exit status is #{expected_status.inspect} but actual is #{exit_status.inspect} for command ruby_exe(#{command.inspect})\nOutput:\n#{formatted_output}"
end
output
ensure
saved_env.each { |key, value| ENV[key] = value }
env.keys.each do |key|
key = key.to_s
ENV.delete key unless saved_env.key? key
end
File.delete tmpfile if tmpfile
end
end
def ruby_cmd(code, opts = {})
body = code
if opts[:escape]
raise "escape: true is no longer supported in ruby_cmd, use ruby_exe or a fixture"
end
if code and !File.exist?(code)
body = "-e #{code.inspect}"
end
command = [RUBY_EXE, opts[:options], body, opts[:args]].compact.join(' ')
STDERR.puts "\nruby_cmd: #{command}" if ENV["DEBUG_MSPEC_RUBY_CMD"] == "true"
command
end
|