diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index 050e8901f98c..33c7d09a42c2 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -914,24 +914,40 @@ def _validate_linestyle(ls): A validator for all possible line styles, the named ones *and* the on-off ink sequences. """ - # Named line style, like u'--' or u'solid' - if isinstance(ls, six.text_type): - return _validate_named_linestyle(ls) - - # On-off ink (in points) sequence *of even length*. + # Look first for a valid named line style, like '--' or 'solid' + if isinstance(ls, six.string_types): + try: + return _validate_named_linestyle(ls) + except (UnicodeDecodeError, KeyError): + # On Python 2, string-like *ls*, like for example + # 'solid'.encode('utf-16'), may raise a unicode error. + raise ValueError("the linestyle string {!r} is not a valid " + "string.".format(ls)) + + if isinstance(ls, (bytes, bytearray)): + # On Python 2, a string-like *ls* should already have lead to a + # successful return or to raising an exception. On Python 3, we have + # to manually raise an exception in the case of a byte-like *ls*. + # Otherwise, if *ls* is of even-length, it will be passed to the + # instance of validate_nseq_float, which will return an absurd on-off + # ink sequence... + raise ValueError("linestyle {!r} neither looks like an on-off ink " + "sequence nor a valid string.".format(ls)) + + # Look for an on-off ink sequence (in points) *of even length*. # Offset is set to None. try: if len(ls) % 2 != 0: - # Expecting a sequence of even length - raise ValueError + raise ValueError("the linestyle sequence {!r} is not of even " + "length.".format(ls)) + return (None, validate_nseq_float()(ls)) - except (ValueError, TypeError): - # TypeError can be raised by wrong types passed to float() - # (called inside the instance of validate_nseq_float). - pass - raise ValueError("linestyle must be a string or " + - "an even-length sequence of floats.") + except (ValueError, TypeError): + # TypeError can be raised inside the instance of validate_nseq_float, + # by wrong types passed to float(), like NoneType. + raise ValueError("linestyle {!r} is not a valid on-off ink " + "sequence.".format(ls)) # a map from key -> value, converter diff --git a/lib/matplotlib/tests/test_rcparams.py b/lib/matplotlib/tests/test_rcparams.py index 4e689f6b9c47..e84a90ee34cf 100644 --- a/lib/matplotlib/tests/test_rcparams.py +++ b/lib/matplotlib/tests/test_rcparams.py @@ -333,29 +333,45 @@ def generate_validator_testcases(valid): ), 'fail': (('aardvark', ValueError), ) - }, - {'validator': _validate_linestyle, # NB: case-insensitive - 'success': (('-', '-'), ('solid', 'solid'), - ('--', '--'), ('dashed', 'dashed'), - ('-.', '-.'), ('dashdot', 'dashdot'), - (':', ':'), ('dotted', 'dotted'), - ('', ''), (' ', ' '), - ('None', 'none'), ('none', 'none'), - ('DoTtEd', 'dotted'), - (['1.23', '4.56'], (None, [1.23, 4.56])), - ([1.23, 456], (None, [1.23, 456.0])), - ([1, 2, 3, 4], (None, [1.0, 2.0, 3.0, 4.0])), - ), - 'fail': (('aardvark', ValueError), # not a valid string - ((None, [1, 2]), ValueError), # (offset, dashes) is not OK - ((0, [1, 2]), ValueError), # idem - ((-1, [1, 2]), ValueError), # idem - ([1, 2, 3], ValueError), # not a sequence of even length - (1.23, ValueError) # not a sequence - ) } ) + # The behavior of _validate_linestyle depends on the version of Python. + # ASCII-compliant bytes arguments should pass on Python 2 because of the + # automatic conversion between bytes and strings. Python 3 does not + # perform such a conversion, so the same cases should raise an exception. + # + # Common cases: + ls_test = {'validator': _validate_linestyle, + 'success': (('-', '-'), ('solid', 'solid'), + ('--', '--'), ('dashed', 'dashed'), + ('-.', '-.'), ('dashdot', 'dashdot'), + (':', ':'), ('dotted', 'dotted'), + ('', ''), (' ', ' '), + ('None', 'none'), ('none', 'none'), + ('DoTtEd', 'dotted'), # case-insensitive + (['1.23', '4.56'], (None, [1.23, 4.56])), + ([1.23, 456], (None, [1.23, 456.0])), + ([1, 2, 3, 4], (None, [1.0, 2.0, 3.0, 4.0])), + ), + 'fail': (('aardvark', ValueError), # not a valid string + ('dotted'.encode('utf-16'), ValueError), # even on PY2 + ((None, [1, 2]), ValueError), # (offset, dashes) != OK + ((0, [1, 2]), ValueError), # idem + ((-1, [1, 2]), ValueError), # idem + ([1, 2, 3], ValueError), # sequence with odd length + (1.23, ValueError), # not a sequence + ) + } + # Add some cases of bytes arguments that Python 2 can convert silently: + ls_bytes_args = (b'dotted', 'dotted'.encode('ascii')) + if six.PY3: + ls_test['fail'] += tuple((arg, ValueError) for arg in ls_bytes_args) + else: + ls_test['success'] += tuple((arg, 'dotted') for arg in ls_bytes_args) + # Update the validation test sequence. + validation_tests += (ls_test,) + for validator_dict in validation_tests: validator = validator_dict['validator'] if valid: