Skip to content

Commit fd5b7be

Browse files
committed
Pass child process log msgs to parent logger
Without that it's not possible to use caplog or other log capturing mechanisms in unit tests. QueueHandler in Child Process: - The child process uses a QueueHandler to send log messages to the logger queue. QueueListener in Parent Process: - The parent process uses a QueueListener to listen for log messages from the child process and handle them using the parent logger's handlers. Exception Handling: - Exceptions raised in the child process are sent back to the parent process via the result queue. - This helps with situations where failing functions executed in a child process lead to the last traceback point to be something like: ``` File "/usr/lib/python2.7/site-packages/convert2rhel/utils.py", line 246, in wrapper raise process.exception ``` Assisted by GitHub Copilot.
1 parent 52fb6ca commit fd5b7be

File tree

3 files changed

+56
-4
lines changed

3 files changed

+56
-4
lines changed

convert2rhel/pkghandler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ def get_files_owned_by_package(installed_pkg_name):
299299
if ret_code != 0:
300300
logger.warning("Failed to list files for package {0}: {1}".format(installed_pkg_name, output))
301301
return []
302-
return output.decode("utf-8").splitlines()
302+
return output.splitlines()
303303

304304

305305
@utils.run_as_child_process

convert2rhel/unit_tests/pkghandler_test.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1860,3 +1860,35 @@ def test_get_highest_package_version(packages, expected):
18601860
def test_get_highest_package_version_value_err(packages):
18611861
with pytest.raises(ValueError):
18621862
pkghandler.get_highest_package_version(pkgs=packages)
1863+
1864+
1865+
@pytest.mark.parametrize(
1866+
"installed_pkg_name, subprocess_output, subprocess_ret_code, expected_files, expected_warning",
1867+
[
1868+
("pkg1", "/etc/file1\n/etc/file2\n", 0, ["/etc/file1", "/etc/file2"], None),
1869+
("pkg2", "", 0, [], None),
1870+
(
1871+
"pkg3",
1872+
"package pkg3 is not installed\n",
1873+
1,
1874+
[],
1875+
"Failed to list files for package pkg3: package pkg3 is not installed",
1876+
),
1877+
],
1878+
)
1879+
def test_get_files_owned_by_package(
1880+
monkeypatch, caplog, installed_pkg_name, subprocess_output, subprocess_ret_code, expected_files, expected_warning
1881+
):
1882+
def mock_run_subprocess(cmd, print_cmd=False, print_output=False):
1883+
return subprocess_output, subprocess_ret_code
1884+
1885+
monkeypatch.setattr(utils, "run_subprocess", mock_run_subprocess)
1886+
1887+
result = pkghandler.get_files_owned_by_package(installed_pkg_name)
1888+
1889+
assert result == expected_files
1890+
1891+
if expected_warning:
1892+
assert expected_warning in caplog.text
1893+
else:
1894+
assert "Failed to list files" not in caplog.text

convert2rhel/utils/__init__.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import fcntl
2121
import getpass
2222
import json
23+
import logging
2324
import multiprocessing
2425
import os
2526
import re
@@ -224,11 +225,25 @@ def inner_wrapper(*args, **kwargs):
224225
"""
225226
func = kwargs.pop("func")
226227
queue = kwargs.pop("queue")
227-
result = func(*args, **kwargs)
228-
queue.put(result)
228+
logger_queue = kwargs.pop("logger_queue")
229+
230+
# Redirect log messages to the logger queue
231+
queue_handler = logging.handlers.QueueHandler(logger_queue)
232+
logger = logging.getLogger()
233+
logger.addHandler(queue_handler)
234+
235+
try:
236+
result = func(*args, **kwargs)
237+
queue.put(result)
238+
except Exception as e:
239+
queue.put(e)
240+
finally:
241+
logger.removeHandler(queue_handler)
229242

230243
queue = multiprocessing.Queue()
231-
kwargs.update({"func": func, "queue": queue})
244+
logger_queue = multiprocessing.Queue()
245+
kwargs.update({"func": func, "queue": queue, "logger_queue": logger_queue})
246+
232247
process = Process(target=inner_wrapper, args=args, kwargs=kwargs)
233248

234249
# Running the process as a daemon prevents it from hanging if a SIGINT
@@ -237,7 +252,12 @@ def inner_wrapper(*args, **kwargs):
237252
process.daemon = True
238253
try:
239254
process.start()
255+
# Set up a listener for log messages from the child process
256+
listener = logging.handlers.QueueListener(logger_queue, *logging.getLogger().handlers)
257+
listener.start()
258+
240259
process.join()
260+
listener.stop()
241261

242262
if process.exception:
243263
raise process.exception

0 commit comments

Comments
 (0)