Class: Mutineer::DaemonClient
- Inherits:
-
Object
- Object
- Mutineer::DaemonClient
- Defined in:
- lib/mutineer/daemon_client.rb
Overview
Tool-side handle for the app-side daemon.
Spawns daemon_server.rb UNDER THE APP'S BUNDLE/RUBY (cleaned env so the
gem's bundler context never leaks; the daemon file is loaded by absolute path
with -r, which bypasses the app bundle that has no mutineer), completes the
ready handshake, then ships per-mutant payloads and reads structured verdicts.
If the daemon dies mid-run it respawns (bounded) and marks the in-flight
mutant error rather than corrupting the run. Reuses the cleaned-env spawn
and stderr-drain proven in the spike driver and the spawn discipline of
ExternalBackend.
Constant Summary collapse
- DAEMON_PATH =
Absolute path to the daemon entry, loaded app-side by
-r(bypasses the bundle). File.("daemon_server.rb", __dir__)
- MAX_RESTARTS =
How many times to respawn a crashing daemon before aborting the run.
3
Instance Method Summary collapse
-
#coverage ⇒ Hash?
Ask the daemon to build the coverage map app-side and return it.
-
#initialize(boot:, app_root:, ruby_version: nil, gemfile: nil, errio: $stderr) ⇒ DaemonClient
constructor
A new instance of DaemonClient.
-
#quit ⇒ void
Graceful shutdown; leaves no orphaned daemon/child.
-
#request(id:, payload:, tests:, timeout:, worker: 0) ⇒ String
Run one mutant: ship the payload + covering tests, return the verdict string.
-
#start ⇒ self
Spawn the daemon and complete the ready handshake.
Constructor Details
#initialize(boot:, app_root:, ruby_version: nil, gemfile: nil, errio: $stderr) ⇒ DaemonClient
Returns a new instance of DaemonClient.
36 37 38 39 40 41 42 43 |
# File 'lib/mutineer/daemon_client.rb', line 36 def initialize(boot:, app_root:, ruby_version: nil, gemfile: nil, errio: $stderr) @boot = boot @app_root = app_root @ruby_version = ruby_version @gemfile = gemfile || File.join(app_root, "Gemfile") @errio = errio @restarts = 0 end |
Instance Method Details
#coverage ⇒ Hash?
Ask the daemon to build the coverage map app-side and return it. One-shot
control message (no id). Returns {"map"=>..., "failed_test_files"=>...}
(possibly with an "error"), or nil if the daemon vanished. The caller then
falls back to running the full test set (no narrowing) rather than
mis-scoring.
96 97 98 99 100 101 |
# File 'lib/mutineer/daemon_client.rb', line 96 def coverage send_line("cmd" => "coverage") read_line rescue Errno::EPIPE, IOError nil end |
#quit ⇒ void
This method returns an undefined value.
Graceful shutdown; leaves no orphaned daemon/child.
106 107 108 109 110 111 112 113 |
# File 'lib/mutineer/daemon_client.rb', line 106 def quit return unless @stdin send_line("cmd" => "quit") rescue nil # rubocop:disable Style/RescueModifier @wait_thr&.join ensure close_io end |
#request(id:, payload:, tests:, timeout:, worker: 0) ⇒ String
Run one mutant: ship the payload + covering tests, return the verdict string.
On a daemon crash (EOF/dead pipe) respawn (bounded) and return "error" for
this mutant. Never a wrong verdict, never a wedged run.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/mutineer/daemon_client.rb', line 65 def request(id:, payload:, tests:, timeout:, worker: 0) # close_io nils the pipes, so a client whose respawn never completed would # otherwise fail per-mutant forever (NoMethodError on nil) and let the backend # score every remaining mutant against nothing. Deadness is a property of the # client, not of whichever exception happened to escape. raise DaemonBootError, "daemon is not running" if @stdin.nil? # A crash can surface on the WRITE (daemon died idle between requests → # Errno::EPIPE) as well as the read (EOF), so guard both: either way, respawn # for future mutants and score THIS one error (re-running a crash-causing # mutant could loop). Never let a dead pipe abort the whole run. reply = begin send_line("id" => id, "worker" => worker, "payload" => payload, "tests" => tests, "timeout" => timeout) read_line rescue Errno::EPIPE, IOError nil end return reply["verdict"] if reply && reply["id"] == id restart! "error" end |
#start ⇒ self
Spawn the daemon and complete the ready handshake. Raises DaemonBootError on failure (surfaced by the CLI as a clean runtime error, not a hang).
49 50 51 52 |
# File 'lib/mutineer/daemon_client.rb', line 49 def start spawn_daemon self end |