Skip to content

Commit

Permalink
Add graceful exit on SIGINT
Browse files Browse the repository at this point in the history
* Mutant will now gracefully exit on SIGINT, but still with urgency.
* All active worker threads and their child processes will get killed.
* Only currently finished mutations will be reported.
* Exit status will depend on the current results, if they are all green:
  you get an exist 0, otherwise nonzero.
  • Loading branch information
mbj committed May 2, 2022
1 parent 464644c commit be01d38
Show file tree
Hide file tree
Showing 6 changed files with 257 additions and 98 deletions.
6 changes: 5 additions & 1 deletion Changelog.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# Unreleased
# v0.11.10 2022-05-02

* [#1328](https://github.com/mbj/mutant/pull/1328)

Fix incomplete mutations for named regexp capture groups. As an example, `/(?<name>\w\d)/` now gets mutated to `/(?<name>\W\d)/` and `/(?<name>\w\D)/` instead of just the former case.

* [#1331](https://github.com/mbj/mutant/pull/1331)

Add graceful but urgent exit on SIGINT.

# v0.11.9 2022-05-01

* [#1327](https://github.com/mbj/mutant/pull/1327)
Expand Down
20 changes: 18 additions & 2 deletions lib/mutant/parallel/driver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Mutant
module Parallel
# Driver for parallelized execution
class Driver
include Adamantium, Anima.new(
include Anima.new(
:threads,
:var_active_jobs,
:var_final,
Expand All @@ -16,18 +16,34 @@ class Driver

private(*anima.attribute_names)

def initialize(**attributes)
@alive = true
super
end

# Wait for computation to finish, with timeout
#
# @param [Float] timeout
#
# @return [Variable::Result<Sink#status>]
# current status
def wait_timeout(timeout)
var_final.take_timeout(timeout)
var_final.take_timeout(timeout) if @alive

finalize(status)
end

# Stop parallel computation
#
# This will cause all work to be immediately stopped.
#
# @return [self]
def stop
@alive = false
threads.each(&:kill)
self
end

private

def finalize(status)
Expand Down
4 changes: 4 additions & 0 deletions lib/mutant/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ def self.run_mutation_analysis(env)
private_class_method :run_mutation_analysis

def self.run_driver(reporter, driver)
Signal.trap('INT') do

This comment has been minimized.

Copy link
@mbj

mbj May 2, 2022

Author Owner

BTW this one cannot be &driver.public_method(:stop) as Signal.trap yields an argument we do not care about.

driver.stop
end

loop do
status = driver.wait_timeout(reporter.delay)
break status.payload if status.done?
Expand Down
3 changes: 2 additions & 1 deletion spec/support/xspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,8 @@ def error(message)
"#{message},
observation:
#{observation.inspect}
expectation: #{expectation.inspect}"
expectation:
#{expectation.inspect}"
MESSAGE
end
end # Verifier
Expand Down
163 changes: 113 additions & 50 deletions spec/unit/mutant/parallel/driver_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

RSpec.describe Mutant::Parallel::Driver do
RSpec.describe Mutant::Parallel::Driver, mutant_expression: 'Mutant::Parallel::Driver*' do
let(:active_jobs) { [] }
let(:sink_status) { instance_double(Object) }
let(:thread_a) { instance_double(Thread, alive?: true) }
Expand Down Expand Up @@ -35,6 +35,31 @@
)
end

describe '#stop' do
def apply
subject.stop
end

let(:raw_expectations) do
[
{
receiver: thread_a,
selector: :kill
},
{
receiver: thread_b,
selector: :kill
}
]
end

it 'returns self' do
verify_events do
expect(apply).to eql(subject)
end
end
end

describe '#wait_timeout' do
def apply
subject.wait_timeout(timeout)
Expand All @@ -57,77 +82,115 @@ def apply
end
end

let(:raw_expectations) do
[
{
receiver: var_final,
selector: :take_timeout,
arguments: [timeout],
reaction: { return: Mutant::Variable.const_get(:Result)::Timeout.new }
},
{
receiver: var_active_jobs,
selector: :with,
reaction: { yields: [active_jobs] }
},
{
receiver: var_sink,
selector: :with,
reaction: { yields: [sink] }
}
]
end
shared_examples 'when done' do
context 'when done' do
before do
allow(thread_a).to receive_messages(alive?: false)
allow(thread_b).to receive_messages(alive?: false)
end

context 'when not done' do
let(:expected_status) do
Mutant::Parallel::Status.new(
active_jobs: active_jobs,
done: false,
payload: sink_status
)
end
let(:raw_expectations) do
[
*super(),
{
receiver: worker_a,
selector: :join
},
{
receiver: worker_b,
selector: :join
},
{
receiver: thread_a,
selector: :join
},
{
receiver: thread_b,
selector: :join
}
]

end

include_examples 'returns expected status'
let(:expected_status) do
Mutant::Parallel::Status.new(
active_jobs: active_jobs,
done: true,
payload: sink_status
)
end

include_examples 'returns expected status'
end
end

context 'when done' do
before do
allow(thread_a).to receive_messages(alive?: false)
allow(thread_b).to receive_messages(alive?: false)
context 'when stopped' do
def apply
subject.stop
super()
end

let(:raw_expectations) do
[
*super(),
{
receiver: worker_a,
selector: :join
receiver: thread_a,
selector: :kill
},
{
receiver: worker_b,
selector: :join
receiver: thread_b,
selector: :kill
},
{
receiver: thread_a,
selector: :join
receiver: var_active_jobs,
selector: :with,
reaction: { yields: [active_jobs] }
},
{
receiver: thread_b,
selector: :join
receiver: var_sink,
selector: :with,
reaction: { yields: [sink] }
}
]
end

include_examples 'when done'
end

context 'when not stopped' do
let(:raw_expectations) do
[
{
receiver: var_final,
selector: :take_timeout,
arguments: [timeout],
reaction: { return: Mutant::Variable.const_get(:Result)::Timeout.new }
},
{
receiver: var_active_jobs,
selector: :with,
reaction: { yields: [active_jobs] }
},
{
receiver: var_sink,
selector: :with,
reaction: { yields: [sink] }
}
]
end

let(:expected_status) do
Mutant::Parallel::Status.new(
active_jobs: active_jobs,
done: true,
payload: sink_status
)
context 'when not done' do
let(:expected_status) do
Mutant::Parallel::Status.new(
active_jobs: active_jobs,
done: false,
payload: sink_status
)
end

include_examples 'returns expected status'
end

include_examples 'returns expected status'
include_examples 'when done'
end
end
end
Loading

0 comments on commit be01d38

Please sign in to comment.