parent
1047e0e660
commit
9ceb2bd650
10 changed files with 298 additions and 3 deletions
|
@ -1,3 +1,3 @@
|
|||
---
|
||||
threshold: 16
|
||||
total_score: 1364
|
||||
total_score: 1395
|
||||
|
|
174
lib/mutant/integration/minitest.rb
Normal file
174
lib/mutant/integration/minitest.rb
Normal file
|
@ -0,0 +1,174 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'minitest'
|
||||
require 'mutant/minitest/coverage'
|
||||
|
||||
module Minitest
|
||||
# Prevent autorun from running tests when the VM closes
|
||||
#
|
||||
# Mutant needs control about the exit status of the VM and
|
||||
# the moment of test execution
|
||||
#
|
||||
# @api private
|
||||
#
|
||||
# @return [nil]
|
||||
def self.autorun; end
|
||||
end # Minitest
|
||||
|
||||
module Mutant
|
||||
class Integration
|
||||
# Minitest integration
|
||||
class Minitest < self
|
||||
TEST_FILE_PATTERN = './test/**/{test_*,*_test}.rb'
|
||||
IDENTIFICATION_FORMAT = 'minitest:%s#%s'
|
||||
|
||||
private_constant(*constants(false))
|
||||
|
||||
# Compose a runnable with test method
|
||||
#
|
||||
# This looks actually like a missing object on minitest implementation.
|
||||
class TestCase
|
||||
include Adamantium, Concord.new(:klass, :test_method)
|
||||
|
||||
# Identification string
|
||||
#
|
||||
# @return [String]
|
||||
def identification
|
||||
IDENTIFICATION_FORMAT % [klass, test_method]
|
||||
end
|
||||
memoize :identification
|
||||
|
||||
# Run test case
|
||||
#
|
||||
# @param [Object] reporter
|
||||
#
|
||||
# @return [Boolean]
|
||||
def call(reporter)
|
||||
::Minitest::Runnable.run_one_method(klass, test_method, reporter)
|
||||
reporter.passed?
|
||||
end
|
||||
|
||||
# Cover expression syntaxes
|
||||
#
|
||||
# @return [String, nil]
|
||||
def expression_syntax
|
||||
klass.resolve_cover_expression
|
||||
end
|
||||
|
||||
end # TestCase
|
||||
|
||||
private_constant(*constants(false))
|
||||
|
||||
# Setup integration
|
||||
#
|
||||
# @return [self]
|
||||
def setup
|
||||
Pathname.glob(TEST_FILE_PATTERN)
|
||||
.map(&:to_s)
|
||||
.each(&method(:require))
|
||||
|
||||
self
|
||||
end
|
||||
|
||||
# Call test integration
|
||||
#
|
||||
# @param [Array<Tests>] tests
|
||||
#
|
||||
# @return [Result::Test]
|
||||
#
|
||||
# rubocop:disable MethodLength
|
||||
#
|
||||
# ignore :reek:TooManyStatements
|
||||
def call(tests)
|
||||
test_cases = tests.map(&all_tests_index.method(:fetch))
|
||||
output = StringIO.new
|
||||
start = Time.now
|
||||
|
||||
reporter = ::Minitest::SummaryReporter.new(output)
|
||||
|
||||
reporter.start
|
||||
|
||||
test_cases.each do |test|
|
||||
break unless test.call(reporter)
|
||||
end
|
||||
|
||||
output.rewind
|
||||
|
||||
Result::Test.new(
|
||||
passed: reporter.passed?,
|
||||
tests: tests,
|
||||
output: output.read,
|
||||
runtime: Time.now - start
|
||||
)
|
||||
end
|
||||
|
||||
# All tests exposed by this integration
|
||||
#
|
||||
# @return [Array<Test>]
|
||||
def all_tests
|
||||
all_tests_index.keys
|
||||
end
|
||||
memoize :all_tests
|
||||
|
||||
private
|
||||
|
||||
# The index of all tests to runnable test cases
|
||||
#
|
||||
# @return [Hash<Test,TestCase>]
|
||||
def all_tests_index
|
||||
all_test_cases.each_with_object({}) do |test_case, index|
|
||||
index[construct_test(test_case)] = test_case
|
||||
end
|
||||
end
|
||||
memoize :all_tests_index
|
||||
|
||||
# Construct test from test case
|
||||
#
|
||||
# @param [TestCase]
|
||||
#
|
||||
# @return [Test]
|
||||
def construct_test(test_case)
|
||||
Test.new(
|
||||
id: test_case.identification,
|
||||
expression: config.expression_parser.call(test_case.expression_syntax)
|
||||
)
|
||||
end
|
||||
|
||||
# All minitest test cases
|
||||
#
|
||||
# Intentional utility method.
|
||||
#
|
||||
# @return [Array<TestCase>]
|
||||
def all_test_cases
|
||||
::Minitest::Runnable
|
||||
.runnables
|
||||
.select(&method(:allow_runnable?))
|
||||
.flat_map(&method(:test_case))
|
||||
end
|
||||
|
||||
# Test if runnable qualifies for mutation testing
|
||||
#
|
||||
# @param [Class]
|
||||
#
|
||||
# @return [Bool]
|
||||
#
|
||||
# ignore :reek:UtilityFunction
|
||||
def allow_runnable?(klass)
|
||||
!klass.equal?(::Minitest::Runnable) && klass.resolve_cover_expression
|
||||
end
|
||||
|
||||
# Turn a minitest runnable into its test cases
|
||||
#
|
||||
# Intentional utility method.
|
||||
#
|
||||
# @param [Object] runnable
|
||||
#
|
||||
# @return [Array<TestCase>]
|
||||
#
|
||||
# ignore :reek:UtilityFunction
|
||||
def test_case(runnable)
|
||||
runnable.runnable_methods.map { |method| TestCase.new(runnable, method) }
|
||||
end
|
||||
end # Minitest
|
||||
end # Integration
|
||||
end # Mutant
|
56
lib/mutant/minitest/coverage.rb
Normal file
56
lib/mutant/minitest/coverage.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'minitest'
|
||||
|
||||
module Mutant
|
||||
module Minitest
|
||||
module Coverage
|
||||
# Setup coverage declaration for current class
|
||||
#
|
||||
# @param [String]
|
||||
#
|
||||
# @example
|
||||
#
|
||||
# class MyTest < MiniTest::Test
|
||||
# cover 'MyCode*'
|
||||
#
|
||||
# def test_some_stuff
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# @api public
|
||||
def cover(expression)
|
||||
fail "#{self} already declares to cover: #{@covers}" if @covers
|
||||
|
||||
@cover_expression = expression
|
||||
end
|
||||
|
||||
# Effective coverage expression
|
||||
#
|
||||
# @return [String, nil]
|
||||
#
|
||||
# @api private
|
||||
def resolve_cover_expression
|
||||
return @cover_expression if defined?(@cover_expression)
|
||||
|
||||
try_superclass_cover_expression
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Attempt to resolve superclass cover expressio
|
||||
#
|
||||
# @return [String, nil]
|
||||
#
|
||||
# @api private
|
||||
def try_superclass_cover_expression
|
||||
return if superclass.equal?(::Minitest::Runnable)
|
||||
|
||||
superclass.resolve_cover_expression
|
||||
end
|
||||
|
||||
end # Coverage
|
||||
end # Minitest
|
||||
end # Mutant
|
||||
|
||||
Minitest::Test.extend(Mutant::Minitest::Coverage)
|
22
mutant-minitest.gemspec
Normal file
22
mutant-minitest.gemspec
Normal file
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require File.expand_path('lib/mutant/version', __dir__)
|
||||
|
||||
Gem::Specification.new do |gem|
|
||||
gem.name = 'mutant-minitest'
|
||||
gem.version = Mutant::VERSION.dup
|
||||
gem.authors = ['Markus Schirp']
|
||||
gem.email = %w[mbj@schirp-dso.com]
|
||||
gem.description = 'Minitest integration for mutant'
|
||||
gem.summary = gem.description
|
||||
gem.homepage = 'https://github.com/mbj/mutant'
|
||||
gem.license = 'MIT'
|
||||
|
||||
gem.require_paths = %w[lib]
|
||||
gem.files = `git ls-files -- lib/mutant/{minitest,/integration/minitest.rb}`.split("\n")
|
||||
gem.test_files = `git ls-files -- spec/integration/mutant/minitest.rb`.split("\n")
|
||||
gem.extra_rdoc_files = %w[LICENSE]
|
||||
|
||||
gem.add_runtime_dependency('minitest', '~> 5.11')
|
||||
gem.add_runtime_dependency('mutant', "~> #{gem.version}")
|
||||
end
|
|
@ -14,9 +14,9 @@ Gem::Specification.new do |gem|
|
|||
|
||||
gem.require_paths = %w[lib]
|
||||
|
||||
mutant_integration_files = `git ls-files -- lib/mutant/integration/*.rb`.split("\n")
|
||||
exclusion = `git ls-files -- lib/mutant/{minitest,integration}`.split("\n")
|
||||
|
||||
gem.files = `git ls-files`.split("\n") - mutant_integration_files
|
||||
gem.files = `git ls-files`.split("\n") - exclusion
|
||||
gem.test_files = `git ls-files -- spec/{unit,integration}`.split("\n")
|
||||
gem.extra_rdoc_files = %w[LICENSE]
|
||||
gem.executables = %w[mutant]
|
||||
|
|
10
spec/integration/mutant/minitest_spec.rb
Normal file
10
spec/integration/mutant/minitest_spec.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
RSpec.describe 'minitest integration', mutant: false do
|
||||
|
||||
let(:base_cmd) { 'bundle exec mutant -I test -I lib --require test_app --use minitest' }
|
||||
|
||||
let(:gemfile) { 'Gemfile.minitest' }
|
||||
|
||||
it_behaves_like 'framework integration'
|
||||
end
|
|
@ -41,6 +41,16 @@
|
|||
mutation_generation: true
|
||||
expected_errors: {}
|
||||
exclude: []
|
||||
- name: auom
|
||||
namespace: AUOM
|
||||
repo_uri: 'https://github.com/mbj/auom.git'
|
||||
repo_ref: 'origin/add/minitest'
|
||||
ruby_glob_pattern: '**/*.rb'
|
||||
integration: minitest
|
||||
mutation_coverage: true
|
||||
mutation_generation: true
|
||||
expected_errors: {}
|
||||
exclude: []
|
||||
- name: axiom
|
||||
namespace: Axiom
|
||||
repo_uri: 'https://github.com/dkubb/axiom.git'
|
||||
|
|
|
@ -167,6 +167,7 @@ module MutantSpec
|
|||
repo_path.join('Gemfile').open('a') do |file|
|
||||
file << "gem 'mutant', path: '#{relative}'\n"
|
||||
file << "gem 'mutant-rspec', path: '#{relative}'\n"
|
||||
file << "gem 'mutant-minitest', path: '#{relative}'\n"
|
||||
file << "eval_gemfile File.expand_path('#{relative.join('Gemfile.shared')}')\n"
|
||||
end
|
||||
lockfile = repo_path.join('Gemfile.lock')
|
||||
|
|
6
test_app/Gemfile.minitest
Normal file
6
test_app/Gemfile.minitest
Normal file
|
@ -0,0 +1,6 @@
|
|||
source 'https://rubygems.org'
|
||||
|
||||
gem 'minitest', '~> 5.11'
|
||||
gem 'mutant', path: '../'
|
||||
gem 'mutant-minitest', path: '../'
|
||||
gem 'adamantium'
|
16
test_app/test/unit/test_app/literal_test.rb
Normal file
16
test_app/test/unit/test_app/literal_test.rb
Normal file
|
@ -0,0 +1,16 @@
|
|||
require 'minitest/autorun'
|
||||
require 'mutant/minitest/coverage'
|
||||
|
||||
class LiteralTest < Minitest::Test
|
||||
cover 'TestApp::Literal*'
|
||||
|
||||
def test_command
|
||||
object = ::TestApp::Literal.new
|
||||
|
||||
assert_equal(object, object.command('x'))
|
||||
end
|
||||
|
||||
def test_string
|
||||
assert_equal('string', ::TestApp::Literal.new.string)
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue