parent
1047e0e660
commit
9ceb2bd650
10 changed files with 298 additions and 3 deletions
|
@ -1,3 +1,3 @@
|
||||||
---
|
---
|
||||||
threshold: 16
|
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]
|
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.test_files = `git ls-files -- spec/{unit,integration}`.split("\n")
|
||||||
gem.extra_rdoc_files = %w[LICENSE]
|
gem.extra_rdoc_files = %w[LICENSE]
|
||||||
gem.executables = %w[mutant]
|
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
|
mutation_generation: true
|
||||||
expected_errors: {}
|
expected_errors: {}
|
||||||
exclude: []
|
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
|
- name: axiom
|
||||||
namespace: Axiom
|
namespace: Axiom
|
||||||
repo_uri: 'https://github.com/dkubb/axiom.git'
|
repo_uri: 'https://github.com/dkubb/axiom.git'
|
||||||
|
|
|
@ -167,6 +167,7 @@ module MutantSpec
|
||||||
repo_path.join('Gemfile').open('a') do |file|
|
repo_path.join('Gemfile').open('a') do |file|
|
||||||
file << "gem 'mutant', path: '#{relative}'\n"
|
file << "gem 'mutant', path: '#{relative}'\n"
|
||||||
file << "gem 'mutant-rspec', 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"
|
file << "eval_gemfile File.expand_path('#{relative.join('Gemfile.shared')}')\n"
|
||||||
end
|
end
|
||||||
lockfile = repo_path.join('Gemfile.lock')
|
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