lib/mutest/result.rb
module Mutest
  # Namespace and mixin module for results
  module Result
    # Coverage mixin
    module Coverage
      FULL_COVERAGE = Rational(1).freeze
      private_constant(*constants(false))

      # Observed coverage
      #
      # @return [Rational]
      def coverage
        if amount_mutation_results.zero?
          FULL_COVERAGE
        else
          Rational(amount_mutations_killed, amount_mutation_results)
        end
      end
    end

    # Class level mixin
    module ClassMethods
      # Generate a sum method from name and collection
      #
      # @param [Symbol] name
      #   the attribute name on collection item and method name to use
      #
      # @param [Symbol] collection
      #   the attribute name used to receive collection
      #
      # @return [undefined]
      def sum(name, collection)
        define_method(name) do
          public_send(collection).map(&name).reduce(0, :+)
        end
        memoize(name)
      end
    end

    private_constant(*constants(false))

    # Mutest overhead running mutatet tests
    #
    # This is NOT the overhead of mutation testing, just an engine specific
    # measurement for the efficiency of the parallelization engine, kill
    # isolation etc.
    #
    # @return [Float]
    def overhead
      runtime - killtime
    end

    # Hook called when module gets included
    #
    # @param [Class, Module] host
    #
    # @return [undefined]
    def self.included(host)
      host.class_eval do
        include Adamantium
        extend ClassMethods
      end
    end

    # Env result object
    class Env
      include Anima.new(
        :env,
        :runtime,
        :subject_results
      )
      include Result
      include Coverage

      # Test if run is successful
      #
      # @return [Boolean]
      def success?
        coverage.eql?(Rational(1))
      end
      memoize :success?

      # Failed subject results
      #
      # @return [Array<Result::Subject>]
      def failed_subject_results
        subject_results.reject(&:success?)
      end

      sum :amount_mutation_results, :subject_results
      sum :amount_mutations_alive,  :subject_results
      sum :amount_mutations_killed, :subject_results
      sum :killtime,                :subject_results

      # Amount of mutations
      #
      # @return [Integer]
      def amount_mutations
        env.mutations.length
      end

      # Amount of subjects
      #
      # @return [Integer]
      def amount_subjects
        env.subjects.length
      end
    end

    # Test result
    class Test
      include Anima.new(
        :output,
        :passed,
        :runtime,
        :tests
      )
      include Result
    end

    # Subject result
    class Subject
      include Anima.new(
        :mutation_results,
        :subject,
        :tests
      )
      include Result
      include Coverage

      sum :killtime, :mutation_results
      sum :runtime,  :mutation_results

      # Test if subject was processed successful
      #
      # @return [Boolean]
      def success?
        alive_mutation_results.empty?
      end

      # Alive mutations
      #
      # @return [Array<Result::Mutation>]
      def alive_mutation_results
        mutation_results.reject(&:success?)
      end
      memoize :alive_mutation_results

      # Amount of mutations
      #
      # @return [Integer]
      def amount_mutation_results
        mutation_results.length
      end

      # Amount of mutations
      #
      # @return [Integer]
      def amount_mutations
        subject.mutations.length
      end

      # Number of killed mutations
      #
      # @return [Integer]
      def amount_mutations_killed
        killed_mutation_results.length
      end

      # Number of alive mutations
      #
      # @return [Integer]
      def amount_mutations_alive
        alive_mutation_results.length
      end

      private

      # Killed mutation results
      #
      # @return [Array<Result::Mutation>]
      def killed_mutation_results
        mutation_results.select(&:success?)
      end
      memoize :killed_mutation_results
    end

    # Mutation result
    class Mutation
      include Anima.new(
        :mutation,
        :test_result
      )
      include Result

      # The runtime
      #
      # @return [Float]
      def runtime
        test_result.runtime
      end

      # The time spent on killing
      #
      # @return [Float]
      #
      # @api private
      alias_method :killtime, :runtime

      # Test if mutation was handled successfully
      #
      # @return [Boolean]
      def success?
        mutation.class.success?(test_result)
      end
    end
  end
end
