Skip to content

Commit

Permalink
Suppress "Compile called before Add" in re2.Filter
Browse files Browse the repository at this point in the history
When compiling an empty set, ``FilteredRE2::Compile`` logs a warning
to stderr which can not be suppressed (google/re2#485).

Use low-level fd redirection to suppress that message as it does not
seem to hurt, and not compiling is a lot worse (google/re2#484).

To add to the fun, when running under pytest not only is stderr
captured `sys.stderr` is redirected to an object with a different fd.
Since the C stdlib does not even know about that it obviously keeps
writing to the "normal" stderr, so use the raw fd number (2) instead
of use the symbolic `sys.stderr.fileno()`.

Thankfully this mess seems to be working on windows

Less thankfully, since fds are process global and there's no real lock
I know of this introduces a race, anyone could be trying to write to
stderr while we've redirected the fd and I don't know how to fix
this...
  • Loading branch information
masklinn committed Mar 16, 2024
1 parent b45380d commit dc1527f
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 1 deletion.
20 changes: 19 additions & 1 deletion src/ua_parser/re2.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
__all__ = ["Resolver"]

import contextlib
import os
import re
from typing import List
import sys
from typing import Iterator, List

import re2 # type: ignore

Expand All @@ -15,6 +18,21 @@
UserAgent,
)

devnull = os.open(os.devnull, os.O_WRONLY | os.O_APPEND)
@contextlib.contextmanager
def suppress_stderr() -> Iterator[None]:
"""Temporarily redirect stderr to devnull.
"""
with contextlib.ExitStack() as defer:
old = os.dup(2)
defer.callback(os.close, old)

os.dup2(devnull, 2)
defer.callback(os.dup2, old, 2)

yield


class Resolver:
ua: re2.Filter
Expand Down
29 changes: 29 additions & 0 deletions tests/test_re2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import pytest

from ua_parser import Domain, PartialParseResult
from ua_parser.loaders import load_data
re2 = pytest.importorskip("ua_parser.re2")
from re2 import Filter


def test_suppress_stderr(capfd) -> None:
f = Filter()
f.Compile()
_, err = capfd.readouterr()
assert err == "re2/filtered_re2.cc:74: Compile called before Add.\n"

f = Filter()
with re2.suppress_stderr():
f.Compile()
out, err = capfd.readouterr()
assert out == ""
assert err == ""


def test_empty(capfd) -> None:
r = re2.Resolver(([], [], []))
assert r("", Domain.ALL) == PartialParseResult(Domain.ALL, None, None, None, "")
out, err = capfd.readouterr()
assert out == ""
assert err == ""

0 comments on commit dc1527f

Please sign in to comment.