Delgan / loguru

Python logging made (stupidly) simple
MIT License
18.69k stars 682 forks source link

Question: How to remove duplication of loguru logger patching in pytest #1153

Closed codeananda closed 2 days ago

codeananda commented 2 weeks ago

I am testing a function which calls loguru.logger.add("file.log") at the start. This causes issues during pytest execution. The file is written to a temp dir and thus is being used by another process (good ol' Windows) when clean-up happens.

PermissionError: [WinError 32] The process cannot access the file because it is being used by another process: 'path/to/tmp_dir/file.log'

One solution is to patch loguru.logger.add on each test. But this results in much repeated (boilerplate?) code. For many tests, I don't need to refer to logger.add, just need patch it so the test runs.

@patch("loguru.logger.add")
def test_one(mock_logger_add):
    ...
    # Useful to check, but don't want to call this in EVERY test
    mock_logger_add.assert_called_once()

@patch("loguru.logger.add")
def test_two(mock_logger_add):
    ...
    # No need to check mock_logger_add, just want my code to run

How can I reduce this duplication?

Things I've tried:

e.g.

@pytest.fixture(autouse=True)
def patch_logger_add(monkeypatch):
    monkeypatch.setattr("loguru.logger.add", lambda *args, **kwargs: None)
    # or
    # monkeypatch.setattr("loguru.logger.add", MagicMock())

or

@pytest.fixture(autouse=True)
def no_logger_add(monkeypatch):
    monkeypatch.delattr("loguru.logger.add")

These don't work. Perhaps because, in order for loguru to work with pytest, we have to redefine caplog and that involves calling logger.add.

This works but only when I do not pass caplog to my tests.

@pytest.fixture(autouse=True)
def patch_logger():
    with patch("loguru.logger.add"):
        yield

Note: I do not want to turn loguru off completely because I check the logs for errors in my tests.

I posted this also on SO but didn't get much response so wondered if I'd get better results here.

Delgan commented 1 week ago

Hey @codeananda.

Could you create a fixture that automatically removes the handlers possible added by your test? Basically, what would happen if you make sure logger.remove() is called at the end of your test?

codeananda commented 2 days ago

Hi @Delgan finally figured it out.

The key is to add caplog to patch_logger_add so that the patch is applied after caplog is created and teardown happens before caplog teardown.

@pytest.fixture(autouse=True)
def patch_logger_add(caplog):
    """Patch logger.add to ensure no files are written to disk.

    Note: we pass caplog to ensure the patch is applied after caplog setup and removed before
    caplog teardown. Otherwise, there are teardown issues when trying to call logger.remove.
    """
    with patch("my_module.logger.add"):
        yield