aboutsummaryrefslogtreecommitdiffstats
path: root/spec/mspec/lib/mspec/helpers/ruby_exe.rb
blob: f74ed014ceafa6b9e8665ce21470408ffcc5946d (plain)
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
require 'mspec/guards/platform'
require 'mspec/helpers/tmp'

# The ruby_exe helper provides a wrapper for invoking the
# same Ruby interpreter with the same falgs 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+ should be Ruby code that will be run with
# the -e command line option. For example:
#
#   ruby_exe('path/to/some/file.rb')
#
# will be executed as
#
#   `#{RUBY_EXE} 'path/to/some/file.rb'`
#
# while
#
#   ruby_exe('puts "hello, world."')
#
# will be executed as
#
#   `#{RUBY_EXE} -e 'puts "hello, world."'`
#
# The ruby_exe helper also accepts an options hash with three
# keys: :options, :args and :env. For example:
#
#   ruby_exe('file.rb', :options => "-w",
#                       :args => "> file.txt",
#                       :env => { :FOO => "bar" })
#
# will be executed as
#
#   `#{RUBY_EXE} -w #{'file.rb'} > file.txt`
#
# with access to ENV["FOO"] with value "bar".
#
# 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 of RUBY_EXE will be
# constructed as follows:
#
#   1. the value of ENV['RUBY_EXE']
#   2. an explicit value based on RUBY_ENGINE
#   3. cwd/(RUBY_ENGINE + $(EXEEXT) || $(exeext) || '')
#   4. $(bindir)/$(RUBY_INSTALL_NAME)
#
# The value will only be used if the file exists and is executable.
# The flags will then be appended to the resulting value.
#
# These 4 ways correspond to the following scenarios:
#
#   1. Using the MSpec runner scripts, the name of the
#      executable is explicitly passed by ENV['RUBY_EXE']
#      so there is no ambiguity.
#
#  Otherwise, if using RSpec (or something else)
#
#   2. Running the specs while developing an alternative
#      Ruby implementation. This explicitly names the
#      executable in the development directory based on
#      the value of RUBY_ENGINE.
#   3. Running the specs within the source directory for
#      some implementation. (E.g. a local build directory.)
#   4. Running the specs against some installed Ruby
#      implementation.
#
# 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.

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'] || 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'] || 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

def ruby_exe(code = :not_given, opts = {})
  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

  begin
    platform_is_not :opal do
      `#{ruby_cmd(code, opts)}`
    end
  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

  [RUBY_EXE, opts[:options], body, opts[:args]].compact.join(' ')
end

unless Object.const_defined?(:RUBY_EXE) and RUBY_EXE
  RUBY_EXE = resolve_ruby_exe
end