2014-06-30 09:55:58 -04:00
|
|
|
require 'rspec/core'
|
|
|
|
|
2012-12-07 10:55:53 -05:00
|
|
|
module Mutant
|
2014-06-28 19:04:18 -04:00
|
|
|
class Integration
|
2015-05-16 18:10:00 -04:00
|
|
|
# Rspec integration
|
|
|
|
#
|
|
|
|
# This looks so complicated, because rspec:
|
|
|
|
#
|
|
|
|
# * Keeps its state global in RSpec.world and lots of other places
|
|
|
|
# * There is no API to "just run a subset of examples", the examples
|
|
|
|
# need to be selected in-place via mutating the `RSpec.filtered_examples`
|
|
|
|
# datastructure
|
|
|
|
# * Does not maintain a unique identification for an example,
|
|
|
|
# aside the instances of `RSpec::Core::Example` objects itself.
|
2015-07-15 10:04:45 -04:00
|
|
|
# For that reason identifying examples by:
|
2015-05-16 18:10:00 -04:00
|
|
|
# * full description
|
|
|
|
# * location
|
2015-07-15 10:04:45 -04:00
|
|
|
# Is NOT enough. It would not be unique. So we add an "example index"
|
2015-05-16 18:10:00 -04:00
|
|
|
# for unique reference.
|
2014-06-28 19:04:18 -04:00
|
|
|
class Rspec < self
|
2014-05-22 22:33:15 -04:00
|
|
|
|
2015-06-21 10:44:33 -04:00
|
|
|
ALL_EXPRESSION = Expression::Namespace::Recursive.new(scope_name: nil)
|
2015-05-16 18:10:00 -04:00
|
|
|
EXPRESSION_CANDIDATE = /\A([^ ]+)(?: )?/.freeze
|
2014-11-27 11:34:08 -05:00
|
|
|
LOCATION_DELIMITER = ':'.freeze
|
|
|
|
EXIT_SUCCESS = 0
|
|
|
|
CLI_OPTIONS = IceNine.deep_freeze(%w[spec --fail-fast])
|
2014-07-12 12:38:51 -04:00
|
|
|
|
2015-06-21 10:44:33 -04:00
|
|
|
private_constant(*constants(false))
|
|
|
|
|
2014-11-27 11:34:08 -05:00
|
|
|
register 'rspec'
|
2014-07-12 12:38:51 -04:00
|
|
|
|
2014-11-27 11:34:08 -05:00
|
|
|
# Initialize rspec integration
|
2014-07-12 12:38:51 -04:00
|
|
|
#
|
2014-11-27 11:34:08 -05:00
|
|
|
# @return [undefined]
|
2014-07-12 12:38:51 -04:00
|
|
|
#
|
|
|
|
# @api private
|
2015-06-28 22:10:04 -04:00
|
|
|
def initialize(*)
|
|
|
|
super
|
2014-11-27 11:34:08 -05:00
|
|
|
@output = StringIO.new
|
|
|
|
@runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new(CLI_OPTIONS))
|
|
|
|
@world = RSpec.world
|
2014-07-12 12:38:51 -04:00
|
|
|
end
|
|
|
|
|
2014-06-28 19:04:18 -04:00
|
|
|
# Setup rspec integration
|
2013-01-18 15:38:46 -05:00
|
|
|
#
|
|
|
|
# @return [self]
|
|
|
|
#
|
|
|
|
# @api private
|
2013-07-17 13:59:44 -04:00
|
|
|
def setup
|
2014-11-27 11:34:08 -05:00
|
|
|
@runner.setup($stderr, @output)
|
2013-01-18 15:38:46 -05:00
|
|
|
self
|
|
|
|
end
|
2013-08-04 12:49:42 -04:00
|
|
|
memoize :setup
|
|
|
|
|
2015-07-03 11:21:39 -04:00
|
|
|
# Run a collection of tests
|
2014-05-22 22:33:15 -04:00
|
|
|
#
|
2014-11-27 11:34:08 -05:00
|
|
|
# @param [Enumerable<Mutant::Test>] tests
|
2014-04-28 15:17:25 -04:00
|
|
|
#
|
2015-01-24 18:12:15 -05:00
|
|
|
# @return [Result::Test]
|
2014-06-28 16:52:47 -04:00
|
|
|
#
|
2014-07-03 17:16:12 -04:00
|
|
|
# rubocop:disable MethodLength
|
|
|
|
#
|
2015-07-01 23:35:54 -04:00
|
|
|
# @api private
|
2014-11-27 11:34:08 -05:00
|
|
|
def call(tests)
|
2015-05-16 18:10:00 -04:00
|
|
|
examples = tests.map(&all_tests_index.method(:fetch))
|
2014-11-27 11:34:08 -05:00
|
|
|
filter_examples(&examples.method(:include?))
|
2014-07-03 17:16:12 -04:00
|
|
|
start = Time.now
|
2015-05-16 18:10:00 -04:00
|
|
|
passed = @runner.run_specs(@world.ordered_example_groups).equal?(EXIT_SUCCESS)
|
2014-11-27 11:34:08 -05:00
|
|
|
@output.rewind
|
2014-07-03 17:16:12 -04:00
|
|
|
Result::Test.new(
|
2014-12-22 12:54:18 -05:00
|
|
|
tests: tests,
|
2014-11-27 11:34:08 -05:00
|
|
|
output: @output.read,
|
2014-07-03 17:16:12 -04:00
|
|
|
runtime: Time.now - start,
|
2014-11-27 11:34:08 -05:00
|
|
|
passed: passed
|
2014-05-22 22:33:15 -04:00
|
|
|
)
|
2014-04-28 15:17:25 -04:00
|
|
|
end
|
|
|
|
|
2015-07-03 11:21:39 -04:00
|
|
|
# Available tests
|
2014-04-28 15:17:25 -04:00
|
|
|
#
|
2014-05-22 22:33:15 -04:00
|
|
|
# @return [Enumerable<Test>]
|
2014-04-28 15:17:25 -04:00
|
|
|
#
|
|
|
|
# @api private
|
2014-05-22 22:33:15 -04:00
|
|
|
def all_tests
|
2014-11-27 11:34:08 -05:00
|
|
|
all_tests_index.keys
|
2014-04-28 15:17:25 -04:00
|
|
|
end
|
2014-05-22 22:33:15 -04:00
|
|
|
memoize :all_tests
|
2014-04-28 15:17:25 -04:00
|
|
|
|
2014-05-22 22:33:15 -04:00
|
|
|
private
|
|
|
|
|
2015-07-03 11:21:39 -04:00
|
|
|
# Index of available tests
|
2014-06-28 16:47:46 -04:00
|
|
|
#
|
2014-11-27 11:34:08 -05:00
|
|
|
# @return [Hash<Test, RSpec::Core::Example]
|
2014-06-28 16:47:46 -04:00
|
|
|
#
|
|
|
|
# @api private
|
2014-11-27 11:34:08 -05:00
|
|
|
def all_tests_index
|
2014-12-21 16:05:16 -05:00
|
|
|
all_examples.each_with_index.each_with_object({}) do |(example, example_index), index|
|
|
|
|
index[parse_example(example, example_index)] = example
|
2014-07-02 16:27:04 -04:00
|
|
|
end
|
2014-06-28 16:47:46 -04:00
|
|
|
end
|
2014-11-27 11:34:08 -05:00
|
|
|
memoize :all_tests_index
|
2014-06-28 16:47:46 -04:00
|
|
|
|
2014-11-27 11:34:08 -05:00
|
|
|
# Parse example into test
|
|
|
|
#
|
2014-12-21 16:05:16 -05:00
|
|
|
# @param [RSpec::Core::Example] example
|
|
|
|
# @param [Fixnum] index
|
2014-04-28 15:17:25 -04:00
|
|
|
#
|
2014-11-27 11:34:08 -05:00
|
|
|
# @return [Test]
|
2014-04-28 15:17:25 -04:00
|
|
|
#
|
|
|
|
# @api private
|
2014-12-21 16:05:16 -05:00
|
|
|
def parse_example(example, index)
|
2015-05-16 19:02:04 -04:00
|
|
|
metadata = example.metadata
|
|
|
|
location = metadata.fetch(:location)
|
2014-11-27 11:34:08 -05:00
|
|
|
full_description = metadata.fetch(:full_description)
|
2015-05-16 18:10:00 -04:00
|
|
|
|
2014-11-27 11:34:08 -05:00
|
|
|
Test.new(
|
2014-12-21 16:05:16 -05:00
|
|
|
id: "rspec:#{index}:#{location}/#{full_description}",
|
2015-05-16 19:02:04 -04:00
|
|
|
expression: parse_expression(metadata)
|
2014-11-27 11:34:08 -05:00
|
|
|
)
|
2014-04-28 15:17:25 -04:00
|
|
|
end
|
|
|
|
|
2015-05-16 19:02:04 -04:00
|
|
|
# Parse metadata into expression
|
|
|
|
#
|
|
|
|
# @param [RSpec::Core::Example::Medatada] metadata
|
|
|
|
#
|
|
|
|
# @return [Expression]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
def parse_expression(metadata)
|
|
|
|
if metadata.key?(:mutant_expression)
|
2015-06-21 10:44:33 -04:00
|
|
|
expression_parser.(metadata.fetch(:mutant_expression))
|
2015-05-16 19:02:04 -04:00
|
|
|
else
|
|
|
|
match = EXPRESSION_CANDIDATE.match(metadata.fetch(:full_description))
|
2015-06-21 10:44:33 -04:00
|
|
|
expression_parser.try_parse(match.captures.first) || ALL_EXPRESSION
|
2015-05-16 19:02:04 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2015-07-03 11:21:39 -04:00
|
|
|
# Available rspec examples
|
2013-08-04 12:49:42 -04:00
|
|
|
#
|
2014-11-27 11:34:08 -05:00
|
|
|
# @return [Array<String, RSpec::Core::Example]
|
2013-08-04 12:49:42 -04:00
|
|
|
#
|
|
|
|
# @api private
|
2014-11-27 11:34:08 -05:00
|
|
|
def all_examples
|
2014-12-12 20:06:04 -05:00
|
|
|
@world.example_groups.flat_map(&:descendants).flat_map(&:examples).select do |example|
|
|
|
|
example.metadata.fetch(:mutant, true)
|
|
|
|
end
|
2013-08-04 12:49:42 -04:00
|
|
|
end
|
2014-07-12 12:38:51 -04:00
|
|
|
|
2014-11-27 11:34:08 -05:00
|
|
|
# Filter examples
|
|
|
|
#
|
|
|
|
# @param [#call] predicate
|
|
|
|
#
|
|
|
|
# @return [undefined]
|
|
|
|
#
|
|
|
|
# @api private
|
|
|
|
def filter_examples(&predicate)
|
|
|
|
@world.filtered_examples.each_value do |examples|
|
|
|
|
examples.keep_if(&predicate)
|
2014-07-12 12:38:51 -04:00
|
|
|
end
|
2014-11-27 11:34:08 -05:00
|
|
|
end
|
2014-07-12 12:38:51 -04:00
|
|
|
|
2014-06-28 19:04:18 -04:00
|
|
|
end # Rspec
|
|
|
|
end # Integration
|
2013-06-14 14:54:02 -04:00
|
|
|
end # Mutant
|