Skip to content

Commit ce572ef

Browse files
authored
Regex Check Type implementation (#17)
* draft regex base function * fix file import * draft regex implementation * fix logic into regex result * remove assertion * add match/no-match logic * add assertion for check option
1 parent 872ffa1 commit ce572ef

File tree

3 files changed

+109
-2
lines changed

3 files changed

+109
-2
lines changed

netcompare/check_type.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""CheckType Implementation."""
22
from typing import Mapping, Tuple, List, Dict, Any
3-
from .evaluator import diff_generator, parameter_evaluator
3+
from .evaluator import diff_generator, parameter_evaluator, regex_evaluator
44
from .runner import extract_values_from_output
55

66

@@ -24,7 +24,8 @@ def init(*args):
2424
return ToleranceType(*args)
2525
if check_type == "parameter_match":
2626
return ParameterMatchType(*args)
27-
27+
if check_type == "regex":
28+
return RegexType(*args)
2829
raise NotImplementedError
2930

3031
@staticmethod
@@ -101,10 +102,46 @@ def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple
101102
parameter = value_to_compare[1]
102103
except IndexError as error:
103104
raise f"Evaluating parameter must be defined as dict at index 1. You have: {value_to_compare}" from error
105+
if not isinstance(parameter, dict):
106+
raise TypeError("check_option must be of type dict()")
107+
104108
diff = parameter_evaluator(reference_value, parameter)
105109
return diff, not diff
106110

107111

112+
class RegexType(CheckType):
113+
"""Regex Match class implementation."""
114+
115+
def evaluate(self, reference_value: Mapping, value_to_compare: Mapping) -> Tuple[Mapping, bool]:
116+
"""Regex Match evaluator implementation."""
117+
# Assert that check parameters are at index 1.
118+
try:
119+
parameter = value_to_compare[1]
120+
except IndexError as error:
121+
raise IndexError(
122+
f"Evaluating parameter must be defined as dict at index 1. You have: {value_to_compare}"
123+
) from error
124+
125+
# Assert that check parameters are at index 1.
126+
if not all([isinstance(parameter, dict)]):
127+
raise TypeError("check_option must be of type dict().")
128+
129+
# Assert that check option has 'regex' and 'mode' dict keys.
130+
if "regex" not in parameter and "mode" not in parameter:
131+
raise KeyError(
132+
"Regex check-type requires check-option. Example: dict(regex='.*UNDERLAY.*', mode='no-match')."
133+
)
134+
135+
# Assert that check option has 'regex' and 'mode' dict keys.\
136+
if parameter["mode"] not in ["match", "no-match"]:
137+
raise ValueError(
138+
"Regex check-type requires check-option. Example: dict(regex='.*UNDERLAY.*', mode='no-match')."
139+
)
140+
141+
diff = regex_evaluator(reference_value, parameter)
142+
return diff, not diff
143+
144+
108145
# TODO: compare is no longer the entry point, we should use the libary as:
109146
# netcompare_check = CheckType.init(check_type_info, options)
110147
# pre_result = netcompare_check.get_value(pre_obj, path)

netcompare/evaluator.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,28 @@ def parameter_evaluator(values: Mapping, parameter: Mapping) -> Dict:
131131
result[inner_key] = temp_dict
132132

133133
return result
134+
135+
136+
def regex_evaluator(values: Mapping, parameter: Mapping) -> Dict:
137+
"""Regex Match evaluator engine."""
138+
# values: [{'7.7.7.7': {'peerGroup': 'EVPN-OVERLAY-SPINE'}}]
139+
# parameter: {'regex': '.*UNDERLAY.*', 'mode': 'include'}
140+
result = {}
141+
if not isinstance(values, list):
142+
raise TypeError("Something went wrong during JMSPath parsing. values must be of type list.")
143+
144+
regex_expression = parameter["regex"]
145+
mode = parameter["mode"]
146+
147+
for item in values:
148+
for founded_value in item.values():
149+
for value in founded_value.values():
150+
match_result = re.search(regex_expression, value)
151+
# Fail if there is not regex match
152+
if mode == "match" and not match_result:
153+
result.update(item)
154+
# Fail if there is regex match
155+
elif mode == "no-match" and match_result:
156+
result.update(item)
157+
158+
return result

tests/test_type_check.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,3 +228,48 @@ def test_param_match(filename, check_args, path, expected_result):
228228
assert actual_results == expected_result, ASSERT_FAIL_MESSAGE.format(
229229
output=actual_results, expected_output=expected_result
230230
)
231+
232+
233+
regex_match_include = (
234+
"pre.json",
235+
("regex", {"regex": ".*UNDERLAY.*", "mode": "match"}),
236+
"result[0].vrfs.default.peerList[*].[$peerAddress$,peerGroup]",
237+
(
238+
{
239+
"7.7.7.7": {"peerGroup": "EVPN-OVERLAY-SPINE"},
240+
},
241+
False,
242+
),
243+
)
244+
245+
regex_match_exclude = (
246+
"pre.json",
247+
("regex", {"regex": ".*UNDERLAY.*", "mode": "no-match"}),
248+
"result[0].vrfs.default.peerList[*].[$peerAddress$,peerGroup]",
249+
(
250+
{
251+
"10.1.0.0": {"peerGroup": "IPv4-UNDERLAY-SPINE"},
252+
"10.2.0.0": {"peerGroup": "IPv4-UNDERLAY-SPINE"},
253+
"10.64.207.255": {"peerGroup": "IPv4-UNDERLAY-MLAG-PEER"},
254+
},
255+
False,
256+
),
257+
)
258+
259+
regex_match = [
260+
regex_match_include,
261+
regex_match_exclude,
262+
]
263+
264+
265+
@pytest.mark.parametrize("filename, check_args, path, expected_result", regex_match)
266+
def test_regex_match(filename, check_args, path, expected_result):
267+
"""Validate regex check type."""
268+
check = CheckType.init(*check_args)
269+
# There is not concept of "pre" and "post" in parameter_match.
270+
data = load_json_file("api", filename)
271+
value = check.get_value(data, path)
272+
actual_results = check.evaluate(value, check_args)
273+
assert actual_results == expected_result, ASSERT_FAIL_MESSAGE.format(
274+
output=actual_results, expected_output=expected_result
275+
)

0 commit comments

Comments
 (0)