Skip to content

Commit e68e5ab

Browse files
authored
Merge pull request #168 from DMTF/interop-anyof-allof
Adjusts AnyOf and AllOf implementations to best representation of spec
2 parents f0204ff + f3c0008 commit e68e5ab

File tree

3 files changed

+100
-16
lines changed

3 files changed

+100
-16
lines changed

common/interop.py

Lines changed: 87 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818
class sEnum(Enum):
1919
FAIL = 'FAIL'
20+
NOPASS = 'NO PASS'
2021
PASS = 'PASS'
2122
WARN = 'WARN'
23+
OK = 'OK'
2224

2325

2426
class msgInterop:
@@ -27,11 +29,74 @@ def __init__(self, name, profile_entry, expected, actual, success):
2729
self.entry = profile_entry
2830
self.expected = expected
2931
self.actual = actual
32+
self.ignore = False
3033
if isinstance(success, bool):
3134
self.success = sEnum.PASS if success else sEnum.FAIL
3235
else:
3336
self.success = success
34-
self.parent = None
37+
self.parent_results = None
38+
39+
def validateComparisonAnyOfAllOf(profile_entry, property_path="Unspecified"):
40+
"""
41+
Gather comparison information after processing all Resources on system
42+
"""
43+
all_msgs = []
44+
for key in profile_entry:
45+
property_profile = profile_entry[key]
46+
my_compare = property_profile.get('Comparison', 'AnyOf')
47+
48+
if property_profile.get('Values') and my_compare in ['AnyOf', 'AllOf']:
49+
my_msgs = property_profile.get('_msgs', [])
50+
my_values, expected_values = [m.actual for m in my_msgs], property_profile['Values']
51+
52+
my_logger.info('Validating {} Comparison for {} : {}'.format(my_compare, property_path, key))
53+
my_logger.info(" {}, Expecting {}".format(my_values, expected_values))
54+
55+
if not len(my_msgs) and property_profile.get('ReadRequirement', 'Mandatory') != 'Mandatory':
56+
continue
57+
58+
msg_name = 'Comparison.{}.{}'.format(property_path, key)
59+
60+
top_msg = msgInterop(msg_name, my_compare, expected_values, my_values, False)
61+
all_msgs.append(top_msg)
62+
63+
# NOPASS by default, if the check fails but the value is still in the array
64+
# OK if passing, FAIL if check fails and value is not in array
65+
for msg in my_msgs:
66+
msg.ignore = False
67+
msg.success = sEnum.NOPASS
68+
msg.expected = '{} {} ({})'.format(msg.expected, expected_values, "Across All Resources")
69+
70+
if my_compare == 'AnyOf':
71+
if any([x in my_values for x in expected_values]):
72+
my_logger.info(' PASS')
73+
top_msg.success = sEnum.PASS
74+
for msg in my_msgs:
75+
msg.success = sEnum.OK
76+
if msg.actual in expected_values:
77+
msg.success = sEnum.PASS
78+
else:
79+
my_logger.info(' FAIL')
80+
for msg in my_msgs:
81+
msg.success = sEnum.FAIL
82+
83+
if my_compare == 'AllOf':
84+
if all([x in my_values for x in expected_values]):
85+
my_logger.info(' PASS')
86+
top_msg.success = sEnum.PASS
87+
for msg in my_msgs:
88+
msg.success = sEnum.OK
89+
else:
90+
my_logger.info(' FAIL')
91+
for msg in my_msgs:
92+
if msg.actual not in expected_values:
93+
msg.success = sEnum.FAIL
94+
95+
if property_profile.get('PropertyRequirements'):
96+
new_msgs = validateComparisonAnyOfAllOf(property_profile.get('PropertyRequirements'), '.'.join([property_path, key]))
97+
all_msgs.extend(new_msgs)
98+
99+
return all_msgs
35100

36101

37102
def validateRequirement(profile_entry, rf_payload_item=None, conditional=False, parent_object_tuple=None):
@@ -158,6 +223,9 @@ def checkComparison(val, compareType, target):
158223
paramPass = False
159224
if compareType is None:
160225
my_logger.error('CompareType not available in payload')
226+
227+
# NOTE: In our current usage, AnyOf and AllOf in this context is only for ConditionalRequirements -> CompareProperty
228+
# Which checks if a particular property inside of this instance applies
161229
if compareType == "AnyOf":
162230
for item in vallist:
163231
paramPass = item in target
@@ -374,14 +442,6 @@ def validatePropertyRequirement(propResourceObj, profile_entry, rf_payload_tuple
374442
my_logger.error("MinCount failed")
375443
msgs.append(msg)
376444
msg.name = itemname + '.' + msg.name
377-
for k, v in profile_entry.get('PropertyRequirements', {}).items():
378-
# default to AnyOf if Comparison is not present but Values is
379-
comparisonValue = v.get("Comparison", "AnyOf") if v.get("Values") is not None else None
380-
if comparisonValue in ["AllOf", "AnyOf"]:
381-
msg, success = (checkComparison([val.get(k, 'DNE') for val in rf_payload_item],
382-
comparisonValue, v["Values"]))
383-
msgs.append(msg)
384-
msg.name = itemname + '.' + msg.name
385445
cnt = 0
386446
for item in rf_payload_item:
387447
listmsgs, listcounts = validatePropertyRequirement(
@@ -405,6 +465,7 @@ def validatePropertyRequirement(propResourceObj, profile_entry, rf_payload_tuple
405465
msg.name = itemname + '.' + msg.name
406466
if not success:
407467
my_logger.error("WriteRequirement failed")
468+
408469
if "MinSupportValues" in profile_entry:
409470
msg, success = validateSupportedValues(
410471
profile_entry["MinSupportValues"],
@@ -413,14 +474,25 @@ def validatePropertyRequirement(propResourceObj, profile_entry, rf_payload_tuple
413474
msg.name = itemname + '.' + msg.name
414475
if not success:
415476
my_logger.error("MinSupportValues failed")
416-
if "Comparison" in profile_entry and not chkCondition and\
417-
profile_entry["Comparison"] not in ["AnyOf", "AllOf"]:
418-
msg, success = checkComparison(rf_payload_item,
419-
profile_entry["Comparison"], profile_entry.get("Values",[]))
477+
478+
if "Values" in profile_entry and not chkCondition:
479+
# Default to AnyOf
480+
# NOTE: chkCondition seems to skip this if a ConditionalRequirement is met, this may be unnecessary
481+
482+
my_compare = profile_entry.get("Comparison", "AnyOf")
483+
msg, success = checkComparison(rf_payload_item, my_compare, profile_entry.get("Values", []))
420484
msgs.append(msg)
421485
msg.name = itemname + '.' + msg.name
422-
if not success:
486+
487+
# Embed test results into profile, going forward seems to be the quick option outside of making a proper test object
488+
if my_compare in ['AnyOf', 'AllOf']:
489+
msg.ignore = True
490+
if not profile_entry.get('_msgs'):
491+
profile_entry['_msgs'] = []
492+
profile_entry['_msgs'].append(msg)
493+
elif not success:
423494
my_logger.error("Comparison failed")
495+
424496
if "PropertyRequirements" in profile_entry:
425497
innerDict = profile_entry["PropertyRequirements"]
426498
if isinstance(rf_payload_item, dict):
@@ -593,7 +665,7 @@ def validateInteropResource(propResourceObj, interop_profile, rf_payload):
593665
my_logger.info('Skipping UpdateResource')
594666
pass
595667

596-
for item in msgs:
668+
for item in [item for item in msgs if not item.ignore]:
597669
if item.success == sEnum.WARN:
598670
counts['warn'] += 1
599671
elif item.success == sEnum.PASS:

tohtml.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,10 @@ def applySuccessColor(num, entry):
5252
success_col = str(entry)
5353
if 'FAIL' in str(success_col).upper():
5454
entry = '<td class="fail center">' + str(success_col) + '</td>'
55-
elif 'DEPRECATED' in str(success_col).upper() or 'WARN' in str(success_col).upper():
55+
elif str(success_col).upper() in ['WARN', 'DEPRECATED']:
5656
entry = '<td class="warn center">' + str(success_col) + '</td>'
57+
elif str(success_col).upper() in ['OK', 'NO PASS', 'NOPASS']:
58+
entry = '<td class="center">' + str(success_col) + '</td>'
5759
elif 'PASS' in str(success_col).upper():
5860
entry = '<td class="pass center">' + str(success_col) + '</td>'
5961
else:

validateResource.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,9 @@ def validateSingleURI(URI, profile, uriName='', expectedType=None, expectedSchem
181181
break
182182
my_logger.info("\t {}".format('PASS' if pass_val else' FAIL...'))
183183

184+
for msg in results[uriName]['messages']:
185+
msg.parent_results = results
186+
184187
return True, counts, results, links, propResourceObj
185188

186189
import re
@@ -298,6 +301,7 @@ def validateURITree(URI, profile, uriName, expectedType=None, expectedSchema=Non
298301
resource_stats[SchemaType]['URIsFound'].append(link.rstrip('/'))
299302
resource_stats[SchemaType]['SubordinateTo'].add(tuple(reversed(subordinate_tree)))
300303

304+
301305
if refLinks is not currentLinks and len(newLinks) == 0 and len(refLinks) > 0:
302306
currentLinks = refLinks
303307
else:
@@ -307,6 +311,11 @@ def validateURITree(URI, profile, uriName, expectedType=None, expectedSchema=Non
307311
resources_in_profile = profile.get('Resources', [])
308312
for resource_type in resources_in_profile:
309313
profile_entry = resources_in_profile[resource_type]
314+
315+
if 'PropertyRequirements' in profile_entry:
316+
msgs = interop.validateComparisonAnyOfAllOf(profile_entry['PropertyRequirements'], resource_type)
317+
message_list.extend(msgs)
318+
310319
apply_requirement, expected_requirement = False, None
311320

312321
# If exist and for what URIs...
@@ -360,6 +369,7 @@ def validateURITree(URI, profile, uriName, expectedType=None, expectedSchema=Non
360369
my_msg.expected = "{} at {}".format(my_msg.expected, ", ".join(uris_applied))
361370
message_list.append(my_msg)
362371

372+
363373
# interop service level checks
364374
finalResults = {}
365375
my_logger.info('Service Level Checks')

0 commit comments

Comments
 (0)