From 9e7ded1ef23634027cbee598a773261a9a325bc1 Mon Sep 17 00:00:00 2001 From: max_metz Date: Fri, 8 Sep 2023 07:12:24 +0200 Subject: [PATCH] adding {file_handling}, {test_handling} and the respective {test_test_handling] from previous projects, docstring-adapted to brecal. Updating the environment yml to ensure, that pytest-cov is also present. Performing the first pytest incl. coverage, where the coverage_reports are gitignored --- .gitignore | 3 +- .../brecal_utils/file_handling.py | 46 ++++++++++ .../brecal_utils/test_handling.py | 84 +++++++++++++++++++ src/lib_brecal_utils/environment.yml | 3 +- .../tests/test_test_handling.py | 48 +++++++++++ 5 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 src/lib_brecal_utils/brecal_utils/file_handling.py create mode 100644 src/lib_brecal_utils/brecal_utils/test_handling.py create mode 100644 src/lib_brecal_utils/tests/test_test_handling.py diff --git a/.gitignore b/.gitignore index 7b220ac..51854ec 100644 --- a/.gitignore +++ b/.gitignore @@ -437,5 +437,6 @@ FodyWeavers.xsd # Local change logs change_log_metz.md - +src/notebooks_metz +src/lib_brecal_utils/**.egg-info diff --git a/src/lib_brecal_utils/brecal_utils/file_handling.py b/src/lib_brecal_utils/brecal_utils/file_handling.py new file mode 100644 index 0000000..0b3c171 --- /dev/null +++ b/src/lib_brecal_utils/brecal_utils/file_handling.py @@ -0,0 +1,46 @@ + + +import os + +def get_project_root(root_base_name:str, root_dir:None=None): + """ + given a {root_base_name}, this function searches the parent folders of {root_dir} until + the basename matches. + + Example: + root_base_name = "brecal" + root_dir = "/home/arbitrary_user/brecal/_template/tests" + + returns: "/home/arbitrary_user/brecal" + + arguments: + root_base_name:str, base directory name that should be searched for + root_dir: defaults to 'None', whereas then the current working directory is selected. Can be an arbitrary path. + + returns: root_dir + """ + if root_dir is None: + root_dir = os.getcwd() + + assert root_base_name in root_dir, f"the desired base name MUST be present within the root directory.\nRoot Directory: {root_dir}\nDesired Root Base Name: {root_base_name}" + assert root_dir.count(root_base_name)==1, f"found multiple matches for root_base_name" # do not change, as a pytest requires precise wording + + + while not os.path.basename(root_dir)==root_base_name: + root_dir = os.path.dirname(root_dir) + + return root_dir + +def ensure_path(path, print_info=0): + """ + Function ensures that a certain directory exists. If it does not exist, it will be created. + It further checks if the parent-directory of the file exists and also ensures that. + + options: + print_info: print additional information (debugging) + """ + if not os.path.exists(path): + os.makedirs(path) + if print_info == 1: + print(f"Created directory and subdirectories: {path}") + return diff --git a/src/lib_brecal_utils/brecal_utils/test_handling.py b/src/lib_brecal_utils/brecal_utils/test_handling.py new file mode 100644 index 0000000..3757f0d --- /dev/null +++ b/src/lib_brecal_utils/brecal_utils/test_handling.py @@ -0,0 +1,84 @@ + +def execute_test_with_pytest(filepath): + """ + creates a subprocess to use 'pytest' on a script. Every function inside the filepath + will be tested individually. The function returns verbose information about the outcome. + + filepath: + can either be an individual .py file or a root directory, which contains multiple files + """ + import os + import pytest + from subprocess import Popen, PIPE + + assert os.path.exists(filepath), f"cannot find file {filepath}" + + with Popen(['pytest', + '-v', + '-W ignore::DeprecationWarning', + '-vv', + '--durations=0', + '--tb=short', # shorter traceback format + str(filepath)], stdout=PIPE, bufsize=1, + universal_newlines=True) as p: + for line in p.stdout: + print(line, end='') + return + +def execute_coverage_test(tests_path, coverage_path, cov_report_dst_dir=None, cov_fail_under_rate=80, is_test=0): + """ + creates a subprocess to use 'coverage' on a script. Every function inside the file + will be tested individually. The function returns verbose information about the outcome. + + this function needs two inputs: + tests_path, a path that locates each test that should be executed + e.g.: "/home/scope_sorting/brecal/src/lib_brecal_utils/tests" + + coverage_path, a path where the code is stored, which should be analyzed for coverage + e.g.: "/home/scope_sorting/brecal/src/lib_brecal_utils/brecal_utils" + + optional: + cov_report_dst_dir, which determines, where the coverage report will be stored. This function then + creates & stores an .html and .xml report in that folder. default: None + + cov_fail_under_rate, an integer which determines, when a coverage test should fail. Default: 80, meaning + that at least 80 % of the directory should be tested to pass the test. + """ + import os + import pytest + from subprocess import Popen, PIPE + + assert os.path.exists(tests_path), f"cannot find root directory {tests_path}" + assert os.path.exists(coverage_path), f"cannot find root directory {coverage_path}" + + if cov_report_dst_dir is not None: + p_open_list_arguments = [ + 'pytest', + f"{str(tests_path)}", + "-v", + "-vv", + "--durations=0", + "--cov-report=term", + f"--cov-report=html:{cov_report_dst_dir}", + f"--cov-report=xml:{cov_report_dst_dir}/coverage.xml", + f"--cov-fail-under={cov_fail_under_rate}", + f"--cov={str(coverage_path)}", + ] + else: + p_open_list_arguments = [ + 'pytest', + f"{str(tests_path)}", + "-v", + "-vv", + "--durations=0", + "--cov-report=term", + f"--cov-fail-under={cov_fail_under_rate}", + f"--cov={str(coverage_path)}", + ] + + with Popen(p_open_list_arguments, stdout=PIPE, bufsize=1, + universal_newlines=True) as p: + for line in p.stdout: + print(line, end='') + if is_test: + raise KeyboardInterrupt("is_test_interrupt") diff --git a/src/lib_brecal_utils/environment.yml b/src/lib_brecal_utils/environment.yml index cb7e07d..09a6d55 100644 --- a/src/lib_brecal_utils/environment.yml +++ b/src/lib_brecal_utils/environment.yml @@ -16,7 +16,8 @@ dependencies: - conda-forge::matplotlib-base>=3.7.2=py311h54ef318_0 - conda-forge::matplotlib>=3.7.2=py311h38be061_0 - matplotlib-inline>=0.1.6=pyhd8ed1ab_0 - - pytest>=7.4.0=pyhd8ed1ab_0 + - conda-forge::pytest>=7.4.0=pyhd8ed1ab_0 + - conda-forge::pytest-cov>=4.1.0=pyhd8ed1ab_0 - python>=3.11.4=hab00c5b_0_cpython - pytz>=2023.3=pyhd8ed1ab_0 - setuptools>=68.0.0=pyhd8ed1ab_0 diff --git a/src/lib_brecal_utils/tests/test_test_handling.py b/src/lib_brecal_utils/tests/test_test_handling.py new file mode 100644 index 0000000..877766e --- /dev/null +++ b/src/lib_brecal_utils/tests/test_test_handling.py @@ -0,0 +1,48 @@ +import unittest +import pytest + +def test_execute_coverage_test(): + """ + executes {execute_coverage_test} to check, whether reporting works as expected + """ + import os + import brecal_utils + + from brecal_utils.file_handling import get_project_root + from brecal_utils.test_handling import execute_coverage_test + + root_dir = brecal_utils.__file__ + root_dir = get_project_root("lib_brecal_utils", root_dir=root_dir) + + tests_path = os.path.join(root_dir, "tests") + coverage_path = os.path.join(root_dir, "brecal_utils") + report_path = os.path.join(root_dir, "coverage_reports") + + with pytest.raises(KeyboardInterrupt, match="is_test_interrupt"): + execute_coverage_test(tests_path=tests_path, coverage_path=coverage_path, cov_report_dst_dir=report_path, cov_fail_under_rate=0, is_test=1) + return + +def test_execute_coverage_test_no_report(): + """ + executes {execute_coverage_test} to check, whether the function also works without reporting + """ + import os + import brecal_utils + + from brecal_utils.file_handling import get_project_root + from brecal_utils.test_handling import execute_coverage_test + + root_dir = brecal_utils.__file__ + root_dir = get_project_root("lib_brecal_utils", root_dir=root_dir) + + tests_path = os.path.join(root_dir, "tests") + coverage_path = os.path.join(root_dir, "brecal_utils") + report_path = os.path.join(root_dir, "coverage_reports") + + with pytest.raises(KeyboardInterrupt, match="is_test_interrupt"): + execute_coverage_test(tests_path=tests_path, coverage_path=coverage_path, cov_report_dst_dir=None, cov_fail_under_rate=0, is_test=1) + return + + +if __name__=="__main__": + pass