From 655a5f6890b8ba09377f8a495a39e0a39290b444 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 15:38:47 +0300 Subject: [PATCH 01/14] Add support for the `NO_COLOR` environment var --- src/darker/config.py | 9 +- src/darker/tests/test_highlighting.py | 194 +++++++++++++++++--------- 2 files changed, 136 insertions(+), 67 deletions(-) diff --git a/src/darker/config.py b/src/darker/config.py index 39cb54ee0..e8bacc0a1 100644 --- a/src/darker/config.py +++ b/src/darker/config.py @@ -106,11 +106,12 @@ def override_color_with_environment(pyproject_config: DarkerConfig) -> DarkerCon :return: The modified configuration """ - py_colors = os.getenv("PY_COLORS") - if py_colors not in {"0", "1"}: - return pyproject_config config = pyproject_config.copy() - config["color"] = py_colors == "1" + py_colors = os.getenv("PY_COLORS") + if py_colors in {"0", "1"}: + config["color"] = py_colors == "1" + elif os.getenv("NO_COLOR"): + config["color"] = False return config diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index 52b73dc69..ff7ed58da 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -1,8 +1,11 @@ """Unit tests for :mod:`darker.highlighting`""" +# pylint: disable=too-many-arguments + import os import sys from pathlib import Path +from typing import Dict, List from unittest.mock import Mock, patch import pytest @@ -12,15 +15,24 @@ from darker.highlighting import colorize, lexers, should_use_color -@pytest.mark.parametrize("config", ["", "color = false", "color = true"]) -@pytest.mark.parametrize("environ", [{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) +@pytest.mark.parametrize("pyproject", ["", "color = false", "color = true"]) +@pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) +@pytest.mark.parametrize("env_py_colors", [{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) @pytest.mark.parametrize("cmdline", [[], ["--no-color"], ["--color"]]) @pytest.mark.parametrize("tty", [False, True]) -def test_should_use_color_no_pygments(tmp_path, config, environ, cmdline, tty): +def test_should_use_color_no_pygments( + tmp_path: Path, + pyproject: str, + env_no_color: Dict[str, str], + env_py_colors: Dict[str, str], + cmdline: List[str], + tty: bool, +) -> None: """Color output is never used if `pygments` is not installed""" - with (tmp_path / "pyproject.toml").open("w") as pyproject: - print(f"[tool.darker]\n{config}\n", file=pyproject) + with (tmp_path / "pyproject.toml").open("w") as pyproject_toml: + print(f"[tool.darker]\n{pyproject}\n", file=pyproject_toml) argv = cmdline + [str(tmp_path / "dummy.py")] + environ = {**env_no_color, **env_py_colors} with patch.dict(os.environ, environ, clear=True): _, config, _ = parse_command_line(argv) mods = sys.modules.copy() @@ -36,72 +48,128 @@ def test_should_use_color_no_pygments(tmp_path, config, environ, cmdline, tty): @pytest.mark.parametrize( - "params, expect", + "params", [ # for the expected value, for readability, strings are used instead of booleans - (" ", ""), - (" tty", "should_use_color() == True"), - (" --no-color ", ""), - (" --no-color tty", ""), - (" --color ", "should_use_color() == True"), - (" --color tty", "should_use_color() == True"), - (" PY_COLORS=0 ", ""), - (" PY_COLORS=0 tty", ""), - (" PY_COLORS=0 --no-color ", ""), - (" PY_COLORS=0 --no-color tty", ""), - (" PY_COLORS=0 --color ", "should_use_color() == True"), - (" PY_COLORS=0 --color tty", "should_use_color() == True"), - (" PY_COLORS=1 ", "should_use_color() == True"), - (" PY_COLORS=1 tty", "should_use_color() == True"), - (" PY_COLORS=1 --no-color ", ""), - (" PY_COLORS=1 --no-color tty", ""), - (" PY_COLORS=1 --color ", "should_use_color() == True"), - (" PY_COLORS=1 --color tty", "should_use_color() == True"), - ("color=false ", ""), - ("color=false tty", ""), - ("color=false --no-color ", ""), - ("color=false --no-color tty", ""), - ("color=false --color ", "should_use_color() == True"), - ("color=false --color tty", "should_use_color() == True"), - ("color=false PY_COLORS=0 ", ""), - ("color=false PY_COLORS=0 tty", ""), - ("color=false PY_COLORS=0 --no-color ", ""), - ("color=false PY_COLORS=0 --no-color tty", ""), - ("color=false PY_COLORS=0 --color ", "should_use_color() == True"), - ("color=false PY_COLORS=0 --color tty", "should_use_color() == True"), - ("color=false PY_COLORS=1 ", "should_use_color() == True"), - ("color=false PY_COLORS=1 tty", "should_use_color() == True"), - ("color=false PY_COLORS=1 --no-color ", ""), - ("color=false PY_COLORS=1 --no-color tty", ""), - ("color=false PY_COLORS=1 --color ", "should_use_color() == True"), - ("color=false PY_COLORS=1 --color tty", "should_use_color() == True"), - ("color=true ", "should_use_color() == True"), - ("color=true tty", "should_use_color() == True"), - ("color=true --no-color ", ""), - ("color=true --no-color tty", ""), - ("color=true --color ", "should_use_color() == True"), - ("color=true --color tty", "should_use_color() == True"), - ("color=true PY_COLORS=0 ", ""), - ("color=true PY_COLORS=0 tty", ""), - ("color=true PY_COLORS=0 --no-color ", ""), - ("color=true PY_COLORS=0 --no-color tty", ""), - ("color=true PY_COLORS=0 --color ", "should_use_color() == True"), - ("color=true PY_COLORS=0 --color tty", "should_use_color() == True"), - ("color=true PY_COLORS=1 ", "should_use_color() == True"), - ("color=true PY_COLORS=1 tty", "should_use_color() == True"), - ("color=true PY_COLORS=1 --no-color ", ""), - ("color=true PY_COLORS=1 --no-color tty", ""), - ("color=true PY_COLORS=1 --color ", "should_use_color() == True"), - ("color=true PY_COLORS=1 --color tty", "should_use_color() == True"), + " ", + " tty should_use_color == True", + " --no-color ", + " --no-color tty ", + " --color should_use_color == True", + " --color tty should_use_color == True", + " PY_COLORS=0 ", + " PY_COLORS=0 tty ", + " PY_COLORS=0 --no-color ", + " PY_COLORS=0 --no-color tty ", + " PY_COLORS=0 --color should_use_color == True", + " PY_COLORS=0 --color tty should_use_color == True", + " PY_COLORS=1 should_use_color == True", + " PY_COLORS=1 tty should_use_color == True", + " PY_COLORS=1 --no-color ", + " PY_COLORS=1 --no-color tty ", + " PY_COLORS=1 --color should_use_color == True", + " PY_COLORS=1 --color tty should_use_color == True", + "color=false ", + "color=false tty ", + "color=false --no-color ", + "color=false --no-color tty ", + "color=false --color should_use_color == True", + "color=false --color tty should_use_color == True", + "color=false PY_COLORS=0 ", + "color=false PY_COLORS=0 tty ", + "color=false PY_COLORS=0 --no-color ", + "color=false PY_COLORS=0 --no-color tty ", + "color=false PY_COLORS=0 --color should_use_color == True", + "color=false PY_COLORS=0 --color tty should_use_color == True", + "color=false PY_COLORS=1 should_use_color == True", + "color=false PY_COLORS=1 tty should_use_color == True", + "color=false PY_COLORS=1 --no-color ", + "color=false PY_COLORS=1 --no-color tty ", + "color=false PY_COLORS=1 --color should_use_color == True", + "color=false PY_COLORS=1 --color tty should_use_color == True", + "color=true should_use_color == True", + "color=true tty should_use_color == True", + "color=true --no-color ", + "color=true --no-color tty ", + "color=true --color should_use_color == True", + "color=true --color tty should_use_color == True", + "color=true PY_COLORS=0 ", + "color=true PY_COLORS=0 tty ", + "color=true PY_COLORS=0 --no-color ", + "color=true PY_COLORS=0 --no-color tty ", + "color=true PY_COLORS=0 --color should_use_color == True", + "color=true PY_COLORS=0 --color tty should_use_color == True", + "color=true PY_COLORS=1 should_use_color == True", + "color=true PY_COLORS=1 tty should_use_color == True", + "color=true PY_COLORS=1 --no-color ", + "color=true PY_COLORS=1 --no-color tty ", + "color=true PY_COLORS=1 --color should_use_color == True", + "color=true PY_COLORS=1 --color tty should_use_color == True", + " NO_COLOR=foo ", + " NO_COLOR=foo tty ", + " NO_COLOR=foo --no-color ", + " NO_COLOR=foo --no-color tty ", + " NO_COLOR=foo --color should_use_color == True", + " NO_COLOR=foo --color tty should_use_color == True", + " NO_COLOR=foo PY_COLORS=0 ", + " NO_COLOR=foo PY_COLORS=0 tty ", + " NO_COLOR=foo PY_COLORS=0 --no-color ", + " NO_COLOR=foo PY_COLORS=0 --no-color tty ", + " NO_COLOR=foo PY_COLORS=0 --color should_use_color == True", + " NO_COLOR=foo PY_COLORS=0 --color tty should_use_color == True", + " NO_COLOR=foo PY_COLORS=1 should_use_color == True", + " NO_COLOR=foo PY_COLORS=1 tty should_use_color == True", + " NO_COLOR=foo PY_COLORS=1 --no-color ", + " NO_COLOR=foo PY_COLORS=1 --no-color tty ", + " NO_COLOR=foo PY_COLORS=1 --color should_use_color == True", + " NO_COLOR=foo PY_COLORS=1 --color tty should_use_color == True", + "color=false NO_COLOR=foo ", + "color=false NO_COLOR=foo tty ", + "color=false NO_COLOR=foo --no-color ", + "color=false NO_COLOR=foo --no-color tty ", + "color=false NO_COLOR=foo --color should_use_color == True", + "color=false NO_COLOR=foo --color tty should_use_color == True", + "color=false NO_COLOR=foo PY_COLORS=0 ", + "color=false NO_COLOR=foo PY_COLORS=0 tty ", + "color=false NO_COLOR=foo PY_COLORS=0 --no-color ", + "color=false NO_COLOR=foo PY_COLORS=0 --no-color tty ", + "color=false NO_COLOR=foo PY_COLORS=0 --color should_use_color == True", + "color=false NO_COLOR=foo PY_COLORS=0 --color tty should_use_color == True", + "color=false NO_COLOR=foo PY_COLORS=1 should_use_color == True", + "color=false NO_COLOR=foo PY_COLORS=1 tty should_use_color == True", + "color=false NO_COLOR=foo PY_COLORS=1 --no-color ", + "color=false NO_COLOR=foo PY_COLORS=1 --no-color tty ", + "color=false NO_COLOR=foo PY_COLORS=1 --color should_use_color == True", + "color=false NO_COLOR=foo PY_COLORS=1 --color tty should_use_color == True", + "color=true NO_COLOR=foo ", + "color=true NO_COLOR=foo tty ", + "color=true NO_COLOR=foo --no-color ", + "color=true NO_COLOR=foo --no-color tty ", + "color=true NO_COLOR=foo --color should_use_color == True", + "color=true NO_COLOR=foo --color tty should_use_color == True", + "color=true NO_COLOR=foo PY_COLORS=0 ", + "color=true NO_COLOR=foo PY_COLORS=0 tty ", + "color=true NO_COLOR=foo PY_COLORS=0 --no-color ", + "color=true NO_COLOR=foo PY_COLORS=0 --no-color tty ", + "color=true NO_COLOR=foo PY_COLORS=0 --color should_use_color == True", + "color=true NO_COLOR=foo PY_COLORS=0 --color tty should_use_color == True", + "color=true NO_COLOR=foo PY_COLORS=1 should_use_color == True", + "color=true NO_COLOR=foo PY_COLORS=1 tty should_use_color == True", + "color=true NO_COLOR=foo PY_COLORS=1 --no-color ", + "color=true NO_COLOR=foo PY_COLORS=1 --no-color tty ", + "color=true NO_COLOR=foo PY_COLORS=1 --color should_use_color == True", + "color=true NO_COLOR=foo PY_COLORS=1 --color tty should_use_color == True", ], ) -def test_should_use_color_pygments(tmp_path: Path, params: str, expect: str) -> None: +def test_should_use_color_pygments(tmp_path: Path, params: str) -> None: """Color output is used only if correct configuration options are in place""" with (tmp_path / "pyproject.toml").open("w") as pyproject: print("[tool.darker]", file=pyproject) if params.startswith("color="): print(params.split()[0], file=pyproject) env = {} + if " NO_COLOR=foo " in params: + env["NO_COLOR"] = "foo" if " PY_COLORS=0 " in params: env["PY_COLORS"] = "0" if " PY_COLORS=1 " in params: @@ -113,11 +181,11 @@ def test_should_use_color_pygments(tmp_path: Path, params: str, expect: str) -> argv.insert(0, "--no-color") with patch.dict(os.environ, env, clear=True): _, config, _ = parse_command_line(argv) - with patch("sys.stdout.isatty", Mock(return_value=params.endswith(" tty"))): + with patch("sys.stdout.isatty", Mock(return_value=" tty " in params)): result = should_use_color(config["color"]) - assert result == (expect == "should_use_color() == True") + assert result == params.endswith(" should_use_color == True") def test_colorize_with_no_color(): From 5267dbe84882cfccf78093f0770b59e57b9f1ff5 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 15:38:47 +0300 Subject: [PATCH 02/14] Update change log --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 58a7ca2e7..42669decf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,7 +11,7 @@ Added - Sort imports only if the range of modified lines overlaps with changes resulting from sorting the imports. - Allow force enabling/disabling of syntax highlighting using the ``color`` option in - ``pyproject.toml``, the ``PY_COLORS`` environment variable, and the + ``pyproject.toml``, the ``PY_COLORS`` and ``NO_COLOR`` environment variables, and the ``--color``/``--no-color`` command line options. - Syntax highlighting is now enabled by default in the GitHub Action. From a95de6d1a5f7d99dfa2f9e25604db0e43a31f21c Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 15:38:47 +0300 Subject: [PATCH 03/14] Refactor tests --- src/darker/tests/test_highlighting.py | 198 ++++++++++---------------- 1 file changed, 79 insertions(+), 119 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index ff7ed58da..c9c4092bc 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -47,122 +47,87 @@ def test_should_use_color_no_pygments( assert result is False +@pytest.mark.parametrize("pyproject", ["", "color = false", "color = true"]) +@pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) +@pytest.mark.parametrize("env_py_colors", [{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) +@pytest.mark.kwparametrize( + dict(cmdline="--no-color", expect=False), + dict(cmdline="--color", expect=True), +) +@pytest.mark.parametrize("tty", [False, True]) +def test_should_use_color_pygments_and_command_line_argument( + tmp_path: Path, + pyproject: str, + env_no_color: Dict[str, str], + env_py_colors: Dict[str, str], + cmdline: str, + expect: bool, + tty: bool, +) -> None: + """--color / --no-color determines highlighting if `pygments` is installed""" + with (tmp_path / "pyproject.toml").open("w") as pyproject_toml: + print(f"[tool.darker]\n{pyproject}\n", file=pyproject_toml) + argv = [cmdline, str(tmp_path / "dummy.py")] + environ = {**env_no_color, **env_py_colors} + with patch.dict(os.environ, environ, clear=True): + _, config, _ = parse_command_line(argv) + with patch("sys.stdout.isatty", Mock(return_value=tty)): + + result = should_use_color(config["color"]) + + assert result == expect + + @pytest.mark.parametrize( - "params", + "params, expect", [ # for the expected value, for readability, strings are used instead of booleans - " ", - " tty should_use_color == True", - " --no-color ", - " --no-color tty ", - " --color should_use_color == True", - " --color tty should_use_color == True", - " PY_COLORS=0 ", - " PY_COLORS=0 tty ", - " PY_COLORS=0 --no-color ", - " PY_COLORS=0 --no-color tty ", - " PY_COLORS=0 --color should_use_color == True", - " PY_COLORS=0 --color tty should_use_color == True", - " PY_COLORS=1 should_use_color == True", - " PY_COLORS=1 tty should_use_color == True", - " PY_COLORS=1 --no-color ", - " PY_COLORS=1 --no-color tty ", - " PY_COLORS=1 --color should_use_color == True", - " PY_COLORS=1 --color tty should_use_color == True", - "color=false ", - "color=false tty ", - "color=false --no-color ", - "color=false --no-color tty ", - "color=false --color should_use_color == True", - "color=false --color tty should_use_color == True", - "color=false PY_COLORS=0 ", - "color=false PY_COLORS=0 tty ", - "color=false PY_COLORS=0 --no-color ", - "color=false PY_COLORS=0 --no-color tty ", - "color=false PY_COLORS=0 --color should_use_color == True", - "color=false PY_COLORS=0 --color tty should_use_color == True", - "color=false PY_COLORS=1 should_use_color == True", - "color=false PY_COLORS=1 tty should_use_color == True", - "color=false PY_COLORS=1 --no-color ", - "color=false PY_COLORS=1 --no-color tty ", - "color=false PY_COLORS=1 --color should_use_color == True", - "color=false PY_COLORS=1 --color tty should_use_color == True", - "color=true should_use_color == True", - "color=true tty should_use_color == True", - "color=true --no-color ", - "color=true --no-color tty ", - "color=true --color should_use_color == True", - "color=true --color tty should_use_color == True", - "color=true PY_COLORS=0 ", - "color=true PY_COLORS=0 tty ", - "color=true PY_COLORS=0 --no-color ", - "color=true PY_COLORS=0 --no-color tty ", - "color=true PY_COLORS=0 --color should_use_color == True", - "color=true PY_COLORS=0 --color tty should_use_color == True", - "color=true PY_COLORS=1 should_use_color == True", - "color=true PY_COLORS=1 tty should_use_color == True", - "color=true PY_COLORS=1 --no-color ", - "color=true PY_COLORS=1 --no-color tty ", - "color=true PY_COLORS=1 --color should_use_color == True", - "color=true PY_COLORS=1 --color tty should_use_color == True", - " NO_COLOR=foo ", - " NO_COLOR=foo tty ", - " NO_COLOR=foo --no-color ", - " NO_COLOR=foo --no-color tty ", - " NO_COLOR=foo --color should_use_color == True", - " NO_COLOR=foo --color tty should_use_color == True", - " NO_COLOR=foo PY_COLORS=0 ", - " NO_COLOR=foo PY_COLORS=0 tty ", - " NO_COLOR=foo PY_COLORS=0 --no-color ", - " NO_COLOR=foo PY_COLORS=0 --no-color tty ", - " NO_COLOR=foo PY_COLORS=0 --color should_use_color == True", - " NO_COLOR=foo PY_COLORS=0 --color tty should_use_color == True", - " NO_COLOR=foo PY_COLORS=1 should_use_color == True", - " NO_COLOR=foo PY_COLORS=1 tty should_use_color == True", - " NO_COLOR=foo PY_COLORS=1 --no-color ", - " NO_COLOR=foo PY_COLORS=1 --no-color tty ", - " NO_COLOR=foo PY_COLORS=1 --color should_use_color == True", - " NO_COLOR=foo PY_COLORS=1 --color tty should_use_color == True", - "color=false NO_COLOR=foo ", - "color=false NO_COLOR=foo tty ", - "color=false NO_COLOR=foo --no-color ", - "color=false NO_COLOR=foo --no-color tty ", - "color=false NO_COLOR=foo --color should_use_color == True", - "color=false NO_COLOR=foo --color tty should_use_color == True", - "color=false NO_COLOR=foo PY_COLORS=0 ", - "color=false NO_COLOR=foo PY_COLORS=0 tty ", - "color=false NO_COLOR=foo PY_COLORS=0 --no-color ", - "color=false NO_COLOR=foo PY_COLORS=0 --no-color tty ", - "color=false NO_COLOR=foo PY_COLORS=0 --color should_use_color == True", - "color=false NO_COLOR=foo PY_COLORS=0 --color tty should_use_color == True", - "color=false NO_COLOR=foo PY_COLORS=1 should_use_color == True", - "color=false NO_COLOR=foo PY_COLORS=1 tty should_use_color == True", - "color=false NO_COLOR=foo PY_COLORS=1 --no-color ", - "color=false NO_COLOR=foo PY_COLORS=1 --no-color tty ", - "color=false NO_COLOR=foo PY_COLORS=1 --color should_use_color == True", - "color=false NO_COLOR=foo PY_COLORS=1 --color tty should_use_color == True", - "color=true NO_COLOR=foo ", - "color=true NO_COLOR=foo tty ", - "color=true NO_COLOR=foo --no-color ", - "color=true NO_COLOR=foo --no-color tty ", - "color=true NO_COLOR=foo --color should_use_color == True", - "color=true NO_COLOR=foo --color tty should_use_color == True", - "color=true NO_COLOR=foo PY_COLORS=0 ", - "color=true NO_COLOR=foo PY_COLORS=0 tty ", - "color=true NO_COLOR=foo PY_COLORS=0 --no-color ", - "color=true NO_COLOR=foo PY_COLORS=0 --no-color tty ", - "color=true NO_COLOR=foo PY_COLORS=0 --color should_use_color == True", - "color=true NO_COLOR=foo PY_COLORS=0 --color tty should_use_color == True", - "color=true NO_COLOR=foo PY_COLORS=1 should_use_color == True", - "color=true NO_COLOR=foo PY_COLORS=1 tty should_use_color == True", - "color=true NO_COLOR=foo PY_COLORS=1 --no-color ", - "color=true NO_COLOR=foo PY_COLORS=1 --no-color tty ", - "color=true NO_COLOR=foo PY_COLORS=1 --color should_use_color == True", - "color=true NO_COLOR=foo PY_COLORS=1 --color tty should_use_color == True", + (" ", ""), + (" tty", "should_use_color() == True"), + (" PY_COLORS=0 ", ""), + (" PY_COLORS=0 tty", ""), + (" PY_COLORS=1 ", "should_use_color() == True"), + (" PY_COLORS=1 tty", "should_use_color() == True"), + ("color=false ", ""), + ("color=false tty", ""), + ("color=false PY_COLORS=0 ", ""), + ("color=false PY_COLORS=0 tty", ""), + ("color=false PY_COLORS=1 ", "should_use_color() == True"), + ("color=false PY_COLORS=1 tty", "should_use_color() == True"), + ("color=true ", "should_use_color() == True"), + ("color=true tty", "should_use_color() == True"), + ("color=true PY_COLORS=0 ", ""), + ("color=true PY_COLORS=0 tty", ""), + ("color=true PY_COLORS=1 ", "should_use_color() == True"), + ("color=true PY_COLORS=1 tty", "should_use_color() == True"), + (" NO_COLOR=foo ", ""), + (" NO_COLOR=foo tty", ""), + (" NO_COLOR=foo PY_COLORS=0 ", ""), + (" NO_COLOR=foo PY_COLORS=0 tty", ""), + (" NO_COLOR=foo PY_COLORS=1 ", "should_use_color() == True"), + (" NO_COLOR=foo PY_COLORS=1 tty", "should_use_color() == True"), + ("color=false NO_COLOR=foo ", ""), + ("color=false NO_COLOR=foo tty", ""), + ("color=false NO_COLOR=foo PY_COLORS=0 ", ""), + ("color=false NO_COLOR=foo PY_COLORS=0 tty", ""), + ("color=false NO_COLOR=foo PY_COLORS=1 ", "should_use_color() == True"), + ("color=false NO_COLOR=foo PY_COLORS=1 tty", "should_use_color() == True"), + ("color=true NO_COLOR=foo ", ""), + ("color=true NO_COLOR=foo tty", ""), + ("color=true NO_COLOR=foo PY_COLORS=0 ", ""), + ("color=true NO_COLOR=foo PY_COLORS=0 tty", ""), + ("color=true NO_COLOR=foo PY_COLORS=1 ", "should_use_color() == True"), + ("color=true NO_COLOR=foo PY_COLORS=1 tty", "should_use_color() == True"), ], ) -def test_should_use_color_pygments(tmp_path: Path, params: str) -> None: - """Color output is used only if correct configuration options are in place""" +def test_should_use_color_pygments(tmp_path: Path, params: str, expect: str) -> None: + """Color output is enabled only if correct configuration options are in place + + These tests are set up so that it appears as if + - ``pygments`` is installed + - there is no ``--color`` or `--no-color`` command line option + + """ with (tmp_path / "pyproject.toml").open("w") as pyproject: print("[tool.darker]", file=pyproject) if params.startswith("color="): @@ -174,18 +139,13 @@ def test_should_use_color_pygments(tmp_path: Path, params: str) -> None: env["PY_COLORS"] = "0" if " PY_COLORS=1 " in params: env["PY_COLORS"] = "1" - argv = [str(tmp_path / "dummy.py")] - if " --color " in params: - argv.insert(0, "--color") - if " --no-color " in params: - argv.insert(0, "--no-color") with patch.dict(os.environ, env, clear=True): - _, config, _ = parse_command_line(argv) - with patch("sys.stdout.isatty", Mock(return_value=" tty " in params)): + _, config, _ = parse_command_line([str(tmp_path / "dummy.py")]) + with patch("sys.stdout.isatty", Mock(return_value=params.endswith(" tty"))): result = should_use_color(config["color"]) - assert result == params.endswith(" should_use_color == True") + assert result == (expect == "should_use_color() == True") def test_colorize_with_no_color(): From 536bc3b7677fb922b002cc491b31037d3012f2a8 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 15:38:47 +0300 Subject: [PATCH 04/14] PY_COLORS=0/1 as a matrix test --- src/darker/tests/test_highlighting.py | 87 +++++++++++++++------------ 1 file changed, 47 insertions(+), 40 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index c9c4092bc..2f8116772 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -78,46 +78,56 @@ def test_should_use_color_pygments_and_command_line_argument( assert result == expect +@pytest.mark.parametrize("pyproject", ["", "color = false", "color = true"]) +@pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) +@pytest.mark.kwparametrize( + dict(env_py_colors={"PY_COLORS": "0"}, expect=False), + dict(env_py_colors={"PY_COLORS": "1"}, expect=True), +) +@pytest.mark.parametrize("tty", [False, True]) +def test_should_use_color_pygments_and_py_colors( + tmp_path: Path, + pyproject: str, + env_no_color: Dict[str, str], + env_py_colors: Dict[str, str], + expect: bool, + tty: bool, +) -> None: + """PY_COLORS determines highlighting when `pygments` installed and no cmdline args + + These tests are set up so that it appears as if + - ``pygments`` is installed + - there is no ``--color`` or `--no-color`` command line option + + """ + with (tmp_path / "pyproject.toml").open("w") as pyproject_toml: + print(f"[tool.darker]\n{pyproject}\n", file=pyproject_toml) + environ = {**env_no_color, **env_py_colors} + with patch.dict(os.environ, environ, clear=True): + _, config, _ = parse_command_line([str(tmp_path / "dummy.py")]) + with patch("sys.stdout.isatty", Mock(return_value=tty)): + + result = should_use_color(config["color"]) + + assert result == expect + + @pytest.mark.parametrize( "params, expect", [ # for the expected value, for readability, strings are used instead of booleans - (" ", ""), - (" tty", "should_use_color() == True"), - (" PY_COLORS=0 ", ""), - (" PY_COLORS=0 tty", ""), - (" PY_COLORS=1 ", "should_use_color() == True"), - (" PY_COLORS=1 tty", "should_use_color() == True"), - ("color=false ", ""), - ("color=false tty", ""), - ("color=false PY_COLORS=0 ", ""), - ("color=false PY_COLORS=0 tty", ""), - ("color=false PY_COLORS=1 ", "should_use_color() == True"), - ("color=false PY_COLORS=1 tty", "should_use_color() == True"), - ("color=true ", "should_use_color() == True"), - ("color=true tty", "should_use_color() == True"), - ("color=true PY_COLORS=0 ", ""), - ("color=true PY_COLORS=0 tty", ""), - ("color=true PY_COLORS=1 ", "should_use_color() == True"), - ("color=true PY_COLORS=1 tty", "should_use_color() == True"), - (" NO_COLOR=foo ", ""), - (" NO_COLOR=foo tty", ""), - (" NO_COLOR=foo PY_COLORS=0 ", ""), - (" NO_COLOR=foo PY_COLORS=0 tty", ""), - (" NO_COLOR=foo PY_COLORS=1 ", "should_use_color() == True"), - (" NO_COLOR=foo PY_COLORS=1 tty", "should_use_color() == True"), - ("color=false NO_COLOR=foo ", ""), - ("color=false NO_COLOR=foo tty", ""), - ("color=false NO_COLOR=foo PY_COLORS=0 ", ""), - ("color=false NO_COLOR=foo PY_COLORS=0 tty", ""), - ("color=false NO_COLOR=foo PY_COLORS=1 ", "should_use_color() == True"), - ("color=false NO_COLOR=foo PY_COLORS=1 tty", "should_use_color() == True"), - ("color=true NO_COLOR=foo ", ""), - ("color=true NO_COLOR=foo tty", ""), - ("color=true NO_COLOR=foo PY_COLORS=0 ", ""), - ("color=true NO_COLOR=foo PY_COLORS=0 tty", ""), - ("color=true NO_COLOR=foo PY_COLORS=1 ", "should_use_color() == True"), - ("color=true NO_COLOR=foo PY_COLORS=1 tty", "should_use_color() == True"), + (" ", ""), + (" tty", "should_use_color() == True"), + ("color=false ", ""), + ("color=false tty", ""), + ("color=true ", "should_use_color() == True"), + ("color=true tty", "should_use_color() == True"), + (" NO_COLOR=foo ", ""), + (" NO_COLOR=foo tty", ""), + ("color=false NO_COLOR=foo ", ""), + ("color=false NO_COLOR=foo tty", ""), + ("color=true NO_COLOR=foo ", ""), + ("color=true NO_COLOR=foo tty", ""), ], ) def test_should_use_color_pygments(tmp_path: Path, params: str, expect: str) -> None: @@ -126,6 +136,7 @@ def test_should_use_color_pygments(tmp_path: Path, params: str, expect: str) -> These tests are set up so that it appears as if - ``pygments`` is installed - there is no ``--color`` or `--no-color`` command line option + - the ``PY_COLORS`` environment variable isn't set to ``0`` or ``1`` """ with (tmp_path / "pyproject.toml").open("w") as pyproject: @@ -135,10 +146,6 @@ def test_should_use_color_pygments(tmp_path: Path, params: str, expect: str) -> env = {} if " NO_COLOR=foo " in params: env["NO_COLOR"] = "foo" - if " PY_COLORS=0 " in params: - env["PY_COLORS"] = "0" - if " PY_COLORS=1 " in params: - env["PY_COLORS"] = "1" with patch.dict(os.environ, env, clear=True): _, config, _ = parse_command_line([str(tmp_path / "dummy.py")]) with patch("sys.stdout.isatty", Mock(return_value=params.endswith(" tty"))): From b891078646dad460438b304cc0dffa662ed4aeca Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 15:56:40 +0300 Subject: [PATCH 05/14] Parametrize `pyproject.toml` in a fixture --- src/darker/tests/test_highlighting.py | 53 ++++++++++++++++++++------- 1 file changed, 39 insertions(+), 14 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index 2f8116772..fdeded406 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -1,36 +1,67 @@ """Unit tests for :mod:`darker.highlighting`""" -# pylint: disable=too-many-arguments +# pylint: disable=too-many-arguments,redefined-outer-name,unused-argument import os import sys from pathlib import Path -from typing import Dict, List +from typing import Dict, Generator, List from unittest.mock import Mock, patch import pytest +from _pytest.fixtures import SubRequest from pygments.token import Token from darker.command_line import parse_command_line from darker.highlighting import colorize, lexers, should_use_color -@pytest.mark.parametrize("pyproject", ["", "color = false", "color = true"]) +@pytest.fixture(params=["", "color = false", "color = true"]) +def pyproject_toml_color( + request: SubRequest, tmp_path: Path +) -> Generator[None, None, None]: + """Parametrized fixture for the ``color =`` option in ``pyproject.toml`` + + Creates three versions of ``pyproject.toml`` in ``tmp_path`` for a test function: + + Without the ``color =`` option:: + + [tool.darker] + + With color turned off:: + + [tool.darker] + color = false + + With color turned on:: + + [tool.darker] + color = true + + :param request: The Pytest ``request`` object + :param tmp_path: A temporary directory created by Pytest + :yield: The ``color =`` option line in ``pyproject.toml``, or an empty string + + """ + with (tmp_path / "pyproject.toml").open("w") as pyproject_toml: + print(f"[tool.darker]\n{request.param}\n", file=pyproject_toml) + + yield request.param + + @pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) @pytest.mark.parametrize("env_py_colors", [{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) @pytest.mark.parametrize("cmdline", [[], ["--no-color"], ["--color"]]) @pytest.mark.parametrize("tty", [False, True]) def test_should_use_color_no_pygments( tmp_path: Path, - pyproject: str, + pyproject_toml_color: str, env_no_color: Dict[str, str], env_py_colors: Dict[str, str], cmdline: List[str], tty: bool, ) -> None: """Color output is never used if `pygments` is not installed""" - with (tmp_path / "pyproject.toml").open("w") as pyproject_toml: - print(f"[tool.darker]\n{pyproject}\n", file=pyproject_toml) argv = cmdline + [str(tmp_path / "dummy.py")] environ = {**env_no_color, **env_py_colors} with patch.dict(os.environ, environ, clear=True): @@ -47,7 +78,6 @@ def test_should_use_color_no_pygments( assert result is False -@pytest.mark.parametrize("pyproject", ["", "color = false", "color = true"]) @pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) @pytest.mark.parametrize("env_py_colors", [{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) @pytest.mark.kwparametrize( @@ -57,7 +87,7 @@ def test_should_use_color_no_pygments( @pytest.mark.parametrize("tty", [False, True]) def test_should_use_color_pygments_and_command_line_argument( tmp_path: Path, - pyproject: str, + pyproject_toml_color: str, env_no_color: Dict[str, str], env_py_colors: Dict[str, str], cmdline: str, @@ -65,8 +95,6 @@ def test_should_use_color_pygments_and_command_line_argument( tty: bool, ) -> None: """--color / --no-color determines highlighting if `pygments` is installed""" - with (tmp_path / "pyproject.toml").open("w") as pyproject_toml: - print(f"[tool.darker]\n{pyproject}\n", file=pyproject_toml) argv = [cmdline, str(tmp_path / "dummy.py")] environ = {**env_no_color, **env_py_colors} with patch.dict(os.environ, environ, clear=True): @@ -78,7 +106,6 @@ def test_should_use_color_pygments_and_command_line_argument( assert result == expect -@pytest.mark.parametrize("pyproject", ["", "color = false", "color = true"]) @pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) @pytest.mark.kwparametrize( dict(env_py_colors={"PY_COLORS": "0"}, expect=False), @@ -87,7 +114,7 @@ def test_should_use_color_pygments_and_command_line_argument( @pytest.mark.parametrize("tty", [False, True]) def test_should_use_color_pygments_and_py_colors( tmp_path: Path, - pyproject: str, + pyproject_toml_color: str, env_no_color: Dict[str, str], env_py_colors: Dict[str, str], expect: bool, @@ -100,8 +127,6 @@ def test_should_use_color_pygments_and_py_colors( - there is no ``--color`` or `--no-color`` command line option """ - with (tmp_path / "pyproject.toml").open("w") as pyproject_toml: - print(f"[tool.darker]\n{pyproject}\n", file=pyproject_toml) environ = {**env_no_color, **env_py_colors} with patch.dict(os.environ, environ, clear=True): _, config, _ = parse_command_line([str(tmp_path / "dummy.py")]) From 5c5355214756d40ef1d5dcde7c53f9689e934d10 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 16:03:41 +0300 Subject: [PATCH 06/14] Parametrize `sys.stdout.isatty` in a fixture --- src/darker/tests/test_highlighting.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index fdeded406..160486e39 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -49,10 +49,24 @@ def pyproject_toml_color( yield request.param +@pytest.fixture(params=[False, True]) +def tty(request: SubRequest) -> Generator[None, None, None]: + """Parametrized fixture for patching `sys.stdout.isatty` + + Patches `sys.stdout.isatty` to return either `False` or `True`. + + :param request: The Pytest ``request`` object + :yield: The patched `False` or `True` return value for `sys.stdout.isatty` + + """ + with patch("sys.stdout.isatty", Mock(return_value=request.param)): + + yield request.param + + @pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) @pytest.mark.parametrize("env_py_colors", [{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) @pytest.mark.parametrize("cmdline", [[], ["--no-color"], ["--color"]]) -@pytest.mark.parametrize("tty", [False, True]) def test_should_use_color_no_pygments( tmp_path: Path, pyproject_toml_color: str, @@ -70,8 +84,7 @@ def test_should_use_color_no_pygments( del mods["darker.highlighting"] # cause an ImportError for `import pygments`: mods["pygments"] = None # type: ignore[assignment] - isatty = Mock(return_value=tty) - with patch.dict(sys.modules, mods, clear=True), patch("sys.stdout.isatty", isatty): + with patch.dict(sys.modules, mods, clear=True): result = should_use_color(config["color"]) @@ -84,7 +97,6 @@ def test_should_use_color_no_pygments( dict(cmdline="--no-color", expect=False), dict(cmdline="--color", expect=True), ) -@pytest.mark.parametrize("tty", [False, True]) def test_should_use_color_pygments_and_command_line_argument( tmp_path: Path, pyproject_toml_color: str, @@ -99,9 +111,8 @@ def test_should_use_color_pygments_and_command_line_argument( environ = {**env_no_color, **env_py_colors} with patch.dict(os.environ, environ, clear=True): _, config, _ = parse_command_line(argv) - with patch("sys.stdout.isatty", Mock(return_value=tty)): - result = should_use_color(config["color"]) + result = should_use_color(config["color"]) assert result == expect @@ -111,7 +122,6 @@ def test_should_use_color_pygments_and_command_line_argument( dict(env_py_colors={"PY_COLORS": "0"}, expect=False), dict(env_py_colors={"PY_COLORS": "1"}, expect=True), ) -@pytest.mark.parametrize("tty", [False, True]) def test_should_use_color_pygments_and_py_colors( tmp_path: Path, pyproject_toml_color: str, @@ -130,9 +140,8 @@ def test_should_use_color_pygments_and_py_colors( environ = {**env_no_color, **env_py_colors} with patch.dict(os.environ, environ, clear=True): _, config, _ = parse_command_line([str(tmp_path / "dummy.py")]) - with patch("sys.stdout.isatty", Mock(return_value=tty)): - result = should_use_color(config["color"]) + result = should_use_color(config["color"]) assert result == expect From 65ce1e3d9ffd777b389f3852492c487ac689181f Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 16:21:37 +0300 Subject: [PATCH 07/14] Parametrize environment variables in fixtures --- src/darker/tests/test_highlighting.py | 52 ++++++++++++++++++--------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index 160486e39..4a6230bac 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -64,8 +64,36 @@ def tty(request: SubRequest) -> Generator[None, None, None]: yield request.param -@pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) -@pytest.mark.parametrize("env_py_colors", [{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) +@pytest.fixture(params=[{}, {"NO_COLOR": "foo"}]) +def env_no_color(request: SubRequest) -> Generator[None, None, None]: + """Parametrized fixture for patching ``NO_COLOR`` + + Patches the environment with or without the ``NO_COLOR`` environment variable + + :param request: The Pytest ``request`` object + :yield: The patched items in the environment + + """ + with patch.dict(os.environ, request.param): + + yield request.param + + +@pytest.fixture(params=[{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) +def env_py_colors(request: SubRequest) -> Generator[None, None, None]: + """Parametrized fixture for patching ``PY_COLORS`` + + Patches the environment with or without the ``PY_COLORS`` environment variable + + :param request: The Pytest ``request`` object + :yield: The patched items in the environment + + """ + with patch.dict(os.environ, request.param): + + yield request.param + + @pytest.mark.parametrize("cmdline", [[], ["--no-color"], ["--color"]]) def test_should_use_color_no_pygments( tmp_path: Path, @@ -77,9 +105,7 @@ def test_should_use_color_no_pygments( ) -> None: """Color output is never used if `pygments` is not installed""" argv = cmdline + [str(tmp_path / "dummy.py")] - environ = {**env_no_color, **env_py_colors} - with patch.dict(os.environ, environ, clear=True): - _, config, _ = parse_command_line(argv) + _, config, _ = parse_command_line(argv) mods = sys.modules.copy() del mods["darker.highlighting"] # cause an ImportError for `import pygments`: @@ -91,8 +117,6 @@ def test_should_use_color_no_pygments( assert result is False -@pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) -@pytest.mark.parametrize("env_py_colors", [{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) @pytest.mark.kwparametrize( dict(cmdline="--no-color", expect=False), dict(cmdline="--color", expect=True), @@ -108,25 +132,22 @@ def test_should_use_color_pygments_and_command_line_argument( ) -> None: """--color / --no-color determines highlighting if `pygments` is installed""" argv = [cmdline, str(tmp_path / "dummy.py")] - environ = {**env_no_color, **env_py_colors} - with patch.dict(os.environ, environ, clear=True): - _, config, _ = parse_command_line(argv) + _, config, _ = parse_command_line(argv) result = should_use_color(config["color"]) assert result == expect -@pytest.mark.parametrize("env_no_color", [{}, {"NO_COLOR": "foo"}]) @pytest.mark.kwparametrize( - dict(env_py_colors={"PY_COLORS": "0"}, expect=False), - dict(env_py_colors={"PY_COLORS": "1"}, expect=True), + dict(env_py_colors_={"PY_COLORS": "0"}, expect=False), + dict(env_py_colors_={"PY_COLORS": "1"}, expect=True), ) def test_should_use_color_pygments_and_py_colors( tmp_path: Path, pyproject_toml_color: str, env_no_color: Dict[str, str], - env_py_colors: Dict[str, str], + env_py_colors_: Dict[str, str], expect: bool, tty: bool, ) -> None: @@ -137,8 +158,7 @@ def test_should_use_color_pygments_and_py_colors( - there is no ``--color`` or `--no-color`` command line option """ - environ = {**env_no_color, **env_py_colors} - with patch.dict(os.environ, environ, clear=True): + with patch.dict(os.environ, env_py_colors_, clear=True): _, config, _ = parse_command_line([str(tmp_path / "dummy.py")]) result = should_use_color(config["color"]) From edb3c896ac5c34835d5b0216ef86fdc0ac488da7 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:18:01 +0300 Subject: [PATCH 08/14] Parametrize command line in a fixture Also uninstall Pygments and sanitize environment variables using fixtures. --- src/darker/tests/test_highlighting.py | 96 +++++++++++++++++++-------- 1 file changed, 68 insertions(+), 28 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index 4a6230bac..deaaf3b0d 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -16,6 +16,22 @@ from darker.highlighting import colorize, lexers, should_use_color +@pytest.fixture(scope="function", autouse=True) +def clean_environ(): + """Fixture for clearing unwanted environment varables + + The ``NO_COLOR`` and ``PY_COLORS`` environment variables are tested in this module, + so we need to ensure they aren't already set. + + """ + environ = os.environ.copy() + environ.pop("PY_COLORS", None) + environ.pop("NO_COLOR", None) + with patch.dict("os.environ", environ, clear=True): + + yield + + @pytest.fixture(params=["", "color = false", "color = true"]) def pyproject_toml_color( request: SubRequest, tmp_path: Path @@ -94,62 +110,84 @@ def env_py_colors(request: SubRequest) -> Generator[None, None, None]: yield request.param -@pytest.mark.parametrize("cmdline", [[], ["--no-color"], ["--color"]]) +@pytest.fixture +def uninstall_pygments() -> Generator[None, None, None]: + """Fixture for uninstalling ``pygments`` temporarily""" + mods = sys.modules.copy() + del mods["darker.highlighting"] + # cause an ImportError for `import pygments`: + mods["pygments"] = None # type: ignore[assignment] + with patch.dict(sys.modules, mods, clear=True): + + yield + + +@pytest.fixture(params=[[], ["--no-color"], ["--color"]]) +def color_argv(request: SubRequest, tmp_path: Path) -> Generator[List[str], None, None]: + """Parametrized fixture for the ``--color`` / ``--no-color`` arguments + + Yields command lines with no color argument, with the ``--color`` argument, and with + the ``--no--color`` argument, appended with a dummy path to a Python file. + + :param request: The Pytest ``request`` object + :param tmp_path: A temporary directory created by Pytest + :yield: The list of arguments for the Darker command line + + """ + yield request.param + [str(tmp_path / "dummy.py")] + + def test_should_use_color_no_pygments( - tmp_path: Path, + uninstall_pygments: None, pyproject_toml_color: str, env_no_color: Dict[str, str], env_py_colors: Dict[str, str], - cmdline: List[str], + color_argv: List[str], tty: bool, ) -> None: """Color output is never used if `pygments` is not installed""" - argv = cmdline + [str(tmp_path / "dummy.py")] - _, config, _ = parse_command_line(argv) - mods = sys.modules.copy() - del mods["darker.highlighting"] - # cause an ImportError for `import pygments`: - mods["pygments"] = None # type: ignore[assignment] - with patch.dict(sys.modules, mods, clear=True): + _, config, _ = parse_command_line(color_argv) - result = should_use_color(config["color"]) + result = should_use_color(config["color"]) assert result is False -@pytest.mark.kwparametrize( - dict(cmdline="--no-color", expect=False), - dict(cmdline="--color", expect=True), +@pytest.mark.parametrize( + "color_argv, expect", + [(["--no-color"], False), (["--color"], True)], + indirect=["color_argv"], ) def test_should_use_color_pygments_and_command_line_argument( tmp_path: Path, pyproject_toml_color: str, env_no_color: Dict[str, str], env_py_colors: Dict[str, str], - cmdline: str, + color_argv: List[str], expect: bool, tty: bool, ) -> None: """--color / --no-color determines highlighting if `pygments` is installed""" - argv = [cmdline, str(tmp_path / "dummy.py")] - _, config, _ = parse_command_line(argv) + _, config, _ = parse_command_line(color_argv) result = should_use_color(config["color"]) assert result == expect -@pytest.mark.kwparametrize( - dict(env_py_colors_={"PY_COLORS": "0"}, expect=False), - dict(env_py_colors_={"PY_COLORS": "1"}, expect=True), +@pytest.mark.parametrize( + "env_py_colors, expect", + [({"PY_COLORS": "0"}, False), ({"PY_COLORS": "1"}, True)], + indirect=["env_py_colors"], ) +@pytest.mark.parametrize("color_argv", [[]]) def test_should_use_color_pygments_and_py_colors( - tmp_path: Path, pyproject_toml_color: str, env_no_color: Dict[str, str], - env_py_colors_: Dict[str, str], - expect: bool, + env_py_colors: Dict[str, str], + color_argv: List[str], tty: bool, + expect: bool, ) -> None: """PY_COLORS determines highlighting when `pygments` installed and no cmdline args @@ -158,14 +196,14 @@ def test_should_use_color_pygments_and_py_colors( - there is no ``--color`` or `--no-color`` command line option """ - with patch.dict(os.environ, env_py_colors_, clear=True): - _, config, _ = parse_command_line([str(tmp_path / "dummy.py")]) + _, config, _ = parse_command_line(color_argv) result = should_use_color(config["color"]) assert result == expect +@pytest.mark.parametrize("color_argv", [[]], indirect=True) @pytest.mark.parametrize( "params, expect", [ @@ -184,7 +222,9 @@ def test_should_use_color_pygments_and_py_colors( ("color=true NO_COLOR=foo tty", ""), ], ) -def test_should_use_color_pygments(tmp_path: Path, params: str, expect: str) -> None: +def test_should_use_color_pygments( + tmp_path: Path, color_argv: List[str], params: str, expect: str +) -> None: """Color output is enabled only if correct configuration options are in place These tests are set up so that it appears as if @@ -200,8 +240,8 @@ def test_should_use_color_pygments(tmp_path: Path, params: str, expect: str) -> env = {} if " NO_COLOR=foo " in params: env["NO_COLOR"] = "foo" - with patch.dict(os.environ, env, clear=True): - _, config, _ = parse_command_line([str(tmp_path / "dummy.py")]) + with patch.dict(os.environ, env): + _, config, _ = parse_command_line(color_argv) with patch("sys.stdout.isatty", Mock(return_value=params.endswith(" tty"))): result = should_use_color(config["color"]) From f6ef6faa9f022bd1aabc0bcbb839953961251094 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:56:02 +0300 Subject: [PATCH 09/14] `test_should_use_color_pygments` with fixtures --- src/darker/tests/test_highlighting.py | 107 +++++++++++++++----------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index deaaf3b0d..945da014b 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -5,6 +5,7 @@ import os import sys from pathlib import Path +from shlex import shlex from typing import Dict, Generator, List from unittest.mock import Mock, patch @@ -65,49 +66,67 @@ def pyproject_toml_color( yield request.param -@pytest.fixture(params=[False, True]) -def tty(request: SubRequest) -> Generator[None, None, None]: +@pytest.fixture(params=["", "tty"]) +def tty(request: SubRequest) -> Generator[bool, None, None]: """Parametrized fixture for patching `sys.stdout.isatty` - Patches `sys.stdout.isatty` to return either `False` or `True`. + Patches `sys.stdout.isatty` to return either `False` or `True`. The parameter values + are strings and not booleans in order to improve readability of parametrized tests. :param request: The Pytest ``request`` object :yield: The patched `False` or `True` return value for `sys.stdout.isatty` """ - with patch("sys.stdout.isatty", Mock(return_value=request.param)): + is_a_tty = request.param == "tty" + with patch("sys.stdout.isatty", Mock(return_value=is_a_tty)): - yield request.param + yield is_a_tty -@pytest.fixture(params=[{}, {"NO_COLOR": "foo"}]) -def env_no_color(request: SubRequest) -> Generator[None, None, None]: +def _parse_environment_variables(definitions: str) -> Dict[str, str]: + """Parse a ``"= ="`` formatted string into a dictionary + + :param definitions: The string to parse + :return: The parsed dictionary + + """ + return dict(item.split("=") for item in shlex(definitions, punctuation_chars=" ")) + + +@pytest.fixture(params=["", "NO_COLOR=foo"]) +def env_no_color(request: SubRequest) -> Generator[Dict[str, str], None, None]: """Parametrized fixture for patching ``NO_COLOR`` - Patches the environment with or without the ``NO_COLOR`` environment variable + Patches the environment with or without the ``NO_COLOR`` environment variable. The + environment is expressed as a space-separated string to improve readability of + parametrized tests. :param request: The Pytest ``request`` object :yield: The patched items in the environment """ - with patch.dict(os.environ, request.param): + environ = _parse_environment_variables(request.param) + with patch.dict(os.environ, environ): - yield request.param + yield environ -@pytest.fixture(params=[{}, {"PY_COLORS": "0"}, {"PY_COLORS": "1"}]) -def env_py_colors(request: SubRequest) -> Generator[None, None, None]: +@pytest.fixture(params=["", "PY_COLORS=0", "PY_COLORS=1"]) +def env_py_colors(request: SubRequest) -> Generator[Dict[str, str], None, None]: """Parametrized fixture for patching ``PY_COLORS`` - Patches the environment with or without the ``PY_COLORS`` environment variable + Patches the environment with or without the ``PY_COLORS`` environment variable. The + environment is expressed as a space-separated string to improve readability of + parametrized tests. :param request: The Pytest ``request`` object :yield: The patched items in the environment """ - with patch.dict(os.environ, request.param): + environ = _parse_environment_variables(request.param) + with patch.dict(os.environ, environ): - yield request.param + yield environ @pytest.fixture @@ -177,7 +196,7 @@ def test_should_use_color_pygments_and_command_line_argument( @pytest.mark.parametrize( "env_py_colors, expect", - [({"PY_COLORS": "0"}, False), ({"PY_COLORS": "1"}, True)], + [("PY_COLORS=0", False), ("PY_COLORS=1", True)], indirect=["env_py_colors"], ) @pytest.mark.parametrize("color_argv", [[]]) @@ -205,46 +224,44 @@ def test_should_use_color_pygments_and_py_colors( @pytest.mark.parametrize("color_argv", [[]], indirect=True) @pytest.mark.parametrize( - "params, expect", + "pyproject_toml_color, env_no_color, tty, expect", [ - # for the expected value, for readability, strings are used instead of booleans - (" ", ""), - (" tty", "should_use_color() == True"), - ("color=false ", ""), - ("color=false tty", ""), - ("color=true ", "should_use_color() == True"), - ("color=true tty", "should_use_color() == True"), - (" NO_COLOR=foo ", ""), - (" NO_COLOR=foo tty", ""), - ("color=false NO_COLOR=foo ", ""), - ("color=false NO_COLOR=foo tty", ""), - ("color=true NO_COLOR=foo ", ""), - ("color=true NO_COLOR=foo tty", ""), + # for readability, padded strings are used for parameters and the expectation + (" ", " ", " ", " "), + (" ", " ", "tty", "should_use_color() == True"), + ("color = false", " ", " ", " "), + ("color = false", " ", "tty", " "), + ("color = true ", " ", " ", "should_use_color() == True"), + ("color = true ", " ", "tty", "should_use_color() == True"), + (" ", "NO_COLOR=foo", " ", " "), + (" ", "NO_COLOR=foo", "tty", " "), + ("color = false", "NO_COLOR=foo", " ", " "), + ("color = false", "NO_COLOR=foo", "tty", " "), + ("color = true ", "NO_COLOR=foo", " ", " "), + ("color = true ", "NO_COLOR=foo", "tty", " "), ], + indirect=["pyproject_toml_color", "env_no_color", "tty"], ) def test_should_use_color_pygments( - tmp_path: Path, color_argv: List[str], params: str, expect: str + tmp_path: Path, + pyproject_toml_color: str, + env_no_color: Dict[str, str], + tty: bool, + color_argv: List[str], + expect: str, ) -> None: """Color output is enabled only if correct configuration options are in place These tests are set up so that it appears as if - - ``pygments`` is installed + - ``pygments`` is installed (required for running the tests) - there is no ``--color`` or `--no-color`` command line option - - the ``PY_COLORS`` environment variable isn't set to ``0`` or ``1`` + - the ``PY_COLORS`` environment variable isn't set to ``0`` or ``1`` (cleared by + the auto-use ``clear_environ`` fixture) """ - with (tmp_path / "pyproject.toml").open("w") as pyproject: - print("[tool.darker]", file=pyproject) - if params.startswith("color="): - print(params.split()[0], file=pyproject) - env = {} - if " NO_COLOR=foo " in params: - env["NO_COLOR"] = "foo" - with patch.dict(os.environ, env): - _, config, _ = parse_command_line(color_argv) - with patch("sys.stdout.isatty", Mock(return_value=params.endswith(" tty"))): - - result = should_use_color(config["color"]) + _, config, _ = parse_command_line(color_argv) + + result = should_use_color(config["color"]) assert result == (expect == "should_use_color() == True") From 30336b3051a885cd32774afb43aa07c466a56711 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 19:22:04 +0300 Subject: [PATCH 10/14] Parse command line inside a fixture, too --- src/darker/tests/test_highlighting.py | 77 ++++++++++++++++++--------- 1 file changed, 51 insertions(+), 26 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index 945da014b..e544013ce 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -6,7 +6,7 @@ import sys from pathlib import Path from shlex import shlex -from typing import Dict, Generator, List +from typing import Dict, Generator from unittest.mock import Mock, patch import pytest @@ -19,7 +19,7 @@ @pytest.fixture(scope="function", autouse=True) def clean_environ(): - """Fixture for clearing unwanted environment varables + """Fixture for clearing unwanted environment variables The ``NO_COLOR`` and ``PY_COLORS`` environment variables are tested in this module, so we need to ensure they aren't already set. @@ -97,6 +97,9 @@ def _parse_environment_variables(definitions: str) -> Dict[str, str]: def env_no_color(request: SubRequest) -> Generator[Dict[str, str], None, None]: """Parametrized fixture for patching ``NO_COLOR`` + This fixture must come before `config_from_env_and_argv` in test function + signatures. + Patches the environment with or without the ``NO_COLOR`` environment variable. The environment is expressed as a space-separated string to improve readability of parametrized tests. @@ -115,6 +118,9 @@ def env_no_color(request: SubRequest) -> Generator[Dict[str, str], None, None]: def env_py_colors(request: SubRequest) -> Generator[Dict[str, str], None, None]: """Parametrized fixture for patching ``PY_COLORS`` + This fixture must come before `config_from_env_and_argv` in test function + signatures. + Patches the environment with or without the ``PY_COLORS`` environment variable. The environment is expressed as a space-separated string to improve readability of parametrized tests. @@ -142,18 +148,29 @@ def uninstall_pygments() -> Generator[None, None, None]: @pytest.fixture(params=[[], ["--no-color"], ["--color"]]) -def color_argv(request: SubRequest, tmp_path: Path) -> Generator[List[str], None, None]: +def config_from_env_and_argv( + request: SubRequest, tmp_path: Path +) -> Generator[bool, None, None]: """Parametrized fixture for the ``--color`` / ``--no-color`` arguments - Yields command lines with no color argument, with the ``--color`` argument, and with - the ``--no--color`` argument, appended with a dummy path to a Python file. + Yields ``color`` configuration boolean values resulting from the current environment + variables and a command line + - with no color argument, + - with the ``--color`` argument, and + - with the ``--no--color`` argument. + + The ``NO_COLOR`` and ``PY_COLORS`` environment variables affect the resulting + configuration, and must precede `config_from_env_and_argv` in test function + signatures (if they are being used). :param request: The Pytest ``request`` object :param tmp_path: A temporary directory created by Pytest :yield: The list of arguments for the Darker command line """ - yield request.param + [str(tmp_path / "dummy.py")] + argv = request.param + [str(tmp_path / "dummy.py")] + _, config, _ = parse_command_line(argv) + yield config["color"] def test_should_use_color_no_pygments( @@ -161,35 +178,41 @@ def test_should_use_color_no_pygments( pyproject_toml_color: str, env_no_color: Dict[str, str], env_py_colors: Dict[str, str], - color_argv: List[str], + config_from_env_and_argv: bool, tty: bool, ) -> None: - """Color output is never used if `pygments` is not installed""" - _, config, _ = parse_command_line(color_argv) + """Color output is never used if `pygments` is not installed + + All combinations of ``pyproject.toml`` options, environment variables and command + line options affecting syntax highlighting are tested without `pygments`. - result = should_use_color(config["color"]) + """ + result = should_use_color(config_from_env_and_argv) assert result is False @pytest.mark.parametrize( - "color_argv, expect", + "config_from_env_and_argv, expect", [(["--no-color"], False), (["--color"], True)], - indirect=["color_argv"], + indirect=["config_from_env_and_argv"], ) def test_should_use_color_pygments_and_command_line_argument( tmp_path: Path, pyproject_toml_color: str, env_no_color: Dict[str, str], env_py_colors: Dict[str, str], - color_argv: List[str], + config_from_env_and_argv: bool, expect: bool, tty: bool, ) -> None: - """--color / --no-color determines highlighting if `pygments` is installed""" - _, config, _ = parse_command_line(color_argv) + """--color / --no-color determines highlighting if `pygments` is installed - result = should_use_color(config["color"]) + All combinations of ``pyproject.toml`` options, environment variables and command + line options affecting syntax highlighting are tested with `pygments` installed. + + """ + result = should_use_color(config_from_env_and_argv) assert result == expect @@ -199,12 +222,12 @@ def test_should_use_color_pygments_and_command_line_argument( [("PY_COLORS=0", False), ("PY_COLORS=1", True)], indirect=["env_py_colors"], ) -@pytest.mark.parametrize("color_argv", [[]]) +@pytest.mark.parametrize("config_from_env_and_argv", [[]], indirect=True) def test_should_use_color_pygments_and_py_colors( pyproject_toml_color: str, env_no_color: Dict[str, str], env_py_colors: Dict[str, str], - color_argv: List[str], + config_from_env_and_argv: bool, tty: bool, expect: bool, ) -> None: @@ -214,15 +237,16 @@ def test_should_use_color_pygments_and_py_colors( - ``pygments`` is installed - there is no ``--color`` or `--no-color`` command line option - """ - _, config, _ = parse_command_line(color_argv) + All combinations of ``pyproject.toml`` options and environment variables affecting + syntax highlighting are tested. - result = should_use_color(config["color"]) + """ + result = should_use_color(config_from_env_and_argv) assert result == expect -@pytest.mark.parametrize("color_argv", [[]], indirect=True) +@pytest.mark.parametrize("config_from_env_and_argv", [[]], indirect=True) @pytest.mark.parametrize( "pyproject_toml_color, env_no_color, tty, expect", [ @@ -247,7 +271,7 @@ def test_should_use_color_pygments( pyproject_toml_color: str, env_no_color: Dict[str, str], tty: bool, - color_argv: List[str], + config_from_env_and_argv: bool, expect: str, ) -> None: """Color output is enabled only if correct configuration options are in place @@ -258,10 +282,11 @@ def test_should_use_color_pygments( - the ``PY_COLORS`` environment variable isn't set to ``0`` or ``1`` (cleared by the auto-use ``clear_environ`` fixture) - """ - _, config, _ = parse_command_line(color_argv) + This test exercises the remaining combinations of ``pyproject.toml`` options and + environment variables affecting syntax highlighting. - result = should_use_color(config["color"]) + """ + result = should_use_color(config_from_env_and_argv) assert result == (expect == "should_use_color() == True") From ee0e197a42c11067330fcdd7178752f9ee207fdc Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 20:07:25 +0300 Subject: [PATCH 11/14] Empty `NO_COLOR` to disable syntax highlighting --- src/darker/config.py | 2 +- src/darker/tests/test_highlighting.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/darker/config.py b/src/darker/config.py index e8bacc0a1..7496e2c0f 100644 --- a/src/darker/config.py +++ b/src/darker/config.py @@ -110,7 +110,7 @@ def override_color_with_environment(pyproject_config: DarkerConfig) -> DarkerCon py_colors = os.getenv("PY_COLORS") if py_colors in {"0", "1"}: config["color"] = py_colors == "1" - elif os.getenv("NO_COLOR"): + elif os.getenv("NO_COLOR") is not None: config["color"] = False return config diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index e544013ce..9d5524423 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -93,7 +93,7 @@ def _parse_environment_variables(definitions: str) -> Dict[str, str]: return dict(item.split("=") for item in shlex(definitions, punctuation_chars=" ")) -@pytest.fixture(params=["", "NO_COLOR=foo"]) +@pytest.fixture(params=["", "NO_COLOR=", "NO_COLOR=foo"]) def env_no_color(request: SubRequest) -> Generator[Dict[str, str], None, None]: """Parametrized fixture for patching ``NO_COLOR`` @@ -257,6 +257,12 @@ def test_should_use_color_pygments_and_py_colors( ("color = false", " ", "tty", " "), ("color = true ", " ", " ", "should_use_color() == True"), ("color = true ", " ", "tty", "should_use_color() == True"), + (" ", "NO_COLOR= ", " ", " "), + (" ", "NO_COLOR= ", "tty", " "), + ("color = false", "NO_COLOR= ", " ", " "), + ("color = false", "NO_COLOR= ", "tty", " "), + ("color = true ", "NO_COLOR= ", " ", " "), + ("color = true ", "NO_COLOR= ", "tty", " "), (" ", "NO_COLOR=foo", " ", " "), (" ", "NO_COLOR=foo", "tty", " "), ("color = false", "NO_COLOR=foo", " ", " "), From 969237baa0c985b305d71c9cebbbf3225f0e7db7 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Sun, 10 Apr 2022 22:42:03 +0300 Subject: [PATCH 12/14] Support FORCE_COLOR. Speed up tests. --- src/darker/config.py | 2 + src/darker/tests/test_highlighting.py | 198 ++++++++++++++++++-------- 2 files changed, 144 insertions(+), 56 deletions(-) diff --git a/src/darker/config.py b/src/darker/config.py index 7496e2c0f..b60b535cd 100644 --- a/src/darker/config.py +++ b/src/darker/config.py @@ -112,6 +112,8 @@ def override_color_with_environment(pyproject_config: DarkerConfig) -> DarkerCon config["color"] = py_colors == "1" elif os.getenv("NO_COLOR") is not None: config["color"] = False + elif os.getenv("FORCE_COLOR") is not None: + config["color"] = True return config diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index 9d5524423..6d7841d44 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -1,13 +1,14 @@ """Unit tests for :mod:`darker.highlighting`""" # pylint: disable=too-many-arguments,redefined-outer-name,unused-argument +# pylint: disable=protected-access import os import sys from pathlib import Path from shlex import shlex -from typing import Dict, Generator -from unittest.mock import Mock, patch +from typing import Dict, Generator, cast +from unittest.mock import patch import pytest from _pytest.fixtures import SubRequest @@ -17,29 +18,52 @@ from darker.highlighting import colorize, lexers, should_use_color -@pytest.fixture(scope="function", autouse=True) +@pytest.fixture(scope="module") +def module_tmp_path(tmp_path_factory: pytest.TempPathFactory) -> Path: + """Fixture for creating a module-scope temporary directory + + :param tmp_path_factory: The temporary path factory fixture from Pytest + :return: The created directory path + + """ + return tmp_path_factory.mktemp("test_highlighting") + + +def unset_our_env_vars(): + """Unset the environment variables used in this test module""" + os.environ.pop("PY_COLORS", None) + os.environ.pop("NO_COLOR", None) + os.environ.pop("FORCE_COLOR", None) + + +@pytest.fixture(scope="module", autouse=True) def clean_environ(): """Fixture for clearing unwanted environment variables The ``NO_COLOR`` and ``PY_COLORS`` environment variables are tested in this module, so we need to ensure they aren't already set. + In all `os.environ` patching, we use our own low-level custom code instead of + `unittest.mock.patch.dict` for performance reasons. + """ - environ = os.environ.copy() - environ.pop("PY_COLORS", None) - environ.pop("NO_COLOR", None) - with patch.dict("os.environ", environ, clear=True): + old = os.environ + os.environ = cast(os._Environ[str], old.copy()) # noqa: B003 + unset_our_env_vars() - yield + yield + + os.environ = old # noqa: B003 @pytest.fixture(params=["", "color = false", "color = true"]) def pyproject_toml_color( - request: SubRequest, tmp_path: Path + request: SubRequest, module_tmp_path: Path ) -> Generator[None, None, None]: """Parametrized fixture for the ``color =`` option in ``pyproject.toml`` - Creates three versions of ``pyproject.toml`` in ``tmp_path`` for a test function: + Creates three versions of ``pyproject.toml`` in ``module_tmp_path`` for a test + function: Without the ``color =`` option:: @@ -56,15 +80,18 @@ def pyproject_toml_color( color = true :param request: The Pytest ``request`` object - :param tmp_path: A temporary directory created by Pytest + :param module_tmp_path: A temporary directory created by Pytest :yield: The ``color =`` option line in ``pyproject.toml``, or an empty string """ - with (tmp_path / "pyproject.toml").open("w") as pyproject_toml: + pyproject_toml_path = module_tmp_path / "pyproject.toml" + with pyproject_toml_path.open("w") as pyproject_toml: print(f"[tool.darker]\n{request.param}\n", file=pyproject_toml) yield request.param + pyproject_toml_path.unlink() + @pytest.fixture(params=["", "tty"]) def tty(request: SubRequest) -> Generator[bool, None, None]: @@ -72,15 +99,19 @@ def tty(request: SubRequest) -> Generator[bool, None, None]: Patches `sys.stdout.isatty` to return either `False` or `True`. The parameter values are strings and not booleans in order to improve readability of parametrized tests. + Custom patching for performance. :param request: The Pytest ``request`` object :yield: The patched `False` or `True` return value for `sys.stdout.isatty` """ - is_a_tty = request.param == "tty" - with patch("sys.stdout.isatty", Mock(return_value=is_a_tty)): + old_isatty = sys.stdout.isatty + is_a_tty: bool = request.param == "tty" + sys.stdout.isatty = lambda: is_a_tty # type: ignore[assignment] + + yield is_a_tty - yield is_a_tty + sys.stdout.isatty = old_isatty # type: ignore[assignment] def _parse_environment_variables(definitions: str) -> Dict[str, str]: @@ -108,10 +139,29 @@ def env_no_color(request: SubRequest) -> Generator[Dict[str, str], None, None]: :yield: The patched items in the environment """ - environ = _parse_environment_variables(request.param) - with patch.dict(os.environ, environ): + os.environ.update(_parse_environment_variables(request.param)) + yield request.param + unset_our_env_vars() - yield environ + +@pytest.fixture(params=["", "FORCE_COLOR=", "FORCE_COLOR=foo"]) +def env_force_color(request: SubRequest) -> Generator[Dict[str, str], None, None]: + """Parametrized fixture for patching ``FORCE_COLOR`` + + This fixture must come before `config_from_env_and_argv` in test function + signatures. + + Patches the environment with or without the ``FORCE_COLOR`` environment variable. + The environment is expressed as a space-separated string to improve readability of + parametrized tests. + + :param request: The Pytest ``request`` object + :yield: The patched items in the environment + + """ + os.environ.update(_parse_environment_variables(request.param)) + yield request.param + unset_our_env_vars() @pytest.fixture(params=["", "PY_COLORS=0", "PY_COLORS=1"]) @@ -129,10 +179,9 @@ def env_py_colors(request: SubRequest) -> Generator[Dict[str, str], None, None]: :yield: The patched items in the environment """ - environ = _parse_environment_variables(request.param) - with patch.dict(os.environ, environ): - - yield environ + os.environ.update(_parse_environment_variables(request.param)) + yield request.param + unset_our_env_vars() @pytest.fixture @@ -147,9 +196,12 @@ def uninstall_pygments() -> Generator[None, None, None]: yield +config_cache = {} + + @pytest.fixture(params=[[], ["--no-color"], ["--color"]]) def config_from_env_and_argv( - request: SubRequest, tmp_path: Path + request: SubRequest, module_tmp_path: Path ) -> Generator[bool, None, None]: """Parametrized fixture for the ``--color`` / ``--no-color`` arguments @@ -164,20 +216,30 @@ def config_from_env_and_argv( signatures (if they are being used). :param request: The Pytest ``request`` object - :param tmp_path: A temporary directory created by Pytest + :param module_tmp_path: A temporary directory created by Pytest :yield: The list of arguments for the Darker command line """ - argv = request.param + [str(tmp_path / "dummy.py")] - _, config, _ = parse_command_line(argv) - yield config["color"] + argv = request.param + [str(module_tmp_path / "dummy.py")] + cache_key = ( + tuple(request.param), + os.getenv("NO_COLOR"), + os.getenv("FORCE_COLOR"), + os.getenv("PY_COLORS"), + (module_tmp_path / "pyproject.toml").read_bytes(), + ) + if cache_key not in config_cache: + _, config, _ = parse_command_line(argv) + config_cache[cache_key] = config["color"] + yield config_cache[cache_key] def test_should_use_color_no_pygments( uninstall_pygments: None, pyproject_toml_color: str, - env_no_color: Dict[str, str], - env_py_colors: Dict[str, str], + env_no_color: str, + env_force_color: str, + env_py_colors: str, config_from_env_and_argv: bool, tty: bool, ) -> None: @@ -198,10 +260,10 @@ def test_should_use_color_no_pygments( indirect=["config_from_env_and_argv"], ) def test_should_use_color_pygments_and_command_line_argument( - tmp_path: Path, pyproject_toml_color: str, - env_no_color: Dict[str, str], - env_py_colors: Dict[str, str], + env_no_color: str, + env_force_color: str, + env_py_colors: str, config_from_env_and_argv: bool, expect: bool, tty: bool, @@ -225,8 +287,9 @@ def test_should_use_color_pygments_and_command_line_argument( @pytest.mark.parametrize("config_from_env_and_argv", [[]], indirect=True) def test_should_use_color_pygments_and_py_colors( pyproject_toml_color: str, - env_no_color: Dict[str, str], - env_py_colors: Dict[str, str], + env_no_color: str, + env_force_color: str, + env_py_colors: str, config_from_env_and_argv: bool, tty: bool, expect: bool, @@ -246,36 +309,59 @@ def test_should_use_color_pygments_and_py_colors( assert result == expect +@pytest.mark.parametrize( + "env_no_color, env_force_color, expect", + [ + (" ", "FORCE_COLOR= ", "should_use_color() == True"), + (" ", "FORCE_COLOR=foo", "should_use_color() == True"), + ("NO_COLOR= ", "FORCE_COLOR= ", " "), + ("NO_COLOR= ", "FORCE_COLOR=foo", " "), + ("NO_COLOR=foo", "FORCE_COLOR= ", " "), + ("NO_COLOR=foo", "FORCE_COLOR=foo", " "), + ], + indirect=["env_no_color", "env_force_color"], +) +@pytest.mark.parametrize("config_from_env_and_argv", [[]], indirect=True) +def test_should_use_color_no_color_force_color( + pyproject_toml_color: str, + env_no_color: str, + env_force_color: str, + config_from_env_and_argv: bool, + tty: bool, + expect: str, +) -> None: + """NO_COLOR/FORCE_COLOR determine highlighting in absence of PY_COLORS/cmdline args + + These tests are set up so that it appears as if + - ``pygments`` is installed + - the ``PY_COLORS`` environment variable is unset + - there is no ``--color`` or `--no-color`` command line option + + All combinations of ``pyproject.toml`` options, ``NO_COLOR``, ``FORCE_COLOR`` and + `sys.stdout.isatty` are tested. + + """ + result = should_use_color(config_from_env_and_argv) + + assert result == (expect == "should_use_color() == True") + + @pytest.mark.parametrize("config_from_env_and_argv", [[]], indirect=True) @pytest.mark.parametrize( - "pyproject_toml_color, env_no_color, tty, expect", + "pyproject_toml_color, tty, expect", [ # for readability, padded strings are used for parameters and the expectation - (" ", " ", " ", " "), - (" ", " ", "tty", "should_use_color() == True"), - ("color = false", " ", " ", " "), - ("color = false", " ", "tty", " "), - ("color = true ", " ", " ", "should_use_color() == True"), - ("color = true ", " ", "tty", "should_use_color() == True"), - (" ", "NO_COLOR= ", " ", " "), - (" ", "NO_COLOR= ", "tty", " "), - ("color = false", "NO_COLOR= ", " ", " "), - ("color = false", "NO_COLOR= ", "tty", " "), - ("color = true ", "NO_COLOR= ", " ", " "), - ("color = true ", "NO_COLOR= ", "tty", " "), - (" ", "NO_COLOR=foo", " ", " "), - (" ", "NO_COLOR=foo", "tty", " "), - ("color = false", "NO_COLOR=foo", " ", " "), - ("color = false", "NO_COLOR=foo", "tty", " "), - ("color = true ", "NO_COLOR=foo", " ", " "), - ("color = true ", "NO_COLOR=foo", "tty", " "), + (" ", " ", " "), + (" ", "tty", "should_use_color() == True"), + ("color = false", " ", " "), + ("color = false", "tty", " "), + ("color = true ", " ", "should_use_color() == True"), + ("color = true ", "tty", "should_use_color() == True"), ], - indirect=["pyproject_toml_color", "env_no_color", "tty"], + indirect=["pyproject_toml_color", "tty"], ) def test_should_use_color_pygments( - tmp_path: Path, pyproject_toml_color: str, - env_no_color: Dict[str, str], tty: bool, config_from_env_and_argv: bool, expect: str, From 0b1e7e30a56f8585e5f7f8242c0fbb693fc5be2a Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Tue, 12 Apr 2022 09:50:47 +0300 Subject: [PATCH 13/14] Python <3.9 compatible typing for `os.environ` --- src/darker/tests/test_highlighting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/darker/tests/test_highlighting.py b/src/darker/tests/test_highlighting.py index 6d7841d44..9514234d9 100644 --- a/src/darker/tests/test_highlighting.py +++ b/src/darker/tests/test_highlighting.py @@ -7,7 +7,7 @@ import sys from pathlib import Path from shlex import shlex -from typing import Dict, Generator, cast +from typing import Dict, Generator from unittest.mock import patch import pytest @@ -48,7 +48,7 @@ def clean_environ(): """ old = os.environ - os.environ = cast(os._Environ[str], old.copy()) # noqa: B003 + os.environ = old.copy() # type: ignore # noqa: B003 unset_our_env_vars() yield From 838d2c1eff0f74f72d363fb739246777889e5de3 Mon Sep 17 00:00:00 2001 From: Antti Kaihola <13725+akaihola@users.noreply.github.com> Date: Tue, 12 Apr 2022 20:43:09 +0300 Subject: [PATCH 14/14] Require `pytest>=6.2.0` for the test suite --- CHANGES.rst | 1 + constraints-oldest.txt | 2 +- setup.cfg | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 42669decf..cb739ebbd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,7 @@ Added ``pyproject.toml``, the ``PY_COLORS`` and ``NO_COLOR`` environment variables, and the ``--color``/``--no-color`` command line options. - Syntax highlighting is now enabled by default in the GitHub Action. +- ``pytest>=6.2.0`` now required for the test suite due to type hinting issues. Fixed ----- diff --git a/constraints-oldest.txt b/constraints-oldest.txt index 4514515b0..cb964f065 100644 --- a/constraints-oldest.txt +++ b/constraints-oldest.txt @@ -13,7 +13,7 @@ flake8-bugbear==22.1.11 flake8-comprehensions==3.7.0 mypy==0.940 Pygments==2.4.0 -pytest==6.1.0 +pytest==6.2.0 pytest-flake8==1.0.6 pytest-isort==1.1.0 pytest-kwparametrize==0.0.3 diff --git a/setup.cfg b/setup.cfg index 218636dc7..d2c8b4bde 100644 --- a/setup.cfg +++ b/setup.cfg @@ -62,7 +62,7 @@ test = mypy>=0.940 pygments pylint - pytest>=6.1.0 + pytest>=6.2.0 pytest-darker pytest-flake8>=1.0.6 pytest-isort>=1.1.0