Skip to content

Commit 34bb8ba

Browse files
authored
Issue253 coverage (#557)
* add coverage script from Mans with changes based on review in #315 #243 * add unit-test and fix minor bug * Catch case for no examples
1 parent 7670334 commit 34bb8ba

File tree

2 files changed

+152
-1
lines changed

2 files changed

+152
-1
lines changed

buildingspy/development/regressiontest.py

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

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

@@ -589,6 +592,7 @@ def getModelicaCommand(self):
589592
elif self._modelica_tool != 'dymola':
590593
return 'jm_ipython.sh'
591594
else:
595+
return "C://Program Files//Dymola 2023x//bin64//Dymola"
592596
return self._modelica_tool
593597

594598
def isExecutable(self, program):
@@ -771,11 +775,12 @@ def setSinglePackage(self, packageName):
771775

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