Class: Evilution::Baseline

Inherits:
Object
  • Object
show all
Defined in:
lib/evilution/baseline.rb

Defined Under Namespace

Classes: Result

Constant Summary collapse

GRACE_PERIOD =
0.5

Instance Method Summary collapse

Constructor Details

#initialize(spec_resolver: Evilution::SpecResolver.new, timeout: 30, runner: nil, fallback_dir: "spec") ⇒ Baseline

Returns a new instance of Baseline.



17
18
19
20
21
22
# File 'lib/evilution/baseline.rb', line 17

def initialize(spec_resolver: Evilution::SpecResolver.new, timeout: 30, runner: nil, fallback_dir: "spec")
  @spec_resolver = spec_resolver
  @timeout = timeout
  @runner = runner
  @fallback_dir = fallback_dir
end

Instance Method Details

#call(subjects) ⇒ Object



24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/evilution/baseline.rb', line 24

def call(subjects)
  start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  spec_files = resolve_unique_spec_files(subjects)
  failed = Set.new

  spec_files.each do |spec_file|
    failed.add(spec_file) unless run_spec_file(spec_file)
  end

  duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
  Result.new(failed_spec_files: failed, duration: duration)
end

#fork_spec_runner(spec_file, read_io, write_io) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/evilution/baseline.rb', line 53

def fork_spec_runner(spec_file, read_io, write_io)
  runner = @runner
  Process.fork do
    read_io.close
    $stdout.reopen(File::NULL, "w")
    $stderr.reopen(File::NULL, "w")

    passed = runner.call(spec_file)
    Marshal.dump({ passed: passed }, write_io)
    write_io.close
    exit!(passed ? 0 : 1)
  end
end

#read_result(read_io, pid) ⇒ Object



69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/evilution/baseline.rb', line 69

def read_result(read_io, pid)
  if read_io.wait_readable(@timeout)
    data = read_io.read
    Process.wait(pid)
    return false if data.empty?

    result = Marshal.load(data) # rubocop:disable Security/MarshalLoad
    result[:passed]
  else
    terminate_child(pid)
    false
  end
end

#run_spec_file(spec_file) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/evilution/baseline.rb', line 37

def run_spec_file(spec_file)
  raise Evilution::Error, "no baseline runner configured" unless @runner

  read_io, write_io = IO.pipe
  pid = fork_spec_runner(spec_file, read_io, write_io)
  write_io.close
  read_result(read_io, pid)
rescue Evilution::Error
  raise
rescue StandardError
  false
ensure
  read_io&.close
  write_io&.close
end

#terminate_child(pid) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/evilution/baseline.rb', line 83

def terminate_child(pid)
  Process.kill("TERM", pid) rescue nil # rubocop:disable Style/RescueModifier
  _, status = Process.waitpid2(pid, Process::WNOHANG)
  return if status

  sleep(GRACE_PERIOD)
  _, status = Process.waitpid2(pid, Process::WNOHANG)
  return if status

  Process.kill("KILL", pid) rescue nil # rubocop:disable Style/RescueModifier
  Process.wait(pid) rescue nil # rubocop:disable Style/RescueModifier
end