Skip to content

Commit

Permalink
foo
Browse files Browse the repository at this point in the history
  • Loading branch information
aeslaughter committed Dec 11, 2020
1 parent d645420 commit 2c75f70
Show file tree
Hide file tree
Showing 14 changed files with 224 additions and 244 deletions.
3 changes: 1 addition & 2 deletions framework/doc/content/templates/sqa/srs.md.template
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,7 @@ testers to test that the system satisfies those requirements.

List each function and its defining requirements here. For each function that is defined, one or more
requirements are then described that define the functionality. The requirements should be as
objective and measurable as possible. Use one of the following examples as a guide for formatting
the requirements.
objective and measurable as possible.

!template-end!

Expand Down
15 changes: 5 additions & 10 deletions framework/doc/sqa_framework.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
functional:
directories:
- ${MOOSE_DIR}/test/tests
specs:
- tests
repo: moose
useability:
files:
- ${MOOSE_DIR}/framework/doc/useability_requirements.hit
prefix: U
directories:
- ${MOOSE_DIR}/test/tests
specs:
- tests
repo: moose
66 changes: 20 additions & 46 deletions python/MooseDocs/extensions/sqa.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def __init__(self, *args, **kwargs):

# Build requirements sets
self.__has_civet = False
self.__requirements = collections.defaultdict(lambda: collections.defaultdict(lambda: collections.defaultdict(list)))
self.__requirements = dict()
self.__dependencies = dict()
self.__remotes = dict()
self.__counts = collections.defaultdict(int)
Expand Down Expand Up @@ -124,14 +124,13 @@ def preExecute(self):
start = time.time()
LOG.info("Gathering SQA requirement information...")

for category, item in self.get('categories').items():
for info in item.values():
self._updateCategoryItems(category, info)
# Build the requirements, dependencies, remotes, and reports data structures
for category, info in self.get('categories').items():
self._updateCategoryItems(category, info)

# Number the requirements
for p in self.__requirements.keys():
for c, category in enumerate(self.__requirements[p].keys()):
moosesqa.number_requirements(self.__requirements[p][category], p, c+1)
for c, category in enumerate(self.__requirements.keys(), start=1):
moosesqa.number_requirements(self.__requirements[category], c)

# Report for the entire app (e.g, moose/modules/doc)
general_reports = self.get('reports')
Expand Down Expand Up @@ -163,20 +162,20 @@ def preExecute(self):
LOG.warning(msg)

# Add RunException tests to FAILURE_ANALYSIS collection
for req_category in self.__requirements['F'].values():
for requirements in req_category.values():
for req in requirements:
t_types = req.types
if (req.collections is None) and (t_types is not None) and ('RunException' in t_types):
req.collections = set(['FAILURE_ANALYSIS'])
for req_category in self.__requirements.values():
for req_prefix in req_category.values():
for requirements in req_prefix.values():
for req in requirements:
t_types = req.types
if (req.collections is None) and (t_types is not None) and ('RunException' in t_types):
req.collections = set(['FAILURE_ANALYSIS'])

LOG.info("Gathering SQA requirement information complete [%s sec.]", time.time() - start)

def _updateCategoryItems(self, category, info):
"""
Helper for processing 'categories' configuration items.
"""
prefix = info.get('prefix', 'F')
specs = info.get('specs', ['tests'])
repo = info.get('repo', 'default')
reports = info.get('reports', None)
Expand All @@ -193,24 +192,8 @@ def _updateCategoryItems(self, category, info):
continue
directories.append(path)

# Custom requirement files
files = list()
for f in info.get('files', []):
loc, fname = os.path.split(f)
path = os.path.join(mooseutils.eval_path(loc), fname)
if not os.path.isfile(path):
path = os.path.join(MooseDocs.ROOT_DIR, loc, fname)
if not os.path.isfile(path):
msg = "Input file does not exist: %s"
LOG.error(msg, path)
continue
files.append(path)

# Create requirement database from tests
self.__requirements[prefix][category].update(moosesqa.get_requirements_from_tests(directories, specs))

# Create requirement database from files
self.__requirements[prefix][category].update(moosesqa.get_requirements_from_files(files))
self.__requirements[category] = moosesqa.get_requirements_from_tests(directories, specs)

# Create dependency database
self.__dependencies[category] = info.get('dependencies', [])
Expand All @@ -223,28 +206,19 @@ def _updateCategoryItems(self, category, info):
if reports:
self.__reports[category] = moosesqa.get_sqa_reports(reports)

def postTokenize(self, page, ast):
"""Update the requirement storage with requirements given in input text."""
for key, value in self.getAttributeItems():
self.__requirements[key[0]][key[1]][key[2]].append(value)

# Re-number to account for custom requirements
for prefix, categories in self.__requirements.items():
for c, out in enumerate(categories.values()):
for i, requirements in enumerate(out.values()):
for j, req in enumerate(requirements):
req.label = "{}{}.{}.{}".format(prefix, c+1, i+1, j+1)

def hasCivetExtension(self):
"""Return True if the CivetExtension exists."""
return self.__has_civet

def requirements(self, category, prefix='F'):
"""Return the requirements dictionaries."""
req = self.__requirements[prefix].get(category, None)
if req is None:
c = self.__requirements.get(category, None)
if c is None:
raise exceptions.MooseDocsException("Unknown or missing 'category': {}", category)
return req
r = c.get(prefix, None)
if r is None:
raise exceptions.MooseDocsException("Unknown or missing 'prefix': {}", prefix)
return r

def dependencies(self, category):
"""Return the dependency list for given category."""
Expand Down
1 change: 1 addition & 0 deletions python/TestHarness/testers/Tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ def validParams():
params.addParam("verification", False, "Set to True to mark test as a verification problem.")
params.addParam("deprecated", False, "When True the test is no longer considered part SQA process and as such does not include the need for a requirement definition.")
params.addParam("collections", [], "A means for defining a collection of tests for SQA process.")
params.addParam("classification", 'functional', "A means for defining a requirement classification for SQA process.")
return params

# This is what will be checked for when we look for valid testers
Expand Down
2 changes: 2 additions & 0 deletions python/moosesqa/Requirement.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ def __str__(self):
@mooseutils.addProperty('design_line', ptype=int)
@mooseutils.addProperty('collections', ptype=set)
@mooseutils.addProperty('collections_line', ptype=int)
@mooseutils.addProperty('classification', ptype=str)
@mooseutils.addProperty('classification_line', ptype=int)
@mooseutils.addProperty('deprecated', ptype=bool, default=False)
@mooseutils.addProperty('deprecated_line', ptype=int)
@mooseutils.addProperty('verification', ptype=list)
Expand Down
5 changes: 3 additions & 2 deletions python/moosesqa/SQARequirementReport.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ def execute(self, **kwargs):
# Build Requirement objects and remove directory based dict
req_dict = get_requirements_from_tests(directories, specs.split())
requirements = []
for values in req_dict.values():
requirements += values
for cat_dict in req_dict.values():
for values in cat_dict.values():
requirements += values

# Populate the lists of tests for SQARequirementDiffReport
self.test_names = set()
Expand Down
8 changes: 7 additions & 1 deletion python/moosesqa/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from .get_sqa_reports import get_sqa_reports
from .check_syntax import check_syntax, file_is_stub, find_md_file
from .get_requirements import get_requirements_from_tests, get_requirements_from_file, get_requirements_from_files
from .get_requirements import get_requirements_from_tests, get_requirements_from_file
from .get_requirements import number_requirements, get_test_specification
from .check_requirements import check_requirements
from .SQAReport import SQAReport
Expand All @@ -20,3 +20,9 @@
MOOSESQA_COLLECTIONS['FAILURE_ANALYSIS'] = "Requirements that perform check for simulation " \
"integrity such as error handling and convergence " \
"failures."

MOOSESQA_CLASSIFICATION = dict()
MOOSESQA_CLASSIFICATION['FUNCTIONAL'] = ('F', "Requirements that define uses cases and are correct, unambiguous, complete, consistent, verifiable, and traceable.")
MOOSESQA_CLASSIFICATION['USABILITY'] = ('U', "Requirements for the system that include measurable effectiveness, efficiency, and satisfaction criteria in specific contexts of use.")
MOOSESQA_CLASSIFICATION['PERFORMANCE'] = ('P', "Requirements to define the critical performance conditions and associated capabilities.")
MOOSESQA_CLASSIFICATION['SYSTEM'] = ('S', "Requirements for interfaces among system elements and with external entities.")
20 changes: 17 additions & 3 deletions python/moosesqa/check_requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def _colorTestInfo(req, filename, name, line):
line = mooseutils.colorText(str(line if (line is not None) else req.line), 'CYAN', colored=RequirementLogHelper.COLOR_TEXT)
return '{}:{}:{}\n'.format(filename, name, line)

def check_requirements(requirements, file_list=None, color_text=True, allowed_collections=None, **kwargs):
def check_requirements(requirements, file_list=None, color_text=True, allowed_collections=None, allowed_classifications=None, **kwargs):
"""
Tool for checking Requirement for deficiencies
"""
Expand Down Expand Up @@ -66,6 +66,7 @@ def check_requirements(requirements, file_list=None, color_text=True, allowed_co
kwargs.setdefault('log_extra_issues', log_default)
kwargs.setdefault('log_extra_collections', log_default)
kwargs.setdefault('log_invalid_collection', log_default)
kwargs.setdefault('log_invalid_classification', log_default)
kwargs.setdefault('log_issue_format', log_default)
kwargs.setdefault('log_design_files', log_default)
kwargs.setdefault('log_validation_files', log_default)
Expand All @@ -90,12 +91,18 @@ def check_requirements(requirements, file_list=None, color_text=True, allowed_co
if allowed_collections is None:
allowed_collections = set(moosesqa.MOOSESQA_COLLECTIONS.keys())

# Setup allowed
if allowed_classifications is None:
allowed_classifications = set(k.upper() for k in moosesqa.MOOSESQA_CLASSIFICATION.keys())
else:
allowed_classifications = set(c.upper() for c in allowed_classifications)

# Storage container for duplicate detection
requirement_dict = collections.defaultdict(set)

# Check each Requirement object for deficiencies
for req in requirements:
_check_requirement(req, logger, file_list, allowed_collections)
_check_requirement(req, logger, file_list, allowed_collections, allowed_classifications)
if req.requirement is not None:
key = [req.requirement]
for detail in req.details:
Expand All @@ -116,7 +123,7 @@ def check_requirements(requirements, file_list=None, color_text=True, allowed_co

return logger

def _check_requirement(req, logger, file_list, allowed_collections):
def _check_requirement(req, logger, file_list, allowed_collections, allowed_classifications):
"""Opens tests specification and extracts requirement items."""

# Test for 'deprecated' with other parameters
Expand Down Expand Up @@ -257,6 +264,13 @@ def _check_requirement(req, logger, file_list, allowed_collections):
msg = 'Invalid collection names found: {}'.format(' '.join(wrong))
LogHelper.log(logger, 'log_invalid_collection', msg)

# Test for invalid 'classification'
if (allowed_classifications is not None) and (req.classification is not None):
if req.classification.upper() not in allowed_classifications:
msg = 'Invalid classification found: {}'.format(req.classification)
LogHelper.log(logger, 'log_invalid_classification', msg)


def _has_file(filename, file_list):
"""Test if the filename is located in the list"""
for f in file_list:
Expand Down

0 comments on commit 2c75f70

Please sign in to comment.