From d13dc2ad1fbd13686cb5019c1af1ad2b4858b982 Mon Sep 17 00:00:00 2001 From: Magnus Bergmark Date: Wed, 20 Nov 2019 14:45:33 +0100 Subject: [PATCH 1/2] Add useful diff representation of Time-like values This should add a more useful diff for Time instances, as well as make the diff output from ActiveSupport::TimeWithZone instances readable. Previously the ActiveSupport::TimeWithZone diff output would contain all the timezones, leading to several thousand lines in the diff. Now all time-like instances will work the same way: Single-line diffs will be of a simple ISO-like string, while the full diff will show which parts of the time actually differs. Timezone is promptly shown, both with name and with GMT offset. --- lib/super_diff/differs.rb | 2 + lib/super_diff/differs/time.rb | 24 +++ .../object_inspection/inspectors.rb | 1 + .../object_inspection/inspectors/time.rb | 13 ++ lib/super_diff/object_inspection/map.rb | 2 + lib/super_diff/operational_sequencers.rb | 1 + .../operational_sequencers/time_like.rb | 21 +++ spec/integration/rspec/eq_matcher_spec.rb | 140 +++++++++++++++++- 8 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 lib/super_diff/differs/time.rb create mode 100644 lib/super_diff/object_inspection/inspectors/time.rb create mode 100644 lib/super_diff/operational_sequencers/time_like.rb diff --git a/lib/super_diff/differs.rb b/lib/super_diff/differs.rb index 8edf4308..da12f71e 100644 --- a/lib/super_diff/differs.rb +++ b/lib/super_diff/differs.rb @@ -7,10 +7,12 @@ module Differs autoload :Empty, "super_diff/differs/empty" autoload :Hash, "super_diff/differs/hash" autoload :MultilineString, "super_diff/differs/multiline_string" + autoload :Time, "super_diff/differs/time" DEFAULTS = [ Array, Hash, + Time, MultilineString, CustomObject, DefaultObject, diff --git a/lib/super_diff/differs/time.rb b/lib/super_diff/differs/time.rb new file mode 100644 index 00000000..10f219b8 --- /dev/null +++ b/lib/super_diff/differs/time.rb @@ -0,0 +1,24 @@ +module SuperDiff + module Differs + class Time < Base + def self.applies_to?(expected, actual) + OperationalSequencers::TimeLike.applies_to?(expected, actual) + end + + def call + operations.to_diff(indent_level: indent_level) + end + + private + + def operations + OperationalSequencers::TimeLike.call( + expected: expected, + actual: actual, + extra_operational_sequencer_classes: extra_operational_sequencer_classes, + extra_diff_formatter_classes: extra_diff_formatter_classes, + ) + end + end + end +end diff --git a/lib/super_diff/object_inspection/inspectors.rb b/lib/super_diff/object_inspection/inspectors.rb index 90ad9e9a..1ac9b28a 100644 --- a/lib/super_diff/object_inspection/inspectors.rb +++ b/lib/super_diff/object_inspection/inspectors.rb @@ -13,6 +13,7 @@ module Inspectors autoload :Hash, "super_diff/object_inspection/inspectors/hash" autoload :Primitive, "super_diff/object_inspection/inspectors/primitive" autoload :String, "super_diff/object_inspection/inspectors/string" + autoload :Time, "super_diff/object_inspection/inspectors/time" end end end diff --git a/lib/super_diff/object_inspection/inspectors/time.rb b/lib/super_diff/object_inspection/inspectors/time.rb new file mode 100644 index 00000000..479a414b --- /dev/null +++ b/lib/super_diff/object_inspection/inspectors/time.rb @@ -0,0 +1,13 @@ +module SuperDiff + module ObjectInspection + module Inspectors + TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%3N %Z %:z".freeze + + Time = InspectionTree.new do + add_text do |time| + "#{time.strftime(TIME_FORMAT)} (#{time.class})" + end + end + end + end +end diff --git a/lib/super_diff/object_inspection/map.rb b/lib/super_diff/object_inspection/map.rb index 99d166b4..9d032542 100644 --- a/lib/super_diff/object_inspection/map.rb +++ b/lib/super_diff/object_inspection/map.rb @@ -16,6 +16,8 @@ def call(object) Inspectors::Hash when String Inspectors::String + when Time + Inspectors::Time when true, false, nil, Symbol, Numeric, Regexp, Class Inspectors::Primitive else diff --git a/lib/super_diff/operational_sequencers.rb b/lib/super_diff/operational_sequencers.rb index 1f41372d..28902965 100644 --- a/lib/super_diff/operational_sequencers.rb +++ b/lib/super_diff/operational_sequencers.rb @@ -10,6 +10,7 @@ module OperationalSequencers :MultilineString, "super_diff/operational_sequencers/multiline_string", ) + autoload :TimeLike, "super_diff/operational_sequencers/time_like" DEFAULTS = [Array, Hash, CustomObject].freeze end diff --git a/lib/super_diff/operational_sequencers/time_like.rb b/lib/super_diff/operational_sequencers/time_like.rb new file mode 100644 index 00000000..d294646e --- /dev/null +++ b/lib/super_diff/operational_sequencers/time_like.rb @@ -0,0 +1,21 @@ +module SuperDiff + module OperationalSequencers + class TimeLike < CustomObject + def self.applies_to?(expected, actual) + (expected.is_a?(Time) && actual.is_a?(Time)) || + ( + # Check for ActiveSupport's #acts_like_time? for their time-like objects + # (like ActiveSupport::TimeWithZone). + expected.respond_to?(:acts_like_time?) && expected.acts_like_time? && + actual.respond_to?(:acts_like_time?) && actual.acts_like_time? + ) + end + + protected + + def attribute_names + ["year", "month", "day", "hour", "min", "sec", "nsec", "zone", "gmt_offset"] + end + end + end +end diff --git a/spec/integration/rspec/eq_matcher_spec.rb b/spec/integration/rspec/eq_matcher_spec.rb index b239b2ba..76808db4 100644 --- a/spec/integration/rspec/eq_matcher_spec.rb +++ b/spec/integration/rspec/eq_matcher_spec.rb @@ -142,7 +142,7 @@ it "produces the correct failure message when used in the negative" do as_both_colored_and_uncolored do |color_enabled| - snippet = %|expect("Jennifer").to eq("Marty")| + snippet = %|expect("Jennifer").not_to eq("Jennifer")| program = make_plain_test_program( snippet, color_enabled: color_enabled, @@ -150,16 +150,150 @@ expected_output = build_expected_output( color_enabled: color_enabled, - snippet: %|expect("Jennifer").to eq("Marty")|, + snippet: %|expect("Jennifer").not_to eq("Jennifer")|, expectation: proc { line do plain "Expected " beta %|"Jennifer"| + plain " not to eq " + alpha %|"Jennifer"| + plain "." + end + }, + ) + + expect(program). + to produce_output_when_run(expected_output). + in_color(color_enabled) + end + end + end + + context "when comparing two different Time instances" do + it "produces the correct failure message when used in the positive" do + as_both_colored_and_uncolored do |color_enabled| + snippet = <<~RUBY + expected = Time.utc(2011, 12, 13, 14, 15, 16) + actual = Time.utc(2011, 12, 13, 14, 15, 16, 500_000) + expect(expected).to eq(actual) + RUBY + program = make_plain_test_program( + snippet, + color_enabled: color_enabled, + ) + + expected_output = build_expected_output( + color_enabled: color_enabled, + snippet: %|expect(expected).to eq(actual)|, + expectation: proc { + line do + plain "Expected " + beta %|2011-12-13 14:15:16.000 UTC +00:00 (Time)| plain " to eq " - alpha %|"Marty"| + alpha %|2011-12-13 14:15:16.500 UTC +00:00 (Time)| plain "." end }, + diff: proc { + plain_line " #