Class: Henitai::Integration::Base

Inherits:
Object
  • Object
show all
Includes:
ChildRuntimeControl
Defined in:
lib/henitai/integration/base.rb,
sig/henitai.rbs

Overview

Base class for all integrations. Provides the framework-agnostic child process lifecycle (wait, timeout handling, cleanup) and subprocess environment helpers. Concrete adapters mix in MutantRunSupport and implement #run_tests plus test selection.

Direct Known Subclasses

Minitest, Rspec

Instance Method Summary collapse

Instance Method Details

#child_debug_logChildDebugLog

Child-run diagnostics. Public so the child-side modules mixed into concrete adapters can reach it without send.

Returns:



20
21
22
# File 'lib/henitai/integration/base.rb', line 20

def child_debug_log
  @child_debug_log ||= ChildDebugLog.new
end

#cleanup_child_process(pid) ⇒ void

This method returns an undefined value.

Parameters:

  • (Integer)


115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/henitai/integration/base.rb', line 115

def cleanup_child_process(pid)
  grace_period = 2.0
  wakeup = Henitai.const_get(:ProcessWakeup).new.install
  Process.kill(:SIGTERM, pid)
  return if wait_nonblocking(pid)

  wakeup.wait(grace_period)
  wakeup.drain
  return if wait_nonblocking(pid)

  Process.kill(:SIGKILL, pid)
rescue Errno::EPERM, Errno::ESRCH
  nil
ensure
  wakeup&.close
end

#cleanup_process_group(pid) ⇒ void

This method returns an undefined value.

Parameters:

  • (Integer)


80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/henitai/integration/base.rb', line 80

def cleanup_process_group(pid)
  grace_period = 2.0
  wakeup = Henitai.const_get(:ProcessWakeup).new.install
  Process.kill(:SIGTERM, -pid)
  return if wait_nonblocking(pid)

  wakeup.wait(grace_period)
  wakeup.drain
  return if wait_nonblocking(pid)

  Process.kill(:SIGKILL, -pid)
rescue Errno::EPERM
  cleanup_child_process(pid)
rescue Errno::ESRCH
  nil
ensure
  wakeup&.close
end

#handle_timeout(pid) ⇒ Symbol

Parameters:

  • (Integer)

Returns:

  • (Symbol)


105
106
107
108
109
110
111
112
113
# File 'lib/henitai/integration/base.rb', line 105

def handle_timeout(pid)
  begin
    debug_child_timeout_dump(pid)
    cleanup_process_group(pid)
  ensure
    reap_child(pid)
  end
  :timeout
end

#pause(seconds) ⇒ void

This method returns an undefined value.

Parameters:

  • (Float)


101
102
103
# File 'lib/henitai/integration/base.rb', line 101

def pause(seconds)
  sleep(seconds)
end

#per_test_coverage_supported?Boolean

Returns:

  • (Boolean)


56
57
58
# File 'lib/henitai/integration/base.rb', line 56

def per_test_coverage_supported?
  false
end

#reap_child(pid) ⇒ void

This method returns an undefined value.

Parameters:

  • (Integer)


74
75
76
77
78
# File 'lib/henitai/integration/base.rb', line 74

def reap_child(pid)
  Process.wait(pid)
rescue Errno::ECHILD, Errno::ESRCH
  nil
end

#restore_subprocess_env(original_env) ⇒ void

This method returns an undefined value.

Parameters:

  • (Hash[String, String?])


157
158
159
160
161
162
163
164
165
# File 'lib/henitai/integration/base.rb', line 157

def restore_subprocess_env(original_env)
  original_env.each do |key, value|
    if value.nil?
      ENV.delete(key)
    else
      ENV[key] = value
    end
  end
end

#run_mutant(mutant:, test_files:, timeout:) ⇒ ScenarioExecutionResult

Run test files in a child process with the mutant active.

Parameters:

  • mutant (Mutant)
  • test_files (Array<String>)
  • timeout (Float)

    seconds

  • mutant: (Mutant)
  • test_files: (Array[String])
  • timeout: (Float)

Returns:

Raises:

  • (NotImplementedError)


41
42
43
# File 'lib/henitai/integration/base.rb', line 41

def run_mutant(mutant:, test_files:, timeout:)
  raise NotImplementedError
end

#run_suiteScenarioExecutionResult

Parameters:

  • (Array[String])
  • timeout: (Float)

Returns:



400
# File 'sig/henitai.rbs', line 400

def run_suite: (Array[String], ?timeout: Float) -> ScenarioExecutionResult

#scenario_log_supportScenarioLogSupport

Returns:



142
143
144
# File 'lib/henitai/integration/base.rb', line 142

def scenario_log_support
  @scenario_log_support ||= ScenarioLogSupport.new
end

#select_tests(subject) ⇒ Array<String>

Returns paths to test files that cover this subject.

Parameters:

Returns:

  • (Array<String>)

    paths to test files that cover this subject

Raises:

  • (NotImplementedError)


26
27
28
# File 'lib/henitai/integration/base.rb', line 26

def select_tests(subject)
  raise NotImplementedError
end

#spawn_mutant(mutant:, test_files:) ⇒ RspecProcessRunner::ChildHandle

Fork a child process for the mutant without waiting for it to finish. Returns a ChildHandle carrying the OS pid and log file paths. The caller is responsible for waiting and cleanup.

Parameters:

  • mutant (Mutant)
  • test_files (Array<String>)
  • mutant: (Mutant)
  • test_files: (Array[String])

Returns:

  • (RspecProcessRunner::ChildHandle)

Raises:

  • (NotImplementedError)


52
53
54
# File 'lib/henitai/integration/base.rb', line 52

def spawn_mutant(mutant:, test_files:)
  raise NotImplementedError
end

#subprocess_envHash[String, String]

Returns:

  • (Hash[String, String])


132
133
134
# File 'lib/henitai/integration/base.rb', line 132

def subprocess_env
  { "PARALLEL_WORKERS" => "1" }
end

#suppress_coverage!void

This method returns an undefined value.



414
# File 'sig/henitai.rbs', line 414

def suppress_coverage!: () -> void

#suppress_simplecov!void

This method returns an undefined value.



413
# File 'sig/henitai.rbs', line 413

def suppress_simplecov!: () -> void

#test_filesArray<String>

Returns all test files for the configured framework.

Returns:

  • (Array<String>)

    all test files for the configured framework

Raises:

  • (NotImplementedError)


31
32
33
# File 'lib/henitai/integration/base.rb', line 31

def test_files
  raise NotImplementedError
end

#wait_nonblocking(pid) ⇒ Object

Parameters:

  • (Integer)

Returns:

  • (Object)


136
137
138
139
140
# File 'lib/henitai/integration/base.rb', line 136

def wait_nonblocking(pid)
  Process.wait(pid, Process::WNOHANG)
rescue Errno::ECHILD, Errno::ESRCH
  nil
end

#wait_with_timeout(pid, timeout) ⇒ Object

Parameters:

  • (Integer)
  • (Float)

Returns:

  • (Object)


60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/henitai/integration/base.rb', line 60

def wait_with_timeout(pid, timeout)
  wakeup = Henitai.const_get(:ProcessWakeup).new.install
  return Process.last_status if wait_nonblocking(pid)

  wakeup.wait(timeout)
  wakeup.drain
  return Process.last_status if wait_nonblocking(pid)
  return Process.last_status if wait_nonblocking(pid)

  handle_timeout(pid)
ensure
  wakeup&.close
end

#with_non_interactive_stdin { ... } ⇒ Object

Yields:

Yield Returns:

  • (Object)

Returns:

  • (Object)


167
168
169
170
171
172
173
# File 'lib/henitai/integration/base.rb', line 167

def with_non_interactive_stdin
  original_stdin = $stdin
  $stdin = StringIO.new
  yield
ensure
  $stdin = original_stdin
end

#with_subprocess_env { ... } ⇒ Object

Yields:

Yield Returns:

  • (Object)

Returns:

  • (Object)


146
147
148
149
150
151
152
153
154
155
# File 'lib/henitai/integration/base.rb', line 146

def with_subprocess_env
  original_env = {} # : Hash[String, String?]
  subprocess_env.each do |key, value|
    original_env[key] = ENV.fetch(key, nil)
    ENV[key] = value
  end
  yield
ensure
  restore_subprocess_env(original_env)
end