GoBoundless/dyph3

View on GitHub
lib/dyph/support/sanity_check.rb

Summary

Maintainability
A
0 mins
Test Coverage
module Dyph
  module Support
    module SanityCheck
      extend self

      # rubocop:disable Metrics/AbcSize
      def ensure_no_lost_data(left, base, right, final_result)
        result_word_map = {}
        final_result.each do |result_block|
          blocks = case result_block
            when Outcome::Resolved then result_block.result
            when Outcome::Conflicted then [result_block.left, result_block.right].flatten
            else raise "Unknown block type, #{result_block[:type]}"
          end
          count_blocks(blocks, result_word_map)
        end

        left_word_map, base_word_map, right_word_map = [left, base, right].map { |str| count_blocks(str) }

        # new words are words that are in left or right, but not in base
        new_left_words = subtract_words(left_word_map, base_word_map)
        new_right_words = subtract_words(right_word_map, base_word_map)

        # now make sure all new words are somewhere in the result
        missing_new_left_words = subtract_words(new_left_words, result_word_map)
        missing_new_right_words = subtract_words(new_right_words, result_word_map)

        if missing_new_left_words.any? || missing_new_right_words.any?
          raise BadMergeException.new(final_result)
        end
      end
      # rubocop:enable Metrics/AbcSize

      private
        def count_blocks(blocks, hash={})
          blocks.reduce(hash) do |map, block|
            map[block] ||= 0
            map[block] += 1
            map
          end
        end

        def subtract_words(left_map, right_map)
          remaining_words = {}
          
          left_map.each do |word, count|
            count_in_right = right_map[word] || 0
            
            new_count = count - count_in_right
            remaining_words[word] = new_count if new_count > 0
          end
          
          remaining_words
        end
    end

    class BadMergeException < StandardError
      attr_accessor :merge_result

      def initialize(merge_result)
        @merge_result = merge_result
      end

      def inspect
        "<#{self.class}: #{merge_result}>"
      end

      def to_s
        inspect
      end
    end
  end
end