Skip to content

Commit

Permalink
default to exact matching and fix a few unnecessary inexact string ma…
Browse files Browse the repository at this point in the history
…tches
  • Loading branch information
cosmicexplorer committed Mar 3, 2019
1 parent aa7ce7c commit 0f2ddb5
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 52 deletions.
12 changes: 6 additions & 6 deletions tests/python/pants_test/test_base.py
Expand Up @@ -617,21 +617,21 @@ def assertInFile(self, string, file_path):
self.assertIn(string, content, '"{}" is not in the file {}:\n{}'.format(string, f.name, content))

@contextmanager
def assertRaisesWithMessageContaining(self, exception_type, error_text, exact=False):
def assertRaisesWithMessageContaining(self, exception_type, error_text, substring_match=False):
"""Verifies that a string appears in an exception message.
:param type exception_type: The exception type which is expected to be raised within the body.
:param str error_text: Text that the exception message should either contain or match exactly.
:param bool exact: If True, use `self.assertEqual()` instead of `self.assertIn()` to match the
exception text. This provides a stronger guarantee but may not be necessary.
:param bool substring_match: If True, use `self.assertIn()` instead of `self.assertEqual()` to
match the exception text.
:API: public
"""
with self.assertRaises(exception_type) as cm:
yield cm
if exact:
self.assertEqual(error_text, str(cm.exception))
else:
if substring_match:
self.assertIn(error_text, str(cm.exception))
else:
self.assertEqual(error_text, str(cm.exception))

def get_bootstrap_options(self, cli_options=()):
"""Retrieves bootstrap options.
Expand Down
92 changes: 46 additions & 46 deletions tests/python/pants_test/util/test_objects.py
Expand Up @@ -42,8 +42,7 @@ class BPrime(A):

class SuperclassesOfTest(TypeConstraintTestBase):
def test_none(self):
with self.assertRaisesWithMessageContaining(ValueError, 'Must supply at least one type',
exact=True):
with self.assertRaisesWithMessageContaining(ValueError, 'Must supply at least one type'):
SubclassesOf()

def test_str_and_repr(self):
Expand Down Expand Up @@ -81,8 +80,7 @@ def test_validate(self):

class ExactlyTest(TypeConstraintTestBase):
def test_none(self):
with self.assertRaisesWithMessageContaining(ValueError, 'Must supply at least one type',
exact=True):
with self.assertRaisesWithMessageContaining(ValueError, 'Must supply at least one type'):
Exactly()

def test_single(self):
Expand All @@ -101,8 +99,7 @@ def test_multiple(self):

def test_disallows_unsplatted_lists(self):
with self.assertRaisesWithMessageContaining(TypeError,
'Supplied types must be types. ([1],)',
exact=True):
'Supplied types must be types. ([1],)'):
Exactly([1])

def test_str_and_repr(self):
Expand Down Expand Up @@ -130,8 +127,7 @@ def test_validate(self):

class SubclassesOfTest(TypeConstraintTestBase):
def test_none(self):
with self.assertRaisesWithMessageContaining(ValueError, 'Must supply at least one type',
exact=True):
with self.assertRaisesWithMessageContaining(ValueError, 'Must supply at least one type'):
SubclassesOf()

def test_str_and_repr(self):
Expand Down Expand Up @@ -334,7 +330,10 @@ class Foo(datatype(['val'], superclass_name='F'), AbsClass):

def test_not_iterable(self):
bar = datatype(['val'])
with self.assertRaisesWithMessageContaining(TypeError, 'datatype object is not iterable'):
with self.assertRaisesWithMessageContaining(
TypeError,
'datatype object is not iterable',
substring_match=True):
for x in bar(1):
pass

Expand Down Expand Up @@ -362,28 +361,26 @@ def test_replace_non_iterable(self):
def test_properties_not_assignable(self):
bar = datatype(['val'])
bar_inst = bar(1)
with self.assertRaisesWithMessageContaining(AttributeError, "can't set attribute", exact=True):
with self.assertRaisesWithMessageContaining(AttributeError, "can't set attribute"):
bar_inst.val = 2

def test_invalid_field_name(self):
with self.assertRaisesWithMessageContaining(
ValueError,
"Type names and field names must be valid identifiers: '0isntanallowedfirstchar'"
if PY3 else
"Type names and field names cannot start with a number: '0isntanallowedfirstchar'",
exact=True):
"Type names and field names cannot start with a number: '0isntanallowedfirstchar'"):
datatype(['0isntanallowedfirstchar'])
with self.assertRaisesWithMessageContaining(
ValueError,
"Field names cannot start with an underscore: '_no_leading_underscore'",
exact=True):
"Field names cannot start with an underscore: '_no_leading_underscore'"):
datatype(['_no_leading_underscore'])

def test_override_eq_disallowed(self):
class OverridesEq(datatype(['myval'])):
def __eq__(self, other):
return other.myval == self.myval
with self.assertRaisesWithMessageContaining(TypeCheckError, 'Should not override __eq__.'):
with self.assertRaisesWithMessageContaining(TypeCheckError, 'type check error in class OverridesEq: Should not override __eq__.'):
OverridesEq(1)

def test_subclass_pickleable(self):
Expand All @@ -399,10 +396,10 @@ def test_mixed_argument_types(self):

def test_double_passed_arg(self):
bar = datatype(['val', 'zal'])
with self.assertRaisesWithMessageContaining(
TypeError,
with self.assertRaisesWithMessageContaining(TypeError,
"__new__() got multiple values for {kw}argument 'val'"
.format(kw='' if PY3 else 'keyword ')):
.format(kw='' if PY3 else 'keyword '),
substring_match=True):
bar(1, val=1)

def test_too_many_args(self):
Expand All @@ -411,13 +408,16 @@ def test_too_many_args(self):
TypeError,
'__new__() takes 3 positional arguments but 4 were given'
if PY3 else
'__new__() takes exactly 3 arguments (4 given)'):
'__new__() takes exactly 3 arguments (4 given)',
substring_match=True):
bar(1, 1, 1)

def test_unexpect_kwarg(self):
bar = datatype(['val'])
with self.assertRaisesWithMessageContaining(
TypeError, "__new__() got an unexpected keyword argument 'other'"):
TypeError,
"__new__() got an unexpected keyword argument 'other'",
substring_match=True):
bar(other=1)


Expand All @@ -434,7 +434,7 @@ def test_class_construction_errors(self):
if PY3 else
"Type names and field names can only contain alphanumeric characters and underscores: \"<type 'int'>\""
)
with self.assertRaisesWithMessageContaining(ValueError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(ValueError, expected_msg):
class NonStrType(datatype([int])): pass

# This raises a TypeError because it doesn't provide a required argument.
Expand All @@ -443,35 +443,35 @@ class NonStrType(datatype([int])): pass
if PY3 else
"datatype() takes at least 1 argument (0 given)"
)
with self.assertRaisesWithMessageContaining(TypeError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeError, expected_msg):
class NoFields(datatype()): pass

expected_msg = (
"Type names and field names must be valid identifiers: \"<class 'str'>\""
if PY3 else
"Type names and field names can only contain alphanumeric characters and underscores: \"<type 'unicode'>\""
)
with self.assertRaisesWithMessageContaining(ValueError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(ValueError, expected_msg):
class JustTypeField(datatype([text_type])): pass

expected_msg = (
"Type names and field names must be valid identifiers: '3'"
if PY3 else
"Type names and field names cannot start with a number: '3'"
)
with self.assertRaisesWithMessageContaining(ValueError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(ValueError, expected_msg):
class NonStringField(datatype([3])): pass

expected_msg = (
"Type names and field names must be valid identifiers: '32'"
if PY3 else
"Type names and field names cannot start with a number: '32'"
)
with self.assertRaisesWithMessageContaining(ValueError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(ValueError, expected_msg):
class NonStringTypeField(datatype([(32, int)])): pass

expected_msg = "Encountered duplicate field name: 'field_a'"
with self.assertRaisesWithMessageContaining(ValueError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(ValueError, expected_msg):
class MultipleSameName(datatype([
'field_a',
'field_b',
Expand All @@ -480,7 +480,7 @@ class MultipleSameName(datatype([
pass

expected_msg = "Encountered duplicate field name: 'field_a'"
with self.assertRaisesWithMessageContaining(ValueError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(ValueError, expected_msg):
class MultipleSameNameWithType(datatype([
'field_a',
('field_a', int),
Expand All @@ -490,7 +490,7 @@ class MultipleSameNameWithType(datatype([
expected_msg = (
"type spec for field 'a_field' was not a type or TypeConstraint: "
"was 2 (type 'int').")
with self.assertRaisesWithMessageContaining(TypeError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeError, expected_msg):
class InvalidTypeSpec(datatype([('a_field', 2)])): pass

def test_instance_construction_by_repr(self):
Expand Down Expand Up @@ -583,7 +583,7 @@ def test_instance_with_collection_construction_str_repr(self):

def test_instance_construction_errors(self):
expected_msg = "type check error in class SomeTypedDatatype: error in namedtuple() base constructor: __new__() got an unexpected keyword argument 'something'"
with self.assertRaisesWithMessageContaining(TypeError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeError, expected_msg):
SomeTypedDatatype(something=3)

# not providing all the fields
Expand All @@ -593,7 +593,7 @@ def test_instance_construction_errors(self):
"__new__() takes exactly 2 arguments (1 given)"
)
expected_msg = "type check error in class SomeTypedDatatype: error in namedtuple() base constructor: {}".format(expected_msg_ending)
with self.assertRaisesWithMessageContaining(TypeError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeError, expected_msg):
SomeTypedDatatype()

# unrecognized fields
Expand All @@ -603,19 +603,19 @@ def test_instance_construction_errors(self):
"__new__() takes exactly 2 arguments (3 given)"
)
expected_msg = "type check error in class SomeTypedDatatype: error in namedtuple() base constructor: {}".format(expected_msg_ending)
with self.assertRaisesWithMessageContaining(TypeError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeError, expected_msg):
SomeTypedDatatype(3, 4)

expected_msg = (
"""type check error in class CamelCaseWrapper: errors type checking constructor arguments:
field 'nonneg_int' was invalid: value 3 (with type 'int') must satisfy this type constraint: Exactly(NonNegativeInt).""")
with self.assertRaisesWithMessageContaining(TypedDatatypeInstanceConstructionError,
expected_msg, exact=True):
expected_msg):
CamelCaseWrapper(nonneg_int=3)

# test that kwargs with keywords that aren't field names fail the same way
expected_msg = "type check error in class CamelCaseWrapper: error in namedtuple() base constructor: __new__() got an unexpected keyword argument 'a'"
with self.assertRaisesWithMessageContaining(TypeError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeError, expected_msg):
CamelCaseWrapper(4, a=3)

def test_type_check_errors(self):
Expand All @@ -630,50 +630,50 @@ def format_string_type_check_message(format_string):
expected_msg = (
"""type check error in class SomeTypedDatatype: errors type checking constructor arguments:
field 'val' was invalid: value [] (with type 'list') must satisfy this type constraint: Exactly(int).""")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
SomeTypedDatatype([])

# type checking failure with multiple arguments (one is correct)
expected_msg = format_string_type_check_message(
"""type check error in class AnotherTypedDatatype: errors type checking constructor arguments:
field 'elements' was invalid: value {u}'should be list' (with type '{str_type}') must satisfy this type constraint: Exactly(list).""")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
AnotherTypedDatatype(text_type('correct'), text_type('should be list'))

# type checking failure on both arguments
expected_msg = format_string_type_check_message(
"""type check error in class AnotherTypedDatatype: errors type checking constructor arguments:
field 'string' was invalid: value 3 (with type 'int') must satisfy this type constraint: Exactly({str_type}).
field 'elements' was invalid: value {u}'should be list' (with type '{str_type}') must satisfy this type constraint: Exactly(list).""")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
AnotherTypedDatatype(3, text_type('should be list'))

expected_msg = format_string_type_check_message(
"""type check error in class NonNegativeInt: errors type checking constructor arguments:
field 'an_int' was invalid: value {u}'asdf' (with type '{str_type}') must satisfy this type constraint: Exactly(int).""")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
NonNegativeInt(text_type('asdf'))

expected_msg = "type check error in class NonNegativeInt: value is negative: -3."
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
NonNegativeInt(-3)

expected_msg = (
"""type check error in class WithSubclassTypeConstraint: errors type checking constructor arguments:
field 'some_value' was invalid: value 3 (with type 'int') must satisfy this type constraint: SubclassesOf(SomeBaseClass).""")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
WithSubclassTypeConstraint(3)

expected_msg = """\
type check error in class WithCollectionTypeConstraint: errors type checking constructor arguments:
field 'dependencies' was invalid: in wrapped constraint TypedCollection(Exactly(int)): value 3 (with type 'int') must satisfy this type constraint: SubclassesOf(Iterable)."""
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
WithCollectionTypeConstraint(3)

expected_msg = format_string_type_check_message("""\
type check error in class WithCollectionTypeConstraint: errors type checking constructor arguments:
field 'dependencies' was invalid: in wrapped constraint TypedCollection(Exactly(int)) matching iterable object [3, {u}'asdf']: value {u}'asdf' (with type '{str_type}') must satisfy this type constraint: Exactly(int).""")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
WithCollectionTypeConstraint([3, "asdf"])

def test_copy(self):
Expand All @@ -689,13 +689,13 @@ def test_copy_failure(self):

expected_msg = (
"""type check error in class AnotherTypedDatatype: error in namedtuple() base constructor: __new__() got an unexpected keyword argument 'nonexistent_field'""")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
obj.copy(nonexistent_field=3)

expected_msg = (
"""type check error in class AnotherTypedDatatype: errors type checking constructor arguments:
field 'elements' was invalid: value 3 (with type 'int') must satisfy this type constraint: Exactly(list).""")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg, exact=True):
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
obj.copy(elements=3)

def test_enum_class_creation_errors(self):
Expand All @@ -710,7 +710,7 @@ def test_enum_instance_creation(self):
self.assertEqual(SomeEnum(2), SomeEnum(value=2))

expected_msg = (
"Value 3 must be one of: [1, 2].")
"type check error in class SomeEnum: Value 3 must be one of: [1, 2].")
with self.assertRaisesWithMessageContaining(EnumVariantSelectionError, expected_msg):
SomeEnum(3)

Expand All @@ -728,7 +728,7 @@ def test_enum_comparison(self):

# Test that comparison fails against another type.
expected_msg = (
"when comparing SomeEnum(value=1) against 1 with type 'int': "
"type check error in class SomeEnum: when comparing SomeEnum(value=1) against 1 with type 'int': "
"enum equality is only defined for instances of the same enum class!")
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
enum_instance == 1
Expand All @@ -738,7 +738,7 @@ def test_enum_comparison(self):
class StrEnum(enum(['a'])): pass
enum_instance = StrEnum('a')
expected_msg = (
"when comparing StrEnum(value={u}'a') against {u}'a' with type '{string_type}': "
"type check error in class StrEnum: when comparing StrEnum(value={u}'a') against {u}'a' with type '{string_type}': "
"enum equality is only defined for instances of the same enum class!"
.format(u='u' if PY2 else '', string_type='unicode' if PY2 else 'str'))
with self.assertRaisesWithMessageContaining(TypeCheckError, expected_msg):
Expand Down

0 comments on commit 0f2ddb5

Please sign in to comment.