|
| 1 | +import re |
1 | 2 | import sys |
| 3 | +from typing import List |
2 | 4 |
|
3 | 5 | import pytest |
| 6 | +from _pytest.pytester import RunResult |
4 | 7 | from _pytest.pytester import Testdir |
5 | 8 |
|
6 | 9 | if sys.gettrace(): |
@@ -78,6 +81,12 @@ def sep(self, sep, line=None): |
78 | 81 | def write(self, msg, **kw): |
79 | 82 | self.lines.append((TWMock.WRITE, msg)) |
80 | 83 |
|
| 84 | + def _write_source(self, lines, indents=()): |
| 85 | + if not indents: |
| 86 | + indents = [""] * len(lines) |
| 87 | + for indent, line in zip(indents, lines): |
| 88 | + self.line(indent + line) |
| 89 | + |
81 | 90 | def line(self, line, **kw): |
82 | 91 | self.lines.append(line) |
83 | 92 |
|
@@ -125,3 +134,64 @@ def runtest(self): |
125 | 134 | def testdir(testdir: Testdir) -> Testdir: |
126 | 135 | testdir.monkeypatch.setenv("PYTEST_DISABLE_PLUGIN_AUTOLOAD", "1") |
127 | 136 | return testdir |
| 137 | + |
| 138 | + |
| 139 | +@pytest.fixture(scope="session") |
| 140 | +def color_mapping(): |
| 141 | + """Returns a utility class which can replace keys in strings in the form "{NAME}" |
| 142 | + by their equivalent ASCII codes in the terminal. |
| 143 | +
|
| 144 | + Used by tests which check the actual colors output by pytest. |
| 145 | + """ |
| 146 | + |
| 147 | + class ColorMapping: |
| 148 | + COLORS = { |
| 149 | + "red": "\x1b[31m", |
| 150 | + "green": "\x1b[32m", |
| 151 | + "yellow": "\x1b[33m", |
| 152 | + "bold": "\x1b[1m", |
| 153 | + "reset": "\x1b[0m", |
| 154 | + "kw": "\x1b[94m", |
| 155 | + "hl-reset": "\x1b[39;49;00m", |
| 156 | + "function": "\x1b[92m", |
| 157 | + "number": "\x1b[94m", |
| 158 | + "str": "\x1b[33m", |
| 159 | + "print": "\x1b[96m", |
| 160 | + } |
| 161 | + RE_COLORS = {k: re.escape(v) for k, v in COLORS.items()} |
| 162 | + |
| 163 | + @classmethod |
| 164 | + def format(cls, lines: List[str]) -> List[str]: |
| 165 | + """Straightforward replacement of color names to their ASCII codes.""" |
| 166 | + return [line.format(**cls.COLORS) for line in lines] |
| 167 | + |
| 168 | + @classmethod |
| 169 | + def format_for_fnmatch(cls, lines: List[str]) -> List[str]: |
| 170 | + """Replace color names for use with LineMatcher.fnmatch_lines""" |
| 171 | + return [line.format(**cls.COLORS).replace("[", "[[]") for line in lines] |
| 172 | + |
| 173 | + @classmethod |
| 174 | + def format_for_rematch(cls, lines: List[str]) -> List[str]: |
| 175 | + """Replace color names for use with LineMatcher.re_match_lines""" |
| 176 | + return [line.format(**cls.RE_COLORS) for line in lines] |
| 177 | + |
| 178 | + @classmethod |
| 179 | + def requires_ordered_markup(cls, result: RunResult): |
| 180 | + """Should be called if a test expects markup to appear in the output |
| 181 | + in the order they were passed, for example: |
| 182 | +
|
| 183 | + tw.write(line, bold=True, red=True) |
| 184 | +
|
| 185 | + In Python 3.5 there's no guarantee that the generated markup will appear |
| 186 | + in the order called, so we do some limited color testing and skip the rest of |
| 187 | + the test. |
| 188 | + """ |
| 189 | + if sys.version_info < (3, 6): |
| 190 | + # terminal writer.write accepts keyword arguments, so |
| 191 | + # py36+ is required so the markup appears in the expected order |
| 192 | + output = result.stdout.str() |
| 193 | + assert "test session starts" in output |
| 194 | + assert "\x1b[1m" in output |
| 195 | + pytest.skip("doing limited testing because lacking ordered markup") |
| 196 | + |
| 197 | + return ColorMapping |
0 commit comments