# frozen_string_literal: true require 'rspec/core' module Mutant class Integration # 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` # data structure # * Does not maintain a unique identification for an example, # aside the instances of `RSpec::Core::Example` objects itself. # For that reason identifying examples by: # * full description # * location # Is NOT enough. It would not be unique. So we add an "example index" # for unique reference. # # :reek:TooManyConstants class Rspec < self ALL_EXPRESSION = Expression::Namespace::Recursive.new(scope_name: nil) EXPRESSION_CANDIDATE = /\A([^ ]+)(?: )?/.freeze LOCATION_DELIMITER = ':' EXIT_SUCCESS = 0 CLI_OPTIONS = IceNine.deep_freeze(%w[spec --fail-fast]) TEST_ID_FORMAT = 'rspec:%d:%s/%s' private_constant(*constants(false)) # Initialize rspec integration # # @return [undefined] def initialize(*) super @output = StringIO.new @runner = RSpec::Core::Runner.new(RSpec::Core::ConfigurationOptions.new(CLI_OPTIONS)) @world = RSpec.world end # Setup rspec integration # # @return [self] def setup @runner.setup($stderr, @output) self end memoize :setup # Run a collection of tests # # @param [Enumerable] tests # # @return [Result::Test] # # rubocop:disable MethodLength def call(tests) examples = tests.map(&all_tests_index.method(:fetch)) filter_examples(&examples.method(:include?)) start = Timer.now passed = @runner.run_specs(@world.ordered_example_groups).equal?(EXIT_SUCCESS) @output.rewind Result::Test.new( output: @output.read, passed: passed, runtime: Timer.now - start, tests: tests ) end # Available tests # # @return [Enumerable] def all_tests all_tests_index.keys end memoize :all_tests private # Index of available tests # # @return [Hash