danmayer/coverband

View on GitHub
lib/coverband/collectors/delta.rb

Summary

Maintainability
A
2 hrs
Test Coverage
# frozen_string_literal: true

module Coverband
  module Collectors
    class Delta
      @@previous_coverage = {}
      @@stubs = {}

      attr_reader :current_coverage

      def initialize(current_coverage)
        @current_coverage = current_coverage
      end

      class RubyCoverage
        def self.results
          if Coverband.configuration.use_oneshot_lines_coverage
            ::Coverage.result(clear: true, stop: false)
          else
            ::Coverage.peek_result
          end
        end
      end

      def self.results(process_coverage = RubyCoverage)
        coverage_results = process_coverage.results
        new(coverage_results).results
      end

      def results
        if Coverband.configuration.use_oneshot_lines_coverage
          transform_oneshot_lines_results(current_coverage)
        else
          new_results = generate
          @@previous_coverage = current_coverage unless Coverband.configuration.simulate_oneshot_lines_coverage
          new_results
        end
      end

      def self.reset
        @@previous_coverage = {}
        @@project_directory = File.expand_path(Coverband.configuration.root)
        @@ignore_patterns = Coverband.configuration.ignore
      end

      private

      def generate
        current_coverage.each_with_object({}) do |(file, line_counts), new_results|
          ###
          # Eager filter:
          # Normally I would break this out into additional methods
          # and improve the readability but this is in a tight loop
          # on the critical performance path, and any refactoring I come up with
          # would slow down the performance.
          ###
          next unless @@ignore_patterns.none? { |pattern| file.match(pattern) } &&
            file.start_with?(@@project_directory)

          # This handles Coverage branch support, setup by default in
          # simplecov 0.18.x
          arr_line_counts = line_counts.is_a?(Hash) ? line_counts[:lines] : line_counts
          new_results[file] = if @@previous_coverage && @@previous_coverage[file]
            prev_line_counts = @@previous_coverage[file].is_a?(Hash) ? @@previous_coverage[file][:lines] : @@previous_coverage[file]
            array_diff(arr_line_counts, prev_line_counts)
          else
            arr_line_counts
          end
        end
      end

      def array_diff(latest, original)
        latest.map.with_index do |v, i|
          [0, v - original[i]].max if v && original[i]
        end
      end

      def transform_oneshot_lines_results(results)
        results.each_with_object({}) do |(file, coverage), new_results|
          ###
          # Eager filter:
          # Normally I would break this out into additional methods
          # and improve the readability but this is in a tight loop
          # on the critical performance path, and any refactoring I come up with
          # would slow down the performance.
          ###
          next unless @@ignore_patterns.none? { |pattern| file.match(pattern) } &&
            file.start_with?(@@project_directory)

          @@stubs[file] ||= ::Coverage.line_stub(file)
          transformed_line_counts = coverage[:oneshot_lines].each_with_object(@@stubs[file].dup) { |line_number, line_counts|
            line_counts[line_number - 1] = 1
          }
          new_results[file] = transformed_line_counts
        end
      end
    end
  end
end