Class: RailVerdict::ProcessRunner

Inherits:
Object
  • Object
show all
Defined in:
lib/rail_verdict/process_runner.rb

Defined Under Namespace

Classes: DirectoryError, Registry, RunResult

Constant Summary collapse

DEFAULT_TIMEOUT_SECONDS =
30.0
DEFAULT_MAX_STDOUT_BYTES =
4 * 1024 * 1024
DEFAULT_MAX_STDERR_BYTES =
64 * 1024
MAX_SAFE_STDOUT_BYTES =

Safe ceiling prevents configuration from escalating to unbounded memory.

64 * 1024 * 1024
MAX_SAFE_STDERR_BYTES =
1 * 1024 * 1024
RSPEC_MAX_STDOUT_BYTES =
16 * 1024 * 1024
READ_CHUNK_BYTES =
64 * 1024
TERM_GRACE_SECONDS =
0.1
REAP_POLL_SECONDS =
0.01
ENV_ALLOWLIST =
%w[
  PATH HOME GEM_HOME GEM_PATH LANG RAILVERDICT_MINITEST_OUTPUT
].freeze
FORCED_ENV =
{ "LC_ALL" => "C.UTF-8", "TZ" => "UTC" }.freeze

Class Method Summary collapse

Class Method Details

.build_envObject



74
75
76
77
78
79
80
81
# File 'lib/rail_verdict/process_runner.rb', line 74

def build_env
  env = ENV.keys.to_h { |key| [key, nil] }
  ENV_ALLOWLIST.each do |key|
    value = ENV[key]
    env[key] = value unless value.nil?
  end
  env.merge(FORCED_ENV)
end

.registryObject



70
71
72
# File 'lib/rail_verdict/process_runner.rb', line 70

def registry
  @registry ||= Registry.new
end

.run(executable, argv, chdir:, timeout_seconds: DEFAULT_TIMEOUT_SECONDS, max_stdout_bytes: DEFAULT_MAX_STDOUT_BYTES, max_stderr_bytes: DEFAULT_MAX_STDERR_BYTES, binary_output: false) ⇒ Object



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
# File 'lib/rail_verdict/process_runner.rb', line 30

def run(executable, argv, chdir:, timeout_seconds: DEFAULT_TIMEOUT_SECONDS,
        max_stdout_bytes: DEFAULT_MAX_STDOUT_BYTES, max_stderr_bytes: DEFAULT_MAX_STDERR_BYTES,
        binary_output: false)
  max_stdout_bytes = clamp_limit(max_stdout_bytes, DEFAULT_MAX_STDOUT_BYTES, MAX_SAFE_STDOUT_BYTES)
  max_stderr_bytes = clamp_limit(max_stderr_bytes, DEFAULT_MAX_STDERR_BYTES, MAX_SAFE_STDERR_BYTES)
  directory = verify_directory(chdir)
  argv = argv.map { |element| validate_argv_element(element) }
  env = build_env

  stdout_read, stdout_write = IO.pipe
  stderr_read, stderr_write = IO.pipe
  pid = nil

  begin
    pid = Process.spawn(
      env,
      [executable, File.basename(executable)],
      *argv,
      chdir: directory,
      pgroup: true,
      in: :close,
      out: stdout_write,
      err: stderr_write
    )
  rescue Errno::ENOENT, Errno::EACCES, Errno::ENOTDIR, ArgumentError => error
    return spawn_failure_result(error)
  end

  registry.register(pid)
  stdout_write.close
  stderr_write.close

  execute_child(pid, stdout_read, stderr_read, timeout_seconds, max_stdout_bytes, max_stderr_bytes, binary_output)
ensure
  registry.unregister(pid) if pid
  [stdout_read, stdout_write, stderr_read, stderr_write].compact.each do |io|
    io.close unless io.closed?
  end
end