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: SpecResolver.new, timeout: 30) ⇒ Baseline

Returns a new instance of Baseline.



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

def initialize(spec_resolver: SpecResolver.new, timeout: 30)
  @spec_resolver = spec_resolver
  @timeout = timeout
end

Instance Method Details

#call(subjects) ⇒ Object



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

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



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/evilution/baseline.rb', line 48

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

    require "rspec/core"
    ::RSpec.reset
    status = ::RSpec::Core::Runner.run(
      ["--format", "progress", "--no-color", "--order", "defined", spec_file]
    )
    Marshal.dump({ passed: status.zero? }, write_io)
    write_io.close
    exit!(status.zero? ? 0 : 1)
  end
end

#read_result(read_io, pid) ⇒ Object



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

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



36
37
38
39
40
41
42
43
44
45
46
# File 'lib/evilution/baseline.rb', line 36

def run_spec_file(spec_file)
  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 StandardError
  false
ensure
  read_io&.close
  write_io&.close
end

#terminate_child(pid) ⇒ Object



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

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