|
| 1 | +import io |
| 2 | +import logging |
| 3 | + |
1 | 4 | import numpy as np |
2 | 5 | import pandas as pd |
3 | 6 | import pytest |
|
8 | 11 | from malariagen_data import ag3 as _ag3 |
9 | 12 | from malariagen_data import adir1 as _adir1 |
10 | 13 | from malariagen_data.anoph.base import AnophelesBase |
| 14 | +from malariagen_data.util import LoggingHelper |
11 | 15 |
|
12 | 16 |
|
13 | 17 | @pytest.fixture |
@@ -258,6 +262,47 @@ def test_lookup_study(fixture, api): |
258 | 262 | api.lookup_study("foobar") |
259 | 263 |
|
260 | 264 |
|
| 265 | +def test_logging_helper_no_handler_accumulation(): |
| 266 | + # Regression test: repeated LoggingHelper construction on the same logger |
| 267 | + # name must not accumulate handlers (StreamHandler leak, FileHandler FD leak). |
| 268 | + logger_name = "test_logging_helper_no_handler_accumulation" |
| 269 | + for _ in range(10): |
| 270 | + LoggingHelper(name=logger_name, out=io.StringIO()) |
| 271 | + logger = logging.getLogger(logger_name) |
| 272 | + assert ( |
| 273 | + len(logger.handlers) <= 1 |
| 274 | + ), f"Handler leak: {len(logger.handlers)} handlers after 10 instantiations" |
| 275 | + |
| 276 | + |
| 277 | +def test_logging_helper_no_duplicate_output(): |
| 278 | + # Regression test: a message emitted after N instantiations must appear |
| 279 | + # exactly once in the output stream. |
| 280 | + logger_name = "test_logging_helper_no_duplicate_output" |
| 281 | + out = io.StringIO() |
| 282 | + for _ in range(5): |
| 283 | + helper = LoggingHelper(name=logger_name, out=out) |
| 284 | + helper.info("sentinel") |
| 285 | + output = out.getvalue() |
| 286 | + assert ( |
| 287 | + output.count("sentinel") == 1 |
| 288 | + ), f"Duplicate log output: 'sentinel' appeared {output.count('sentinel')} times" |
| 289 | + |
| 290 | + |
| 291 | +def test_logging_helper_set_level_updates_logger(): |
| 292 | + # Regression test: set_level() must update the logger level, not just the |
| 293 | + # handler level. Without fixing the logger, the logger itself would filter |
| 294 | + # out DEBUG messages before they ever reached the handler. |
| 295 | + logger_name = "test_logging_helper_set_level_updates_logger" |
| 296 | + out = io.StringIO() |
| 297 | + helper = LoggingHelper(name=logger_name, out=out, debug=False) |
| 298 | + helper.set_level(logging.DEBUG) |
| 299 | + helper.debug("should appear") |
| 300 | + output = out.getvalue() |
| 301 | + assert "should appear" in output, ( |
| 302 | + "set_level(DEBUG) had no effect: debug message was silently dropped" |
| 303 | + ) |
| 304 | + |
| 305 | + |
261 | 306 | def _strip_terms_of_use_from_manifest(manifest_path): |
262 | 307 | """Rewrite a manifest TSV file without terms-of-use columns.""" |
263 | 308 | df = pd.read_csv(manifest_path, sep="\t") |
|
0 commit comments