From 4eb01735f6531ce1e0bbbcb2928e739562855c86 Mon Sep 17 00:00:00 2001 From: sslypushenko Date: Fri, 30 Sep 2016 17:19:39 +0300 Subject: [PATCH] Improve class and method regexps This commit improves regexps that check class and method names: '.' (single dot) is no longer considered a valid method name. Class names can no longer end with dot or include two dots in a row. CamelCase detection is also improved to allow short uppercase names ('IP' or 'CDN') and required first letter to be uppercase (i.e. 'aBCD' is no longer legal) Added positive and negative tests for these cases Co-Authored-By: Kirill Zaitsev Change-Id: I9cefd115f552cc7067d643c98f4b4a4ab4e11735 --- .../tests/test_muranopl_validator.py | 67 ++++++++++++++++++- muranopkgcheck/validators/muranopl.py | 26 ++++--- 2 files changed, 84 insertions(+), 9 deletions(-) diff --git a/muranopkgcheck/tests/test_muranopl_validator.py b/muranopkgcheck/tests/test_muranopl_validator.py index fd8e98e..ada81dd 100644 --- a/muranopkgcheck/tests/test_muranopl_validator.py +++ b/muranopkgcheck/tests/test_muranopl_validator.py @@ -70,13 +70,51 @@ class MuranoPlTests(helpers.BaseValidatorTestClass): self.assertIn('Wrong namespace or FNQ of extended class "w_ww#"', next(self.g).message) + def test_correct_name_single(self): + self.g = self.mpl_validator._valid_name('A') + self.assertEqual(0, len([e for e in self.g])) + + def test_correct_name_upper(self): + self.g = self.mpl_validator._valid_name('ABC') + self.assertEqual(0, len([e for e in self.g])) + + def test_dot_in_name(self): + self.g = self.mpl_validator._valid_name('.') + self.assertIn('Invalid class name "."', next(self.g).message) + + def test_startswith_number_in_name(self): + self.g = self.mpl_validator._valid_name('1A') + self.assertIn('Invalid class name "1A"', next(self.g).message) + + def test_dot_in_name_startswith_dot(self): + self.g = self.mpl_validator._valid_name('.A') + self.assertIn('Invalid class name ".A"', next(self.g).message) + + def test_dot_in_name_endswith_dot(self): + self.g = self.mpl_validator._valid_name('A.') + self.assertIn('Invalid class name "A."', next(self.g).message) + + def test_dot_in_name_double_dot(self): + self.g = self.mpl_validator._valid_name('A..B') + self.assertIn('Invalid class name "A..B"', next(self.g).message) + def test_double_underscored_name(self): self.g = self.mpl_validator._valid_name('__Instance') self.assertIn('Invalid class name "__Instance"', next(self.g).message) def test_not_camel_case_name(self): self.g = self.mpl_validator._valid_name('notcamelcase') - self.assertIn('Invalid class name "notcamelcase"', + self.assertIn('Class name "notcamelcase" not in CamelCase', + next(self.g).message) + + def test_not_camel_case_name_upper(self): + self.g = self.mpl_validator._valid_name('ABCD') + self.assertIn('Class name "ABCD" not in CamelCase', + next(self.g).message) + + def test_not_camel_case_name_first_lower(self): + self.g = self.mpl_validator._valid_name('almostCamel') + self.assertIn('Class name "almostCamel" not in CamelCase', next(self.g).message) def test_whitespace_in_name(self): @@ -85,6 +123,12 @@ class MuranoPlTests(helpers.BaseValidatorTestClass): self.assertIn('Invalid class name "white space"', next(self.g).message) + def test_name_not_a_string(self): + name = 42 + self.g = self.mpl_validator._valid_name(name) + self.assertIn('Class name should be a string', + next(self.g).message) + def test_properties_list(self): self.g = self.mpl_validator._valid_properties([]) self.assertIn('Properties should be a dict', @@ -260,12 +304,33 @@ class MuranoPlTests(helpers.BaseValidatorTestClass): self.assertIn('Wrong type of Extends field', next(self.g).message) + def test_method_valid_name(self): + self.g = self.mpl_validator._valid_methods({'foo': {}}) + self.assertEqual(0, len([e for e in self.g])) + def test_method_invalid_name(self): m_dict = {'foo#': {}} self.g = self.mpl_validator._valid_methods(m_dict) self.assertIn('Invalid name of method "foo#"', next(self.g).message) + def test_method_invalid_name_dot(self): + m_dict = {'.foo': {}} + self.g = self.mpl_validator._valid_methods(m_dict) + self.assertIn('Invalid name of method ".foo"', + next(self.g).message) + + def test_method_valid_special_name(self): + for name in muranopl.SPECIAL_METHODS: + m_dict = {name: {}} + self.g = self.mpl_validator._valid_methods(m_dict) + + def test_method_invalid_name_number(self): + m_dict = {'1abc': {}} + self.g = self.mpl_validator._valid_methods(m_dict) + self.assertIn('Invalid name of method "1abc"', + next(self.g).message) + def test_method_unknown_keyword(self): m_dict = deepcopy(MURANOPL_BASE['Methods']) del m_dict['foo']['Body'] diff --git a/muranopkgcheck/validators/muranopl.py b/muranopkgcheck/validators/muranopl.py index b2be1e4..8e8f254 100644 --- a/muranopkgcheck/validators/muranopl.py +++ b/muranopkgcheck/validators/muranopl.py @@ -32,8 +32,9 @@ PROPERTIES_USAGE_VALUES = frozenset(['In', 'Out', 'InOut', 'Const', 'Static', APPLIES_VALUES = frozenset(['Package', 'Type', 'Method', 'Property', 'Argument', 'All']) -CLASSNAME_REGEX = re.compile('^[A-Za-z_]\w*$') -METHOD_NAME_REGEX = re.compile('^[A-Za-z_\.][\w]*$') +CLASSNAME_REGEX = re.compile(r'^[a-zA-Z_]\w*(\.?\w+)*$') +METHOD_NAME_REGEX = re.compile(r'^[a-zA-Z_]\w*$') +SPECIAL_METHODS = frozenset(['.init', '.destroy']) error.register.E011(description='Invalid class name') error.register.E025(description='Wrong namespace or FNQ of extended class') @@ -106,13 +107,21 @@ class MuranoPLValidator(base.YamlValidator): 'class "{0}"').format(import_), import_) def _valid_name(self, value): - if value.startswith('__') or \ - not CLASSNAME_REGEX.match(value): + if not isinstance(value, six.string_types): + yield error.report.E011(_('Invalid class name "{}". ' + 'Class name should be a string') + .format(value), value) + elif (value.startswith('__') or + not CLASSNAME_REGEX.match(value)): yield error.report.E011(_('Invalid class name "{}"').format(value), value) - elif not (value != value.lower() and value != value.upper()): - yield error.report.W011(_('Invalid class name "{}"').format(value), - value) + else: + # NOTE (kzaitsev): allow short uppercase names like Q, IP, CDN + if (not value[0].isupper() or + (len(value) > 3 and value == value.upper())): + yield error.report.W011(_( + 'Class name "{}" not in CamelCase').format(value), + value) def _valid_extends(self, value, can_be_list=True): if can_be_list and isinstance(value, list): @@ -195,7 +204,8 @@ class MuranoPLValidator(base.YamlValidator): method_name) return - if not METHOD_NAME_REGEX.match(method_name): + if not(method_name in SPECIAL_METHODS or + METHOD_NAME_REGEX.match(method_name)): yield error.report.E054(_('Invalid name of method "{}"') .format(method_name), method_name) scope = method_data.get('Scope')