Skip to content

Commit 585a47f

Browse files
mwetterFWuellhorst
andauthored
Issue253 coverage (#557) (#563)
* Issue253 coverage (#557) * add coverage script from Mans with changes based on review in #315 #243 --------- Co-authored-by: FWuellhorst <[email protected]>
1 parent c6dfed5 commit 585a47f

File tree

3 files changed

+170
-1
lines changed

3 files changed

+170
-1
lines changed

buildingspy/CHANGES.txt

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ Version 5.2.0, xxxx
1010
- In buildingspy/development/regressiontest.py, add option to create reference
1111
results in batch mode.
1212
(https://github.com/lbl-srg/BuildingsPy/issues/560)
13+
- In buildingspy/development/regressiontest.py, add option to get the coverage
14+
rate, i.e., what percentage of examples are covered by regression tests.
15+
(https://github.com/lbl-srg/BuildingsPy/issues/253)
1316
- For Optimica regression tests, added check for Integers that are too large to be represented
1417
- In buildingspy/development/refactor.py, corrected moving images to avoid creating
1518
a directory if the target directory already exists.

buildingspy/development/regressiontest.py

+132-1
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,9 @@ def __init__(
352352
self._data = []
353353
self._reporter = rep.Reporter(os.path.join(os.getcwd(), "unitTests-{}.log".format(tool)))
354354

355+
# List to store tested packages, used for coverage report
356+
self._packages = []
357+
355358
# By default, include export of FMUs.
356359
self._include_fmu_test = True
357360

@@ -776,11 +779,12 @@ def setSinglePackage(self, packageName):
776779

777780
# Set data dictionary as it may have been generated earlier for the whole library.
778781
self._data = []
779-
782+
self._packages = []
780783
for pac in packages:
781784
pacSep = pac.find('.')
782785
pacPat = pac[pacSep + 1:]
783786
pacPat = pacPat.replace('.', os.sep)
787+
self._packages.append(pacPat)
784788
rooPat = os.path.join(self._libHome, 'Resources', 'Scripts', 'Dymola', pacPat)
785789
# Verify that the directory indeed exists
786790
if not os.path.isdir(rooPat):
@@ -4297,3 +4301,130 @@ def _model_from_mo(self, mo_file):
42974301
model = '.'.join(splt[root:])
42984302
# remove the '.mo' at the end
42994303
return model[:-3]
4304+
4305+
def getCoverage(self):
4306+
"""
4307+
Analyse how many examples are tested.
4308+
If ``setSinglePackage`` is called before this function,
4309+
only packages set will be included. Else, the whole library
4310+
will be checked.
4311+
4312+
Returns:
4313+
- The coverage rate in percent as float
4314+
- The number of examples tested as int
4315+
- The total number of examples as int
4316+
- The list of models not tested as List[str]
4317+
- The list of packages included in the analysis as List[str]
4318+
4319+
Example:
4320+
>>> from buildingspy.development.regressiontest import Tester
4321+
>>> import os
4322+
>>> ut = Tester(tool='dymola')
4323+
>>> myMoLib = os.path.join("buildingspy", "tests", "MyModelicaLibrary")
4324+
>>> ut.setLibraryRoot(myMoLib)
4325+
>>> ut.setSinglePackage('Examples')
4326+
Regression tests are only run for the following package:
4327+
Examples
4328+
MyModelicaLibrary.Examples.NoSolution: Excluded from simulation. Model excluded from simulation as it has no solution.
4329+
>>> coverage_result = ut.getCoverage()
4330+
"""
4331+
# first lines copy and paste from run function
4332+
if self.get_number_of_tests() == 0:
4333+
self.setDataDictionary(self._rootPackage)
4334+
4335+
# Remove all data that do not require a simulation or an FMU export.
4336+
# Otherwise, some processes may have no simulation to run and then
4337+
# the json output file would have an invalid syntax
4338+
4339+
# now we got clean _data to compare
4340+
# next step get all examples in the package (whether whole library or
4341+
# single package)
4342+
if self._packages:
4343+
packages = self._packages
4344+
else:
4345+
packages = list(dict.fromkeys(
4346+
[pac['ScriptFile'].split(os.sep)[0] for pac in self._data])
4347+
)
4348+
4349+
all_examples = []
4350+
for package in packages:
4351+
package_path = os.path.join(self._libHome, package)
4352+
for dirpath, dirnames, filenames in os.walk(package_path):
4353+
for filename in filenames:
4354+
filepath = os.path.abspath(os.path.join(dirpath, filename))
4355+
if any(
4356+
xs in filepath for xs in ['Examples', 'Validation']
4357+
) and not filepath.endswith(('package.mo', '.order')):
4358+
all_examples.append(filepath)
4359+
4360+
n_tested_examples = len(self._data)
4361+
n_examples = len(all_examples)
4362+
if n_examples > 0:
4363+
coverage = round(n_tested_examples / n_examples, 2) * 100
4364+
else:
4365+
coverage = 100
4366+
4367+
tested_model_names = [
4368+
nam['ScriptFile'].split(os.sep)[-1][:-1] for nam in self._data
4369+
]
4370+
4371+
missing_examples = [
4372+
i for i in all_examples if not any(
4373+
xs in i for xs in tested_model_names)
4374+
]
4375+
4376+
return coverage, n_tested_examples, n_examples, missing_examples, packages
4377+
4378+
def printCoverage(
4379+
self,
4380+
coverage: float,
4381+
n_tested_examples: int,
4382+
n_examples: int,
4383+
missing_examples: list,
4384+
packages: list,
4385+
printer: callable = None
4386+
) -> None:
4387+
"""
4388+
Print the output of getCoverage to inform about
4389+
coverage rate and missing models.
4390+
The default printer is the ``reporter.writeOutput``.
4391+
If another printing method is required, e.g. ``print`` or
4392+
``logging.info``, it may be passed via the ``printer`` argument.
4393+
4394+
Example:
4395+
>>> from buildingspy.development.regressiontest import Tester
4396+
>>> import os
4397+
>>> ut = Tester(tool='dymola')
4398+
>>> myMoLib = os.path.join("buildingspy", "tests", "MyModelicaLibrary")
4399+
>>> ut.setLibraryRoot(myMoLib)
4400+
>>> ut.setSinglePackage('Examples')
4401+
Regression tests are only run for the following package:
4402+
Examples
4403+
MyModelicaLibrary.Examples.NoSolution: Excluded from simulation. Model excluded from simulation as it has no solution.
4404+
>>> coverage_result = ut.getCoverage()
4405+
>>> ut.printCoverage(*coverage_result, printer=print)
4406+
***
4407+
Model Coverage: 88 %
4408+
***
4409+
You are testing: 7 out of 8 examples in package:
4410+
Examples
4411+
***
4412+
The following examples are not tested
4413+
<BLANKLINE>
4414+
/Examples/ParameterEvaluation.mo
4415+
4416+
"""
4417+
if printer is None:
4418+
printer = self._reporter.writeOutput
4419+
printer(f'***\nModel Coverage: {int(coverage)} %')
4420+
printer(
4421+
f'***\nYou are testing: {n_tested_examples} '
4422+
f'out of {n_examples} examples in package{"s" if len(packages) > 1 else ""}:',
4423+
)
4424+
for package in packages:
4425+
printer(package)
4426+
4427+
if missing_examples:
4428+
print('***\nThe following examples are not tested\n')
4429+
for i in missing_examples:
4430+
print(i.split(self._libHome)[1])

buildingspy/tests/test_development_regressiontest.py

+35
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,41 @@ def test_expand_packages(self):
326326
self.assertRaises(ValueError,
327327
r.Tester.expand_packages, "AB}a{")
328328

329+
def test_get_coverage_single_package(self):
330+
coverage_result = self._test_get_and_print_coverage(package="Examples")
331+
self.assertEqual(coverage_result[0], 88)
332+
self.assertEqual(coverage_result[1], 7)
333+
self.assertEqual(coverage_result[2], 8)
334+
self.assertTrue(coverage_result[3][0].endswith("ParameterEvaluation.mo"))
335+
self.assertEqual(coverage_result[4], ["Examples"])
336+
337+
def test_get_coverage_all_packages(self):
338+
coverage_result = self._test_get_and_print_coverage(package=None)
339+
self.assertEqual(coverage_result[0], 89)
340+
self.assertEqual(coverage_result[1], 8)
341+
self.assertEqual(coverage_result[2], 9)
342+
self.assertEqual(len(coverage_result[3]), 1)
343+
self.assertEqual(len(coverage_result[4]), 2)
344+
345+
def _test_get_and_print_coverage(self, package: str = None):
346+
import buildingspy.development.regressiontest as r
347+
ut = r.Tester(tool='dymola')
348+
myMoLib = os.path.join("buildingspy", "tests", "MyModelicaLibrary")
349+
ut.setLibraryRoot(myMoLib)
350+
if package is not None:
351+
ut.setSinglePackage(package)
352+
coverage_result = ut.getCoverage()
353+
self.assertIsInstance(coverage_result, tuple)
354+
self.assertIsInstance(coverage_result[0], float)
355+
self.assertIsInstance(coverage_result[1], int)
356+
self.assertIsInstance(coverage_result[2], int)
357+
self.assertIsInstance(coverage_result[3], list)
358+
self.assertIsInstance(coverage_result[4], list)
359+
# Check print with both custom and standard printer
360+
ut.printCoverage(*coverage_result, printer=print)
361+
ut.printCoverage(*coverage_result)
362+
return coverage_result
363+
329364

330365
if __name__ == '__main__':
331366
unittest.main()

0 commit comments

Comments
 (0)