Skip to content

Commit 179a3b7

Browse files
authored
Unit test for rules (#375)
* Added test for rule syntax * Added not enclosed rule * Added test no rules for message * Added test no rules for enums * Update rule search syntax * Changed fullloader to baseloader * Updated documentation
1 parent a527f86 commit 179a3b7

File tree

3 files changed

+160
-51
lines changed

3 files changed

+160
-51
lines changed

doc/commenting.rst

Lines changed: 15 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -288,45 +288,21 @@ The rule definition must follow the syntax which is defined by a regex search wh
288288

289289
.. code-block:: python
290290
291-
'is_greater_than': r'\b(is_greater_than)\b: \d+(\.\d+)?' # is_greater_than: 1
292-
'is_greater_than_or_equal_to': r'\b(is_greater_than_or_equal_to)\b: \d+(\.\d+)?' # is_greater_than_or_equal_to: 1
293-
'is_less_than_or_equal_to': r'\b(is_less_than_or_equal_to)\b: \d+(\.\d+)?' # is_less_than_or_equal_to: 10
294-
'is_less_than': r'\b(is_less_than)\b: \d+(\.\d+)?' # is_less_than: 2
295-
'is_equal': r'\b(is_equal)\b: \d+(\.\d+)?' # is_equal: 1
296-
'is_different': r'\b(is_different)\b: \d+(\.\d+)?' # is_different: 2
297-
'is_global_unique': r'\b(is_global_unique)\b' # is_global_unique
298-
'refers': r'\b(refers)\b' # refers
299-
'is_iso_country_code': r'\b(is_iso_country_code)\b' # is_iso_country_code
300-
'first_element': r'\b(first_element)\b: \{.*: \d+\.\d+\}' # first_element: {is_equal: 0.13, is_greater_than: 0.13}
301-
'last_element': r'\b(last_element)\b: \{.*: \d+\.\d+\}' # last_element: {is_equal: 0.13, is_greater_than: 0.13}
302-
'is_optional': r'\b(is_optional)\b' # is_optional
303-
'check_if': r'\b(check_if)\b: \[\{.*: \d+(\.\d+)?, target: .*}, \{do_check: \{.*: \d+(\.\d+)?}}]' # check_if: [{is_equal: 2, is_greater_than: 3, target: this.y}, {do_check: {is_equal: 1, is_less_than: 3}}]
304-
305-
You can check the correctness of these regular expression on `regex101 <https://regex101.com/r/6tomm6/16>`_.
306-
307-
308-
.. is_greater_than: 2
309-
.. is_greater_than: 2.23
310-
.. is_greater_than_or_equal_to: 1
311-
.. is_greater_than_or_equal_to: 1.12
312-
.. is_less_than_or_equal_to: 10
313-
.. is_less_than_or_equal_to: 10.123
314-
.. is_less_than: 2
315-
.. is_less_than: 2.321
316-
.. is_equal: 1
317-
.. is_equal: 1.312
318-
.. is_different: 2
319-
.. is_different: 2.2122
320-
.. is_global_unique
321-
.. refers
322-
.. is_iso_country_code
323-
.. first_element: {is_equal: 3, is_greater: 2}
324-
.. first_element: {is_equal: 0.13, is_greater: 0.13}
325-
.. last_element: {is_equal: 3, is_greater: 2}
326-
.. last_element: {is_equal: 0.13, is_greater: 0.13}
327-
.. check_if: [{is_equal: 2, is_greater_than: 3, target: this.y}, {do_check: {is_equal: 1, is_less_than: 3}}]
328-
.. is_set
329-
291+
'is_greater_than': r'^[ ]\b(is_greater_than)\b: ([\s\d]+)$' # is_greater_than: 1
292+
'is_greater_than_or_equal_to': r'^[ ]\b(is_greater_than_or_equal_to)\b: ([\s\d]+)$' # is_greater_than_or_equal_to: 1
293+
'is_less_than_or_equal_to': r'^[ ]\b(is_less_than_or_equal_to)\b: ([\s\d]+)$' # is_less_than_or_equal_to: 10
294+
'is_less_than': r'^[ ]\b(is_less_than)\b: ([\s\d]+)$' # is_less_than: 2
295+
'is_equal': r'^[ ]\b(is_equal_to)\b: ([\s\d]+)$' # is_equal_to: 1
296+
'is_different': r'^[ ]\b(is_different_to)\b: ([\s\d]+)$' # is_different_to: 2
297+
'is_global_unique': r'^[ ]\b(is_globally_unique)\b' # is_globally_unique
298+
'refers': r'^[ ]\b(refers_to)\b' # refers_to: DetectedObject
299+
'is_iso_country_code': r'^[ ]\b(is_iso_country_code)\b' # is_iso_country_code
300+
'first_element': r'^[ ]\b(first_element)\b' # first_element height is_equal_to 0.13
301+
'last_element': r'^[ ]\b(last_element)\b' # last_element width is_equal_to 0.13
302+
'check_if': r'^[ ](\bcheck_if\b)(.*\belse do_check\b)' # check_if this.type is_equal_to 2 else do_check is_set
303+
304+
You can check the correctness of these regular expression on `regex101 <https://regex101.com/r/P4KeuO/1>`_.
305+
330306

331307
Commenting with doxygen references
332308
------------------------------------

rules.yml

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
is_greater_than: '\b(is_greater_than)\b: '
2-
is_greater_than_or_equal_to: '\b(is_greater_than_or_equal_to)\b: '
3-
is_less_than_or_equal_to: '\b(is_less_than_or_equal_to)\b: '
4-
is_less_than: '\b(is_less_than)\b: '
5-
is_equal_to: '\b(is_equal_to)\b: '
6-
is_different_to: '\b(is_different_to)\b: '
7-
is_globally_unique: '\b(is_globally_unique)\b'
8-
refers_to: '\b(refers_to)\b'
9-
is_iso_country_code: '\b(is_iso_country_code)\b'
10-
first_element: '\b(first_element)\b'
11-
last_element: '\b(last_element)\b'
12-
check_if: '(\bcheck_if\b)(.*\belse do_check\b)'
1+
is_greater_than: '^[ ]\b(is_greater_than)\b: ([\s\d]+)$'
2+
is_greater_than_or_equal_to: '^[ ]\b(is_greater_than_or_equal_to)\b: ([\s\d]+)$'
3+
is_less_than_or_equal_to: '^[ ]\b(is_less_than_or_equal_to)\b: ([\s\d]+)$'
4+
is_less_than: '^[ ]\b(is_less_than)\b: ([\s\d]+)$'
5+
is_equal_to: '^[ ]\b(is_equal_to)\b: ([\s\d]+)$'
6+
is_different_to: '^[ ]\b(is_different_to)\b: ([\s\d]+)$'
7+
is_globally_unique: '^[ ]\b(is_globally_unique)\b'
8+
refers_to: '^[ ]\b(refers_to)\b'
9+
is_iso_country_code: '^[ ]\b(is_iso_country_code)\b'
10+
first_element: '^[ ]\b(first_element)\b'
11+
last_element: '^[ ]\b(last_element)\b'
12+
check_if: '^[ ](\bcheck_if\b)(.*\belse do_check\b)'

tests/test_rules.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import re
2+
import glob
3+
import unittest
4+
import yaml
5+
6+
PROTO_FILES = glob.glob("*.proto")
7+
8+
class TestRules(unittest.TestCase):
9+
""" Test class for units documentation. """
10+
11+
def test_rules_compliance(self):
12+
''' Test rule compliance syntax of proto files. '''
13+
14+
with open(r'rules.yml') as rules_file:
15+
RULES_DICT = yaml.load(rules_file, Loader=yaml.BaseLoader)
16+
17+
for file in PROTO_FILES:
18+
with open(file, "rt") as fin, self.subTest(file=file):
19+
line_number = 0
20+
numMessage = 0
21+
lineruleCount = 0
22+
foundruleCount = 0
23+
saveStatement = ""
24+
isEnum = False
25+
26+
for line in fin:
27+
line_number += 1
28+
29+
# Divide statement and comment. Concatenate multi line statements.
30+
# Search for comment ("//").
31+
matchComment = re.search("//", line)
32+
if matchComment is not None:
33+
statement = line[:matchComment.start()]
34+
comment = line[matchComment.end():]
35+
else:
36+
statement = line
37+
comment = ""
38+
39+
# Add part of the statement from last line.
40+
statement = saveStatement + " " + statement
41+
saveStatement = ""
42+
43+
# New line is not necessary. Remove for a better output.
44+
statement = statement.replace("\n", "")
45+
comment = comment.replace("\n", "")
46+
47+
# Is statement complete
48+
matchSep = re.search(r"[{};]", statement)
49+
if matchSep is None:
50+
saveStatement = statement
51+
statement = ""
52+
else:
53+
saveStatement = statement[matchSep.end():]
54+
statement = statement[:matchSep.end()]
55+
56+
# Search for "message".
57+
matchMessage = re.search(r"\bmessage\b", statement)
58+
if matchMessage is not None:
59+
# a new message or a new nested message
60+
numMessage += 1
61+
self.assertFalse(foundruleCount > 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: message should not have rules for '{statement.strip()}'")
62+
endOfLine = statement[matchMessage.end():]
63+
matchName = re.search(r"\b\w[\S]*\b", endOfLine)
64+
65+
elif re.search(r"\bextend\b", statement) is not None:
66+
# treat extend as message
67+
numMessage += 1
68+
else:
69+
# Check field names
70+
if numMessage > 0:
71+
matchName = re.search(r"\b\w[\S]*\b\s*=", statement)
72+
if matchName is not None:
73+
checkName = statement[matchName.start():matchName.end()-1]
74+
# Check field message type (remove field name)
75+
type = statement.replace(checkName, "")
76+
matchName = re.search(r"\b\w[\S\.]*\s*=", type)
77+
78+
if isEnum:
79+
matchName = re.search(r"\b\w[\S:]+\b", statement)
80+
if matchName is not None:
81+
checkName = statement[matchName.start():matchName.end()]
82+
self.assertFalse(foundruleCount > 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: enum field should not have rules for '{statement.strip()}'")
83+
84+
# Search for "enum".
85+
matchEnum = re.search(r"\benum\b", statement)
86+
if matchEnum is not None:
87+
isEnum = True
88+
endOfLine = statement[matchEnum.end():]
89+
matchName = re.search(r"\b\w[\S]*\b", endOfLine)
90+
if matchName is not None:
91+
self.assertFalse(foundruleCount > 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: enum should not have rules for '{statement.strip()}'")
92+
93+
# Search for a closing brace.
94+
matchClosingBrace = re.search("}", statement)
95+
if numMessage > 0 and matchClosingBrace is not None:
96+
numMessage -= 1
97+
98+
if isEnum is True and matchClosingBrace is not None:
99+
isEnum = False
100+
101+
if matchComment is not None:
102+
if re.search(r"^[ ]\\\bendrules\b$", comment) is not None:
103+
endRule = True
104+
105+
if re.search(r"^[ ]\\\brules\b$", comment) is not None:
106+
hasRule = True
107+
lineruleCount = 0
108+
foundruleCount = 0
109+
110+
if not endRule and comment != '':
111+
for rulename, ruleregex in RULES_DICT.items():
112+
if re.search(ruleregex, comment):
113+
foundruleCount += 1
114+
115+
elif len(saveStatement) == 0:
116+
if numMessage > 0:
117+
if statement.find(";") != -1:
118+
statement = statement.strip()
119+
self.assertFalse(hasRule and lineruleCount != foundruleCount and endRule and lineruleCount-foundruleCount-1>0, file + " in line " + str(line_number) + ": "+str(lineruleCount-foundruleCount-1)+" defined rule(s) does not exists for: '"+statement+"'")
120+
self.assertFalse(hasRule and not endRule, file + " in line " + str(line_number) + ": \\endrules statement does not exists for: '"+statement+"'. Check spacing and rule syntax.")
121+
self.assertFalse(not hasRule and endRule, file + " in line " + str(line_number) + ": \\rules statement does not exists for: '"+statement+"'. Check spacing and rule syntax.")
122+
self.assertFalse(not hasRule and not endRule and lineruleCount < foundruleCount, file + " in line " + str(line_number) + ": rules found but no statements (\\rules and \\endrules) around it for: '"+statement+"'")
123+
lineruleCount = 0
124+
foundruleCount = 0
125+
126+
hasRule = False
127+
endRule = False
128+
129+
if hasRule and not endRule or not hasRule and endRule:
130+
lineruleCount += 1
131+
132+
if __name__ == '__main__':
133+
unittest.main()

0 commit comments

Comments
 (0)