Skip to content

Commit

Permalink
Add test only runner
Browse files Browse the repository at this point in the history
  • Loading branch information
mbj committed Feb 14, 2024
1 parent c30fb9f commit 6691b0a
Show file tree
Hide file tree
Showing 40 changed files with 1,719 additions and 213 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,16 @@ jobs:
ruby: ruby-3.1
- os: macos-latest
ruby: ruby-3.0
execution:
- bundle exec rspec spec/unit
- bundle exec mutant environment test run spec/unit
steps:
- uses: actions/checkout@v4
- uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- run: bundle exec rspec spec/unit
- run: ${{ matrix.execution }}
ruby-mutant:
name: Mutation coverage
runs-on: ${{ matrix.os }}
Expand Down
2 changes: 1 addition & 1 deletion Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,4 @@ DEPENDENCIES
rubocop (~> 1.7)

BUNDLED WITH
2.4.10
2.5.3
1 change: 1 addition & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ integration:
# Below shows an example configuring rspec to use a static seed from the config file.
arguments:
- --fail-fast # rspec integration default, keep this when specifying manual options!
- --force-color # rspec integration default, keep this if colored output is not an issue for you
- --seed # option
- '0' # option value, needs to be a string.
- spec # rspec integration default, tell rspec integration where to find specs
Expand Down
4 changes: 4 additions & 0 deletions lib/mutant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,8 @@ module Mutant
require 'mutant/expression/namespace'
require 'mutant/expression/parser'
require 'mutant/test'
require 'mutant/test/runner'
require 'mutant/test/runner/sink'
require 'mutant/timer'
require 'mutant/integration'
require 'mutant/integration/null'
Expand Down Expand Up @@ -243,6 +245,7 @@ module Mutant
require 'mutant/reporter/cli/printer/mutation_result'
require 'mutant/reporter/cli/printer/status_progressive'
require 'mutant/reporter/cli/printer/subject_result'
require 'mutant/reporter/cli/printer/test'
require 'mutant/reporter/cli/format'
require 'mutant/repository'
require 'mutant/repository/diff'
Expand Down Expand Up @@ -328,6 +331,7 @@ module Mutant
stderr: $stderr,
stdout: $stdout,
thread: Thread,
time: Time,
timer: timer
)

Expand Down
28 changes: 27 additions & 1 deletion lib/mutant/bootstrap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,32 @@ def self.call(env)
selected_subjects.flat_map(&:mutations)
end

setup_integration(
env: env,
mutations: mutations,
selected_subjects: selected_subjects
)
end
end
# rubocop:enable Metrics/MethodLength

# Run test only bootstrap
#
# @param [Env] env
#
# @return [Either<String, Env>]
def self.call_test(env)
env.record(:bootstrap) do
setup_integration(
env: load_hooks(env),
mutations: [],
selected_subjects: []
)
end
end

def self.setup_integration(env:, mutations:, selected_subjects:)
env.record(__method__) do
Integration.setup(env).fmap do |integration|
env.with(
integration: integration,
Expand All @@ -57,7 +83,7 @@ def self.call(env)
end
end
end
# rubocop:enable Metrics/MethodLength
private_class_method :setup_integration

def self.load_hooks(env)
env.record(__method__) do
Expand Down
39 changes: 38 additions & 1 deletion lib/mutant/cli/command/environment/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,21 @@ class Test < self
NAME = 'test'
SHORT_DESCRIPTION = 'test subcommands'

private

def parse_remaining_arguments(arguments)
arguments.each(&method(:add_integration_argument))
Either::Right.new(self)
end

def bootstrap
env = Env.empty(world, @config)

env
.record(:config) { Config.load(cli_config: @config, world: world) }
.bind { |config| Bootstrap.call_test(env.with(config: config)) }
end

class List < self
NAME = 'list'
SHORT_DESCRIPTION = 'List tests detected in the environment'
Expand All @@ -28,7 +43,29 @@ def list_tests(env)
end
end

SUBCOMMANDS = [List].freeze
class Run < self
NAME = 'run'
SHORT_DESCRIPTION = 'Run tests'
SUBCOMMANDS = EMPTY_ARRAY

private

def action
bootstrap
.bind(&Mutant::Test::Runner.public_method(:call))
.bind(&method(:from_result))
end

def from_result(result)
if result.success?
Either::Right.new(nil)
else
Either::Left.new('Test failures, exiting nonzero!')
end
end
end

SUBCOMMANDS = [List, Run].freeze
end # Test
end # Environment
end # Command
Expand Down
35 changes: 35 additions & 0 deletions lib/mutant/env.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,28 @@ def cover_index(mutation_index)
)
end

def run_test_index(test_index)
test_result = nil

output = with_capture do
test_result = integration.call([integration.all_tests.fetch(test_index)])
end

Result::Test.new(
output: output,
passed: test_result.passed,
runtime: test_result.runtime
)
end

def emit_mutation_worker_process_start(index:)
hooks.run(:mutation_worker_process_start, index: index)
end

def emit_test_worker_process_start(index:)
hooks.run(:test_worker_process_start, index: index)
end

# The test selections
#
# @return Hash{Mutation => Enumerable<Test>}
Expand Down Expand Up @@ -176,6 +194,23 @@ def timer
world.timer
end

def with_capture
stderr, stdout = world.stderr, world.stdout
original_stdout = stderr.dup
original_stderr = stderr.dup
Tempfile.create('capture') do |io|
stdout.reopen(io)
stdout.sync = true
stderr.reopen(stdout)
yield
io.rewind
io.read
end
ensure
stdout.reopen(original_stdout)
stderr.reopen(original_stderr)
end

end # Env
# rubocop:enable Metrics/ClassLength
end # Mutant
1 change: 1 addition & 0 deletions lib/mutant/hooks.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Hooks
mutation_insert_post
mutation_insert_pre
mutation_worker_process_start
test_worker_process_start
].product([EMPTY_ARRAY]).to_h.transform_values(&:freeze).freeze

MESSAGE = 'Unknown hook %s'
Expand Down
6 changes: 5 additions & 1 deletion lib/mutant/integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ module Mutant

# Abstract base class mutant test framework integrations
class Integration
include AbstractType, Adamantium, Anima.new(:arguments, :expression_parser, :world)
include AbstractType, Adamantium, Anima.new(
:arguments,
:expression_parser,
:world
)

LOAD_MESSAGE = <<~'MESSAGE'
Unable to load integration mutant-%<integration_name>s:
Expand Down
1 change: 1 addition & 0 deletions lib/mutant/integration/minitest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def call(tests)
reporter.report

Result::Test.new(
output: '',
passed: reporter.passed?,
runtime: timer.now - start
)
Expand Down
1 change: 1 addition & 0 deletions lib/mutant/integration/null.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ def all_tests
# @return [Result::Test]
def call(_tests)
Result::Test.new(
output: '',
passed: true,
runtime: 0.0
)
Expand Down
18 changes: 14 additions & 4 deletions lib/mutant/integration/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,16 @@ class Rspec < self
ALL_EXPRESSION = Expression::Namespace::Recursive.new(scope_name: nil)
EXPRESSION_CANDIDATE = /\A([^ ]+)(?: )?/
EXIT_SUCCESS = 0
DEFAULT_CLI_OPTIONS = %w[--fail-fast spec].freeze
DEFAULT_CLI_OPTIONS = %w[--fail-fast --force-color spec].freeze
TEST_ID_FORMAT = 'rspec:%<index>d:%<location>s/%<description>s'

private_constant(*constants(false))

def freeze
super() if @setup_elapsed
self
end

# Initialize rspec integration
#
# @return [undefined]
Expand All @@ -42,10 +47,12 @@ def initialize(*)
#
# @return [self]
def setup
@runner.setup($stderr, $stdout)
example_group_map
@setup_elapsed = timer.elapsed do
@runner.setup(world.stderr, world.stdout)
example_group_map
end
reset_examples
self
freeze
end
memoize :setup

Expand All @@ -55,10 +62,13 @@ def setup
#
# @return [Result::Test]
def call(tests)
reset_examples
setup_examples(tests.map(&all_tests_index))
@runner.configuration.start_time = world.time.now - @setup_elapsed
start = timer.now
passed = @runner.run_specs(@rspec_world.ordered_example_groups).equal?(EXIT_SUCCESS)
Result::Test.new(
output: '',
passed: passed,
runtime: timer.now - start
)
Expand Down
23 changes: 22 additions & 1 deletion lib/mutant/reporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,41 @@ class Reporter
# @return [self]
abstract_method :start

# Report collector state
# Report test start
#
# @param [Env] env
#
# @return [self]
abstract_method :test_start

# Report final state
#
# @param [Runner::Collector] collector
#
# @return [self]
abstract_method :report

# Report final test state
#
# @param [Runner::Collector] collector
#
# @return [self]
abstract_method :test_report

# Report progress on object
#
# @param [Object] object
#
# @return [self]
abstract_method :progress

# Report progress on object
#
# @param [Object] object
#
# @return [self]
abstract_method :test_progress

# The reporter delay
#
# @return [Float]
Expand Down
28 changes: 28 additions & 0 deletions lib/mutant/reporter/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,16 @@ def start(env)
self
end

# Report test start
#
# @param [Env] env
#
# @return [self]
def test_start(env)
write(format.test_start(env))
self
end

# Report progress object
#
# @param [Parallel::Status] status
Expand All @@ -38,6 +48,14 @@ def progress(status)
self
end

# Report progress object
#
# @return [self]
def test_progress(status)
write(format.test_progress(status))
self
end

# Report delay in seconds
#
# @return [Float]
Expand Down Expand Up @@ -65,6 +83,16 @@ def report(env)
self
end

# Report env
#
# @param [Result::Env] env
#
# @return [self]
def test_report(env)
Printer::Test::EnvResult.call(output: output, object: env)
self
end

private

def write(frame)
Expand Down
Loading

0 comments on commit 6691b0a

Please sign in to comment.