J
John W. Long
The problem:
When debugging a program that uses large objects and a test fails because
the object is different from the expected it is sometimes hard to discern
the difference between the objects from the output of Test::Unit.
The goal of this hole is to create a method that will output the differences
of two objects in an intelligent manner. Something similar in concept to
this:
#<TestObject1:0x1 ... @b="b", ... @d="d", ...>
#<TestObject1:0x2 ... @b="", ... @d="", ...>
You are free to use whatever method you might choose. Program size does not
matter. Creativity and one-up-man-ship is encouraged. We will probably post
the best solution on the wiki.
The following test case is a guideline and may be changed if you have better
ideas for how such a method should work:
<TestCase>
class TestObject1
attr_accessor :a, :b, :c, :d, :e
def initialize(a, b, c, d, e = nil)
@a, @b, @c, @d, @e = a, b, c, d, e
end
end
class TestObject2
attr_accessor :a, :b, :c, :d, :f, :g
def initialize(a, b, c, d, f, g)
@a, @b, @c, @d, @f, @g = a, b, c, d, f, g
end
end
class TC_ObjectDiff < Test::Unit::TestCase
def test_to_s
object = TestObject1.new('a', 'b', 'c', 'd', [1, 2, 3, 4])
def object.__id__
0
end
assert_equal('#<TestObject1:0x0 @a="a", @b="b", @c="c", @d="d", @e=[1, 2,
3, 4]>',
ObjectDiff.object_to_s(object))
end
def test_compare
object1 = TestObject1.new('a', 'b', 'c', 'd')
def object1.__id__
1
end
object2 = TestObject1.new('a', '', 'c', '')
def object2.__id__
2
end
string1, string2 = ObjectDiff.compare(object1, object2)
puts [string1, string2]
assert_equal('#<TestObject1:0x1 ... @b="b", ... @d="d", ...>', string1)
assert_equal('#<TestObject1:0x2 ... @b="", ... @d="", ...>', string2)
complexObject1 = TestObject1.new('1', {:a => 1, :b => 2, :j => 3}, [1, 2,
3], object1)
def complexObject1.__id__
3
end
complexObject2 = TestObject1.new('1', {:a => 2, :b => object1, :j => 3},
[0, 2], object2)
def complexObject2.__id__
4
end
string1, string2 = ObjectDiff.compare(complexObject1, complexObject2)
puts [string1, string2]
assert_equal('#<TestObject1:0x3 ... @b={:a=>1, :b=>2, ...}, @c=[1, ... 3],
@d=#<TestObject1:0x1 ... @b="b", ... @d="d", ...>, ...>', string1)
assert_equal('#<TestObject1:0x4 ... @b={:a=>2, :b=>#<TestObject1:0x284a3a8
@c="c", @b="b", @e=nil, @a="a", @d="d">, ...}, @c=[0, ...],
@d=#<TestObject1:0x2 ... @b="", ... @d="", ...>, ...>', string2)
end
def test_compare_different_classes
object1 = TestObject1.new('a', 'b', 'c', 'd')
def object1.__id__
5
end
object2 = TestObject2.new('a', '', 'c', '', 'f', 'g')
def object2.__id__
6
end
string1, string2 = ObjectDiff.compare(object1, object2)
puts [string1, string2]
assert_equal('#<TestObject1:0x5 ... @b="b", ... @d="d", @e=nil>', string1)
assert_equal('#<TestObject2:0x6 ... @b="", ... @d="", @f="f", @g="g">',
string2)
end
def test_compare_array
a = [1, 2, 3, 4, 5]
b = [1, 3, 4, 4, 5, 6]
string_a, string_b = ObjectDiff.compare_array(a, b)
puts [string_a, string_b]
assert_equal('[... 2, 3, ... ...]', string_a)
assert_equal('[... 3, 4, ... ... 6]', string_b)
end
def test_compare_hash
a = {
:a => 1,
:b => 2,
:c => 3,
:d => 4
}
b = {
:j => 2,
:b => 3,
:c => 3,
:d => 5
}
string_a, string_b = ObjectDiff.compare_hash(a, b)
puts [string_a, string_b]
assert_equal('{:a=>1, :b=>2, ... :d=>4}', string_a)
assert_equal('{ :b=>3, ... :d=>5, :j=>2}', string_b)
end
end
</TestCase>
Here is my solution:
<MySolution>
class ObjectDiff
class Node
attr_reader bject, :nodes
def initialize(value)
@object = value
@nodes = {}
variables = @object.instance_variables
variables.each { |key|
variable = @object.instance_eval(key)
@nodes[key] = Node.new(variable)
}
end
def to_s
return @object.inspect if [String, Integer, NilClass, Array, Hash,
Fixnum, Integer, Bignum].index(@object.class)
string = s_begin(self)
@nodes.each { |key, node|
string << "#{key}=#{node.to_s}, "
}
s_end(string)
end
def compare_with(node)
return ::ObjectDiff::compare_hash(@object, node.object) if
@object.instance_of?(Hash) and node.object.instance_of?(Hash)
return ::ObjectDiff::compare_array(@object, node.object) if
@object.instance_of?(Array) and node.object.instance_of?(Array)
return [@object.inspect, node.object.inspect] if [String, Integer,
NilClass, Array, Hash, Fixnum, Integer, Bignum].index(@object.class)
keys = []
@nodes.each_key { |key|
keys << key
}
node.nodes.each_key { |key|
keys << key
}
keys.uniq!
keys.sort!
string1 = s_begin(self)
string2 = s_begin(node)
keys.each { |key|
node1 = @nodes[key]
node2 = node.nodes[key]
if @nodes.has_key?(key) and node.nodes.has_key?(key)
if node1.to_s == node2.to_s
string1 << '... '
string2 << '... '
else
s1, s2 = node1.compare_with(node2)
string1 << "#{key}=#{s1}, "
string2 << "#{key}=#{s2}, "
end
else
if @nodes.has_key?(key)
string1 << "#{key}=#{node1.to_s}, "
end
if node.nodes.has_key?(key)
string2 << "#{key}=#{node2.to_s}, "
end
end
}
string1 = s_end(string1)
string2 = s_end(string2)
[string1, string2]
end
def s_begin(node)
"#<#{node.object.class.name}:0x#{format('%x', node.object.__id__)} "
end
def s_end(string)
string.chomp!(' ')
string.chomp!(',')
string << '>'
end
end
def self.object_to_s(object)
Node.new(object).to_s
end
def self.compare(object1, object2)
node1 = Node.new(object1)
node2 = Node.new(object2)
node1.compare_with(node2)
end
def self.compare_array(array1, array2)
if array1.size < array2.size
a = array2
b = array1
flipped = true
else
a = array1
b = array2
flipped = false
end
string1 = '['
string2 = '['
for i in 0...a.size
if i < b.size
node1 = Node.new(a)
node2 = Node.new(b)
if node1.to_s == node2.to_s
string1 << '... '
string2 << '... '
else
string_a, string_b = compare(a, b)
string1 << "#{string_a}, "
string2 << "#{string_b}, "
end
else
string1 << Node.new(a).to_s
end
end
string1.strip!
string1.chomp!(',')
string2.strip!
string2.chomp!(',')
string1 << ']'
string2 << ']'
if flipped
[string2, string1]
else
[string1, string2]
end
end
def self.compare_hash(hash1, hash2)
keys = []
hash1.each_key { |key|
keys << key
}
hash2.each_key { |key|
keys << key
}
keys.uniq!
keys.sort!{ |a, b|
a = a.inspect if a.is_a?(Symbol)
b = b.inspect if b.is_a?(Symbol)
a <=> b
}
string1 = '{'
string2 = '{'
keys.each { |key|
node1 = Node.new(hash1[key])
node2 = Node.new(hash2[key])
if hash1.has_key?(key) and hash2.has_key?(key)
if node1.to_s == node2.to_s
string1 << '... '
string2 << '... '
else
s1, s2 = node1.compare_with(node2)
string1 << "#{key.inspect}=>#{s1}, "
string2 << "#{key.inspect}=>#{s2}, "
end
else
if hash1.has_key?(key) and not hash2.has_key?(key)
append = "#{key.inspect}=>#{node1.to_s}, "
string1 << append
string2 << (' ' * append.size)
else
append = "#{key.inspect}=>#{node2.to_s}, "
string2 << append
string1 << (' ' * append.size)
end
end
}
string1.strip!
string1.chomp!(',')
string2.strip!
string2.chomp!(',')
string1 << '}'
string2 << '}'
[string1, string2]
end
end
</MySolution>
Feel free to improve upon my code or create your own. Remember this is for
posterity, so be honest...
___________________
John Long
www.wiseheartdesign.com
When debugging a program that uses large objects and a test fails because
the object is different from the expected it is sometimes hard to discern
the difference between the objects from the output of Test::Unit.
The goal of this hole is to create a method that will output the differences
of two objects in an intelligent manner. Something similar in concept to
this:
#<TestObject1:0x1 ... @b="b", ... @d="d", ...>
#<TestObject1:0x2 ... @b="", ... @d="", ...>
You are free to use whatever method you might choose. Program size does not
matter. Creativity and one-up-man-ship is encouraged. We will probably post
the best solution on the wiki.
The following test case is a guideline and may be changed if you have better
ideas for how such a method should work:
<TestCase>
class TestObject1
attr_accessor :a, :b, :c, :d, :e
def initialize(a, b, c, d, e = nil)
@a, @b, @c, @d, @e = a, b, c, d, e
end
end
class TestObject2
attr_accessor :a, :b, :c, :d, :f, :g
def initialize(a, b, c, d, f, g)
@a, @b, @c, @d, @f, @g = a, b, c, d, f, g
end
end
class TC_ObjectDiff < Test::Unit::TestCase
def test_to_s
object = TestObject1.new('a', 'b', 'c', 'd', [1, 2, 3, 4])
def object.__id__
0
end
assert_equal('#<TestObject1:0x0 @a="a", @b="b", @c="c", @d="d", @e=[1, 2,
3, 4]>',
ObjectDiff.object_to_s(object))
end
def test_compare
object1 = TestObject1.new('a', 'b', 'c', 'd')
def object1.__id__
1
end
object2 = TestObject1.new('a', '', 'c', '')
def object2.__id__
2
end
string1, string2 = ObjectDiff.compare(object1, object2)
puts [string1, string2]
assert_equal('#<TestObject1:0x1 ... @b="b", ... @d="d", ...>', string1)
assert_equal('#<TestObject1:0x2 ... @b="", ... @d="", ...>', string2)
complexObject1 = TestObject1.new('1', {:a => 1, :b => 2, :j => 3}, [1, 2,
3], object1)
def complexObject1.__id__
3
end
complexObject2 = TestObject1.new('1', {:a => 2, :b => object1, :j => 3},
[0, 2], object2)
def complexObject2.__id__
4
end
string1, string2 = ObjectDiff.compare(complexObject1, complexObject2)
puts [string1, string2]
assert_equal('#<TestObject1:0x3 ... @b={:a=>1, :b=>2, ...}, @c=[1, ... 3],
@d=#<TestObject1:0x1 ... @b="b", ... @d="d", ...>, ...>', string1)
assert_equal('#<TestObject1:0x4 ... @b={:a=>2, :b=>#<TestObject1:0x284a3a8
@c="c", @b="b", @e=nil, @a="a", @d="d">, ...}, @c=[0, ...],
@d=#<TestObject1:0x2 ... @b="", ... @d="", ...>, ...>', string2)
end
def test_compare_different_classes
object1 = TestObject1.new('a', 'b', 'c', 'd')
def object1.__id__
5
end
object2 = TestObject2.new('a', '', 'c', '', 'f', 'g')
def object2.__id__
6
end
string1, string2 = ObjectDiff.compare(object1, object2)
puts [string1, string2]
assert_equal('#<TestObject1:0x5 ... @b="b", ... @d="d", @e=nil>', string1)
assert_equal('#<TestObject2:0x6 ... @b="", ... @d="", @f="f", @g="g">',
string2)
end
def test_compare_array
a = [1, 2, 3, 4, 5]
b = [1, 3, 4, 4, 5, 6]
string_a, string_b = ObjectDiff.compare_array(a, b)
puts [string_a, string_b]
assert_equal('[... 2, 3, ... ...]', string_a)
assert_equal('[... 3, 4, ... ... 6]', string_b)
end
def test_compare_hash
a = {
:a => 1,
:b => 2,
:c => 3,
:d => 4
}
b = {
:j => 2,
:b => 3,
:c => 3,
:d => 5
}
string_a, string_b = ObjectDiff.compare_hash(a, b)
puts [string_a, string_b]
assert_equal('{:a=>1, :b=>2, ... :d=>4}', string_a)
assert_equal('{ :b=>3, ... :d=>5, :j=>2}', string_b)
end
end
</TestCase>
Here is my solution:
<MySolution>
class ObjectDiff
class Node
attr_reader bject, :nodes
def initialize(value)
@object = value
@nodes = {}
variables = @object.instance_variables
variables.each { |key|
variable = @object.instance_eval(key)
@nodes[key] = Node.new(variable)
}
end
def to_s
return @object.inspect if [String, Integer, NilClass, Array, Hash,
Fixnum, Integer, Bignum].index(@object.class)
string = s_begin(self)
@nodes.each { |key, node|
string << "#{key}=#{node.to_s}, "
}
s_end(string)
end
def compare_with(node)
return ::ObjectDiff::compare_hash(@object, node.object) if
@object.instance_of?(Hash) and node.object.instance_of?(Hash)
return ::ObjectDiff::compare_array(@object, node.object) if
@object.instance_of?(Array) and node.object.instance_of?(Array)
return [@object.inspect, node.object.inspect] if [String, Integer,
NilClass, Array, Hash, Fixnum, Integer, Bignum].index(@object.class)
keys = []
@nodes.each_key { |key|
keys << key
}
node.nodes.each_key { |key|
keys << key
}
keys.uniq!
keys.sort!
string1 = s_begin(self)
string2 = s_begin(node)
keys.each { |key|
node1 = @nodes[key]
node2 = node.nodes[key]
if @nodes.has_key?(key) and node.nodes.has_key?(key)
if node1.to_s == node2.to_s
string1 << '... '
string2 << '... '
else
s1, s2 = node1.compare_with(node2)
string1 << "#{key}=#{s1}, "
string2 << "#{key}=#{s2}, "
end
else
if @nodes.has_key?(key)
string1 << "#{key}=#{node1.to_s}, "
end
if node.nodes.has_key?(key)
string2 << "#{key}=#{node2.to_s}, "
end
end
}
string1 = s_end(string1)
string2 = s_end(string2)
[string1, string2]
end
def s_begin(node)
"#<#{node.object.class.name}:0x#{format('%x', node.object.__id__)} "
end
def s_end(string)
string.chomp!(' ')
string.chomp!(',')
string << '>'
end
end
def self.object_to_s(object)
Node.new(object).to_s
end
def self.compare(object1, object2)
node1 = Node.new(object1)
node2 = Node.new(object2)
node1.compare_with(node2)
end
def self.compare_array(array1, array2)
if array1.size < array2.size
a = array2
b = array1
flipped = true
else
a = array1
b = array2
flipped = false
end
string1 = '['
string2 = '['
for i in 0...a.size
if i < b.size
node1 = Node.new(a)
node2 = Node.new(b)
if node1.to_s == node2.to_s
string1 << '... '
string2 << '... '
else
string_a, string_b = compare(a, b)
string1 << "#{string_a}, "
string2 << "#{string_b}, "
end
else
string1 << Node.new(a).to_s
end
end
string1.strip!
string1.chomp!(',')
string2.strip!
string2.chomp!(',')
string1 << ']'
string2 << ']'
if flipped
[string2, string1]
else
[string1, string2]
end
end
def self.compare_hash(hash1, hash2)
keys = []
hash1.each_key { |key|
keys << key
}
hash2.each_key { |key|
keys << key
}
keys.uniq!
keys.sort!{ |a, b|
a = a.inspect if a.is_a?(Symbol)
b = b.inspect if b.is_a?(Symbol)
a <=> b
}
string1 = '{'
string2 = '{'
keys.each { |key|
node1 = Node.new(hash1[key])
node2 = Node.new(hash2[key])
if hash1.has_key?(key) and hash2.has_key?(key)
if node1.to_s == node2.to_s
string1 << '... '
string2 << '... '
else
s1, s2 = node1.compare_with(node2)
string1 << "#{key.inspect}=>#{s1}, "
string2 << "#{key.inspect}=>#{s2}, "
end
else
if hash1.has_key?(key) and not hash2.has_key?(key)
append = "#{key.inspect}=>#{node1.to_s}, "
string1 << append
string2 << (' ' * append.size)
else
append = "#{key.inspect}=>#{node2.to_s}, "
string2 << append
string1 << (' ' * append.size)
end
end
}
string1.strip!
string1.chomp!(',')
string2.strip!
string2.chomp!(',')
string1 << '}'
string2 << '}'
[string1, string2]
end
end
</MySolution>
Feel free to improve upon my code or create your own. Remember this is for
posterity, so be honest...
___________________
John Long
www.wiseheartdesign.com