lib/blood_contracts/runner.rb in blood_contracts-0.1.0 vs lib/blood_contracts/runner.rb in blood_contracts-0.2.0

- old
+ new

@@ -1,95 +1,91 @@ +require_relative "contracts/validator" +require_relative "contracts/matcher" +require_relative "contracts/description" + module BloodContracts class Runner extend Dry::Initializer param :checking_proc - option :context, optional: true option :suite + option :storage, default: -> { suite.storage } option :iterations, ->(v) do v = ENV["iterations"] if ENV["iterations"] v.to_i.positive? ? v.to_i : 1 end, default: -> { 1 } - - option :time_to_run, ->(v) do v = ENV["duration"] if ENV["duration"] v.to_f if v.to_f.positive? end, optional: true + option :context, optional: true option :stop_on_unexpected, default: -> { false } + option :statistics, default: -> { Statistics.new(iterations) } + option :matcher, default: -> { Contracts::Matcher.new(suite.contract) } + option :validator, default: -> { Contracts::Validator.new(suite.contract) } + option :contract_description, default: -> do + Contracts::Description.call(suite.contract) + end + def call iterate do - unexpected = match_rules(*run) do |input, output, rules| - suite.storage.save_run( - input: input, output: output, rules: rules, context: context, - ) - end.empty? - throw :unexpected_behavior, :stop if stop_on_unexpected && unexpected + next if match_rules?(matches_storage: statistics) do + input = suite.data_generator.call + [input, checking_proc.call(input)] + end + throw :unexpected_behavior, :stop if stop_on_unexpected end - valid? - end - - def valid? return if stopped_by_unexpected_behavior? - return if found_unexpected_behavior? - last_run_stats = run_stats - expectations_checks.all? do |rule, check| - percent = last_run_stats[rule.name]&.percent || 0.0 - check.call(percent, rule) - end + validator.valid?(statistics) end - def found_unexpected_behavior? - run_stats.key?(Storage::UNDEFINED_RULE) - end - + # FIXME: Move to locales def failure_message intro = "expected that given Proc would meet the contract:" - if found_unexpected_behavior? + if validator.expected_behavior? "#{intro}\n#{contract_description}\n"\ - " during #{iterations} run(s) but got unexpected behavior.\n\n"\ - "For further investigations open: #{unexpected_behavior_report_path}/" + " during #{iterations} run(s) but got:\n#{statistics}\n\n"\ + "For further investigations open: #{storage.suggestion}" else "#{intro}\n#{contract_description}\n"\ - " during #{iterations} run(s) but got:\n#{stats_description}\n\n"\ - "For further investigations open: #{suite.storage.path}" + " during #{iterations} run(s) but got unexpected behavior.\n\n"\ + "For further investigations open: #{storage.unexpected_suggestion}" end end - def unexpected_behavior_report_path - File.join(suite.storage.path, Storage::UNDEFINED_RULE.to_s) - end - + # FIXME: Move to locales def description "meet the contract:\n#{contract_description} \n"\ - " during #{iterations} run(s). Stats:\n#{stats_description}\n\n"\ - "For further investigations open: #{suite.storage.path}\n" + " during #{iterations} run(s). Stats:\n#{statistics}\n\n"\ + "For further investigations open: #{storage.suggestion}\n" end - private + protected - def run - input = suite.data_generator.call - [input, checking_proc.call(input)] + def match_rules?(matches_storage:) + matcher.call(*yield, storage: matches_storage) do |rules, options| + storage.store(options: options, rules: rules, context: context) + end + rescue StandardError => error + # FIXME: Possible recursion? + # Write test about error in the storage#store (e.g. writing error) + store_exception(error, input, output, context) + raise end def stopped_by_unexpected_behavior? @_stopped_by_unexpected_behavior == :stop end - def stats - suite.storage.stats - end - def iterate - run_iterations = iterations + run_iterations ||= iterations if time_to_run run_iterations = iterations_count_from_time_to_run { yield } @iterations = run_iterations + 1 end @@ -102,82 +98,18 @@ def iterations_count_from_time_to_run time_per_action = Benchmark.measure { yield } (time_to_run / time_per_action.real).ceil end - def match_rules(input, output) - matched_rules = suite.contract.select do |_name, rule| - rule.check.call(input, output) - end.keys - matched_rules = [Storage::UNDEFINED_RULE] if matched_rules.empty? - yield(input, output, matched_rules) - - matched_rules - # FIXME: Possible recursion? - # Write test about error in the yield (e.g. writing error) - rescue => e - yield [Storage::UNDEFINED_RULE] - raise e - end - - def threshold_check(value, rule) - value > rule.threshold - end - - def limit_check(value, rule) - value <= rule.limit - end - - def expectations_checks - Hash[ - suite.contract.map do |name, rule| - if rule.threshold - [rule.merge(name: name), method(:threshold_check)] - elsif rule.limit - [rule.merge(name: name), method(:limit_check)] - else - nil - end - end.compact - ] - end - - def contract_description - suite.contract.map do |name, rule| - rule_description = " - '#{name}' " - if rule.threshold - rule_description << <<~TEXT - in more then #{(rule.threshold * 100).round(2)}% of cases; - TEXT - elsif rule.limit - rule_description << <<~TEXT - in less then #{(rule.limit * 100).round(2)}% of cases; - TEXT - else - next - end - rule_description - end.compact.join - end - - def stats_description - run_stats.map do |name, occasions| - " - '#{name}' happened #{occasions.times} time(s) "\ - "(#{(occasions.percent * 100).round(2)}% of the time)" - end.join("; \n") - end - - def run_stats - Hash[ - stats.map do |rule_name, times| - [ - rule_name, - Hashie::Mash.new( - times: times, - percent: (times.to_f / iterations), - ), - ] - end - ] + def store_exception(error, input, output, context) + storage.store( + options: { + input: input, + output: output || {}, + meta: {exception: error}, + }, + rules: [Storage::EXCEPTION_CAUGHT], + context: context, + ) end end end