From d82601ed51a0d8e3a61bb49164cbe2683eed2edb Mon Sep 17 00:00:00 2001 From: Dan Tao Date: Thu, 7 Dec 2017 01:23:22 -0600 Subject: [PATCH] make Schema.__eq__ deterministic (#316) * add test for schema equality and fix Schema.__eq__ to deal with dicts properly (fixes #315) * add negative equality tests, implement Schema.__ne__ method to support != operator This fills in missing test coverage to ensure the __eq__ method does not return True in some potentially unexpected cases (these tests would fail before be867c5). Previously only the __eq__ method was implemented, which could lead to surprising behavior e.g.: Schema('foo') == Schema('foo') # True Schema('foo') != Schema('foo') # True This adds the __ne__ method so that these operators are complementary as one might expect. --- voluptuous/schema_builder.py | 10 +++--- voluptuous/tests/tests.py | 65 +++++++++++++++++++++++++++++++++++- 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/voluptuous/schema_builder.py b/voluptuous/schema_builder.py index f4244c8..724c504 100644 --- a/voluptuous/schema_builder.py +++ b/voluptuous/schema_builder.py @@ -242,10 +242,12 @@ def value_to_schema_type(value): return cls(value_to_schema_type(data), **kwargs) def __eq__(self, other): - if str(other) == str(self.schema): - # Because repr is combination mixture of object and schema - return True - return False + if not isinstance(other, Schema): + return False + return other.schema == self.schema + + def __ne__(self, other): + return not (self == other) def __str__(self): return str(self.schema) diff --git a/voluptuous/tests/tests.py b/voluptuous/tests/tests.py index cc0ed61..25a938c 100644 --- a/voluptuous/tests/tests.py +++ b/voluptuous/tests/tests.py @@ -3,7 +3,7 @@ import os import sys -from nose.tools import assert_equal, assert_raises, assert_true +from nose.tools import assert_equal, assert_false, assert_raises, assert_true from voluptuous import ( Schema, Required, Exclusive, Optional, Extra, Invalid, In, Remove, Literal, @@ -410,6 +410,69 @@ def test_subschema_extension(): assert_equal(extended.schema, {'a': {'b': str, 'c': float, 'e': int}, 'd': str}) +def test_equality(): + assert_equal(Schema('foo'), Schema('foo')) + + assert_equal(Schema(['foo', 'bar', 'baz']), + Schema(['foo', 'bar', 'baz'])) + + # Ensure two Schemas w/ two equivalent dicts initialized in a different + # order are considered equal. + dict_a = {} + dict_a['foo'] = 1 + dict_a['bar'] = 2 + dict_a['baz'] = 3 + + dict_b = {} + dict_b['baz'] = 3 + dict_b['bar'] = 2 + dict_b['foo'] = 1 + + assert_equal(Schema(dict_a), Schema(dict_b)) + + +def test_equality_negative(): + """Verify that Schema objects are not equal to string representations""" + assert_false(Schema('foo') == 'foo') + + assert_false(Schema(['foo', 'bar']) == "['foo', 'bar']") + assert_false(Schema(['foo', 'bar']) == Schema("['foo', 'bar']")) + + assert_false(Schema({'foo': 1, 'bar': 2}) == "{'foo': 1, 'bar': 2}") + assert_false(Schema({'foo': 1, 'bar': 2}) == Schema("{'foo': 1, 'bar': 2}")) + + +def test_inequality(): + assert_true(Schema('foo') != 'foo') + + assert_true(Schema(['foo', 'bar']) != "['foo', 'bar']") + assert_true(Schema(['foo', 'bar']) != Schema("['foo', 'bar']")) + + assert_true(Schema({'foo': 1, 'bar': 2}) != "{'foo': 1, 'bar': 2}") + assert_true(Schema({'foo': 1, 'bar': 2}) != Schema("{'foo': 1, 'bar': 2}")) + + +def test_inequality_negative(): + assert_false(Schema('foo') != Schema('foo')) + + assert_false(Schema(['foo', 'bar', 'baz']) != + Schema(['foo', 'bar', 'baz'])) + + # Ensure two Schemas w/ two equivalent dicts initialized in a different + # order are considered equal. + dict_a = {} + dict_a['foo'] = 1 + dict_a['bar'] = 2 + dict_a['baz'] = 3 + + dict_b = {} + dict_b['baz'] = 3 + dict_b['bar'] = 2 + dict_b['foo'] = 1 + + assert_false(Schema(dict_a) != Schema(dict_b)) + + def test_repr(): """Verify that __repr__ returns valid Python expressions""" match = Match('a pattern', msg='message')