diff --git a/CHANGES.rst b/CHANGES.rst index 72ca6ad..462e681 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -10,8 +10,14 @@ Misc + `#14`_: drop support for Python 3.5. -.. _#14: https://github.com/RKrahl/auto-patch/pull/14 +Bug fixes and minor changes +--------------------------- ++ `#16`_, `#17`_: Review of the test framework. + +.. _#14: https://github.com/RKrahl/auto-patch/pull/14 +.. _#16: https://github.com/RKrahl/auto-patch/issues/16 +.. _#17: https://github.com/RKrahl/auto-patch/pull/17 1.1.0 (2022-10-03) diff --git a/tests/conftest.py b/tests/conftest.py index 1a72ee1..2590cdb 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,6 @@ import argparse from configparser import ConfigParser +import contextlib import json from multiprocessing import Process import os @@ -11,9 +12,61 @@ test_dir = Path(__file__).parent script_dir = Path(os.environ['BUILD_SCRIPTS_DIR']) +auto_patch_path = script_dir / "auto-patch.py" os.environ['AUTO_PATCH_CFG'] = "auto-patch.cfg" +def get_zypper_argument_parser(): + parser = argparse.ArgumentParser() + parser.add_argument('--quiet', action='store_true') + parser.add_argument('--non-interactive', action='store_true') + parser.add_argument('subcmd') + parser.add_argument('--skip-interactive', action='store_true') + return parser + +zypper_arg_parser = get_zypper_argument_parser() + +class mock_subprocess_run: + """A mock replacement for subprocess.run. + It fakes all invocations of the zypper binary. + """ + def __init__(self, results): + self.results_iter = iter(results) + + def __call__(self, cmd, stdout=None, **kwargs): + zypp_res = next(self.results_iter) + assert Path(cmd[0]).name == "zypper" + args = zypper_arg_parser.parse_args(args=cmd[1:]) + assert args.subcmd == zypp_res.cmd + stdout.write(zypp_res.stdout) + return subprocess.CompletedProcess(cmd, zypp_res.returncode, + stderr=zypp_res.stderr) + +class mock_smtp(contextlib.AbstractContextManager): + """A mock replacement for smtplib.SMTP. + Instead of sending the mail, it pickles it to a well known file. + """ + def __init__(self, host='', **kwargs): + self.host = host + + def __exit__(self, *args): + pass + + def send_message(self, msg, **kwargs): + with open("report.pickle", "wb") as f: + pickle.dump(self.host, f) + pickle.dump(msg, f) + +def invoke_auto_patch(zypper_results): + """Patch the current Python intepreter and execute auto-patch.py. + This function is supposed to be the target of a Process. + """ + import subprocess + import smtplib + subprocess.run = mock_subprocess_run(zypper_results) + smtplib.SMTP = mock_smtp + with auto_patch_path.open("rt") as script: + exec(script.read(), dict(__name__="__main__")) class ZypperResult: """Represent the result of one mock zypper call in AutoPatchCaller. @@ -32,16 +85,15 @@ class AutoPatchCaller: relations to the outside world for the script. """ - auto_patch_path = script_dir / "auto-patch.py" - _zypper_result_data = None - @classmethod def _get_zypper_result_data(cls): - if not cls._zypper_result_data: + try: + return cls._zypper_result_data + except AttributeError: datafile = test_dir / "zypper-result-data.json" with datafile.open("rt") as f: cls._zypper_result_data = json.load(f) - return cls._zypper_result_data + return cls._zypper_result_data @classmethod def get_caller(cls, case, config=None): @@ -62,46 +114,10 @@ def _create_config(self, config): def __init__(self, zypper_results, config=None): self._create_config(config) - parser = argparse.ArgumentParser() - parser.add_argument('--quiet', action='store_true') - parser.add_argument('--non-interactive', action='store_true') - parser.add_argument('subcmd') - parser.add_argument('--skip-interactive', action='store_true') - self.zypper_arg_parser = parser self.zypper_results = zypper_results - self.results_iter = iter(self.zypper_results) - - def _mock_subprocess_run(self, cmd, stdout=None, **kwargs): - zypp_res = next(self.results_iter) - assert Path(cmd[0]).name == "zypper" - args = self.zypper_arg_parser.parse_args(args=cmd[1:]) - assert args.subcmd == zypp_res.cmd - stdout.write(zypp_res.stdout) - return subprocess.CompletedProcess(cmd, zypp_res.returncode, - stderr=zypp_res.stderr) - - class _mock_smtp: - def __init__(self, host='', **kwargs): - self.host = host - def __enter__(self): - return self - def __exit__(self, *args): - pass - def send_message(self, msg, **kwargs): - with open("report.pickle", "wb") as f: - pickle.dump(self.host, f) - pickle.dump(msg, f) - - def _patch_and_call(self): - import subprocess - import smtplib - subprocess.run = self._mock_subprocess_run - smtplib.SMTP = self._mock_smtp - with self.auto_patch_path.open("rt") as script: - exec(script.read(), dict(__name__="__main__")) def run(self, exitcode=0): - p = Process(target=self._patch_and_call) + p = Process(target=invoke_auto_patch, args=(self.zypper_results,)) p.start() p.join() assert p.exitcode == exitcode