From 4b36b4e78241d88fcb77da406b574f317a4d9221 Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 20 May 2017 13:44:08 -0400 Subject: [PATCH] options: Report redefinition errors when underscores are used Fixes #2020 --- tornado/options.py | 6 +++--- tornado/test/options_test.py | 20 +++++++++++++++++++- tornado/test/util.py | 13 +++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/tornado/options.py b/tornado/options.py index 0a72cc65e..707fbd35e 100644 --- a/tornado/options.py +++ b/tornado/options.py @@ -223,9 +223,10 @@ class OptionParser(object): override options set earlier on the command line, but can be overridden by later flags. """ - if name in self._options: + normalized = self._normalize_name(name) + if normalized in self._options: raise Error("Option %r already defined in %s" % - (name, self._options[name].file_name)) + (normalized, self._options[normalized].file_name)) frame = sys._getframe(0) options_file = frame.f_code.co_filename @@ -247,7 +248,6 @@ class OptionParser(object): group_name = group else: group_name = file_name - normalized = self._normalize_name(name) option = _Option(name, file_name=file_name, default=default, type=type, help=help, metavar=metavar, multiple=multiple, diff --git a/tornado/test/options_test.py b/tornado/test/options_test.py index bafeea6fd..1a0ac8fb8 100644 --- a/tornado/test/options_test.py +++ b/tornado/test/options_test.py @@ -7,7 +7,7 @@ import sys from tornado.options import OptionParser, Error from tornado.util import basestring_type, PY3 -from tornado.test.util import unittest +from tornado.test.util import unittest, subTest if PY3: from io import StringIO @@ -232,6 +232,24 @@ class OptionsTest(unittest.TestCase): self.assertRegexpMatches(str(cm.exception), 'Option.*foo.*already defined') + def test_error_redefine_underscore(self): + # Ensure that the dash/underscore normalization doesn't + # interfere with the redefinition error. + tests = [ + ('foo-bar', 'foo-bar'), + ('foo_bar', 'foo_bar'), + ('foo-bar', 'foo_bar'), + ('foo_bar', 'foo-bar'), + ] + for a, b in tests: + with subTest(self, a=a, b=b): + options = OptionParser() + options.define(a) + with self.assertRaises(Error) as cm: + options.define(b) + self.assertRegexpMatches(str(cm.exception), + 'Option.*foo.bar.*already defined') + def test_dash_underscore_cli(self): # Dashes and underscores should be interchangeable. for defined_name in ['foo-bar', 'foo_bar']: diff --git a/tornado/test/util.py b/tornado/test/util.py index 6c032da63..5f534e84e 100644 --- a/tornado/test/util.py +++ b/tornado/test/util.py @@ -1,5 +1,6 @@ from __future__ import absolute_import, division, print_function +import contextlib import os import platform import socket @@ -94,3 +95,15 @@ def is_coverage_running(): except AttributeError: return False return mod.startswith('coverage') + + +def subTest(test, *args, **kwargs): + """Compatibility shim for unittest.TestCase.subTest. + + Usage: ``with tornado.test.util.subTest(self, x=x):`` + """ + try: + subTest = test.subTest # py34+ + except AttributeError: + subTest = contextlib.contextmanager(lambda *a, **kw: (yield)) + return subTest(*args, **kwargs) -- 2.47.2