diff --git a/AUTHORS b/AUTHORS index 8f184f353..d5a107103 100644 --- a/AUTHORS +++ b/AUTHORS @@ -106,6 +106,7 @@ Edson Tadeu M. Manoel Eduardo Schettino Eli Boyarski Elizaveta Shashkova +Éloi Rivard Endre Galaczi Eric Hunsberger Eric Liu diff --git a/changelog/7132.feature.rst b/changelog/7132.feature.rst new file mode 100644 index 000000000..9fb735ee1 --- /dev/null +++ b/changelog/7132.feature.rst @@ -0,0 +1 @@ +Added two environment variables :envvar:`PYTEST_THEME` and :envvar:`PYTEST_THEME_MODE` to let the users customize the pygments theme used. diff --git a/doc/en/reference/customize.rst b/doc/en/reference/customize.rst index d6fb5c6b2..035d0f7ad 100644 --- a/doc/en/reference/customize.rst +++ b/doc/en/reference/customize.rst @@ -239,3 +239,11 @@ Builtin configuration file options ---------------------------------------------- For the full list of options consult the :ref:`reference documentation `. + +Syntax highlighting theme customization +--------------------------------------- + +The syntax highlighting themes used by pytest can be customized using two environment variables: + +- :envvar:`PYTEST_THEME` sets a `pygment style `_ to use. +- :envvar:`PYTEST_THEME_MODE` sets this style to *light* or *dark*. diff --git a/doc/en/reference/reference.rst b/doc/en/reference/reference.rst index b9b68c01d..db49b0f1d 100644 --- a/doc/en/reference/reference.rst +++ b/doc/en/reference/reference.rst @@ -1075,6 +1075,14 @@ Contains comma-separated list of modules that should be loaded as plugins: export PYTEST_PLUGINS=mymodule.plugin,xdist +.. envvar:: PYTEST_THEME + +Sets a `pygment style `_ to use for the code output. + +.. envvar:: PYTEST_THEME_MODE + +Sets the :envvar:`PYTEST_THEME` to be either *dark* or *light*. + .. envvar:: PY_COLORS When set to ``1``, pytest will use color in terminal output. diff --git a/src/_pytest/_io/terminalwriter.py b/src/_pytest/_io/terminalwriter.py index 8edf4cd75..379035d85 100644 --- a/src/_pytest/_io/terminalwriter.py +++ b/src/_pytest/_io/terminalwriter.py @@ -195,16 +195,39 @@ class TerminalWriter: def _highlight(self, source: str) -> str: """Highlight the given source code if we have markup support.""" + from _pytest.config.exceptions import UsageError + if not self.hasmarkup or not self.code_highlight: return source try: from pygments.formatters.terminal import TerminalFormatter from pygments.lexers.python import PythonLexer from pygments import highlight + import pygments.util except ImportError: return source else: - highlighted: str = highlight( - source, PythonLexer(), TerminalFormatter(bg="dark") - ) - return highlighted + try: + highlighted: str = highlight( + source, + PythonLexer(), + TerminalFormatter( + bg=os.getenv("PYTEST_THEME_MODE", "dark"), + style=os.getenv("PYTEST_THEME"), + ), + ) + return highlighted + except pygments.util.ClassNotFound: + raise UsageError( + "PYTEST_THEME environment variable had an invalid value: '{}'. " + "Only valid pygment styles are allowed.".format( + os.getenv("PYTEST_THEME") + ) + ) + except pygments.util.OptionError: + raise UsageError( + "PYTEST_THEME_MODE environment variable had an invalid value: '{}'. " + "The only allowed values are 'dark' and 'light'.".format( + os.getenv("PYTEST_THEME_MODE") + ) + ) diff --git a/testing/test_terminal.py b/testing/test_terminal.py index 3e3502b5c..c8834b545 100644 --- a/testing/test_terminal.py +++ b/testing/test_terminal.py @@ -2408,6 +2408,60 @@ class TestCodeHighlight: ) ) + def test_code_highlight_custom_theme( + self, pytester: Pytester, color_mapping, monkeypatch: MonkeyPatch + ) -> None: + pytester.makepyfile( + """ + def test_foo(): + assert 1 == 10 + """ + ) + monkeypatch.setenv("PYTEST_THEME", "solarized-dark") + monkeypatch.setenv("PYTEST_THEME_MODE", "dark") + result = pytester.runpytest("--color=yes") + result.stdout.fnmatch_lines( + color_mapping.format_for_fnmatch( + [ + " {kw}def{hl-reset} {function}test_foo{hl-reset}():", + "> {kw}assert{hl-reset} {number}1{hl-reset} == {number}10{hl-reset}", + "{bold}{red}E assert 1 == 10{reset}", + ] + ) + ) + + def test_code_highlight_invalid_theme( + self, pytester: Pytester, color_mapping, monkeypatch: MonkeyPatch + ) -> None: + pytester.makepyfile( + """ + def test_foo(): + assert 1 == 10 + """ + ) + monkeypatch.setenv("PYTEST_THEME", "invalid") + result = pytester.runpytest_subprocess("--color=yes") + result.stderr.fnmatch_lines( + "ERROR: PYTEST_THEME environment variable had an invalid value: 'invalid'. " + "Only valid pygment styles are allowed." + ) + + def test_code_highlight_invalid_theme_mode( + self, pytester: Pytester, color_mapping, monkeypatch: MonkeyPatch + ) -> None: + pytester.makepyfile( + """ + def test_foo(): + assert 1 == 10 + """ + ) + monkeypatch.setenv("PYTEST_THEME_MODE", "invalid") + result = pytester.runpytest_subprocess("--color=yes") + result.stderr.fnmatch_lines( + "ERROR: PYTEST_THEME_MODE environment variable had an invalid value: 'invalid'. " + "The only allowed values are 'dark' and 'light'." + ) + def test_raw_skip_reason_skipped() -> None: report = SimpleNamespace()