diff options
| author | Brett Smith <brettcsmith@brettcsmith.org> | 2021-08-21 09:39:07 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-08-21 14:39:07 +0100 |
| commit | 8f5ca1165749efd71d4d3afc19285a9257fe1279 (patch) | |
| tree | c4460a2ed2b848f0184feb83b964b4a963b03ad1 | |
| parent | e1f1826d0533c3b45cbe72c9210ae0cca3ad2181 (diff) | |
| download | tox-git-8f5ca1165749efd71d4d3afc19285a9257fe1279.tar.gz | |
ASCII output for --parallel report lines (#2164)
* Add SpinnerMessage helper class
This class provides a single interface for spinner strings where we
ideally want to report Unicode but want to fall back to ASCII when the
terminal doesn't support it.
* Add ASCII messages for OK/FAIL/SKIP lines
Fixes #1421.
| -rw-r--r-- | CONTRIBUTORS | 1 | ||||
| -rw-r--r-- | docs/changelog/1421.bugfix.rst | 1 | ||||
| -rw-r--r-- | src/tox/util/spinner.py | 40 | ||||
| -rw-r--r-- | tests/unit/util/test_spinner.py | 25 |
4 files changed, 46 insertions, 21 deletions
diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 14d9bbad..d91bef34 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -20,6 +20,7 @@ Bastien Vallet Benoit Pierre Bernat Gabor Brett Langdon +Brett Smith Bruno Oliveira Carl Meyer Charles Brunet diff --git a/docs/changelog/1421.bugfix.rst b/docs/changelog/1421.bugfix.rst new file mode 100644 index 00000000..8e0a5ff5 --- /dev/null +++ b/docs/changelog/1421.bugfix.rst @@ -0,0 +1 @@ +``--parallel`` reports now show ASCII OK/FAIL/SKIP lines when full Unicode output is not available - by :user:`brettcs` diff --git a/src/tox/util/spinner.py b/src/tox/util/spinner.py index 523a0c49..ee225895 100644 --- a/src/tox/util/spinner.py +++ b/src/tox/util/spinner.py @@ -5,7 +5,7 @@ from __future__ import absolute_import, unicode_literals import os import sys import threading -from collections import OrderedDict +from collections import OrderedDict, namedtuple from datetime import datetime import py @@ -19,34 +19,32 @@ if os.name == "nt": _fields_ = [("size", ctypes.c_int), ("visible", ctypes.c_byte)] -def _file_support_encoding(chars, file): - encoding = getattr(file, "encoding", None) - if encoding is not None: - for char in chars: - try: - char.encode(encoding) - except UnicodeEncodeError: - break +_BaseMessage = namedtuple("_BaseMessage", ["unicode_msg", "ascii_msg"]) + + +class SpinnerMessage(_BaseMessage): + def for_file(self, file): + try: + self.unicode_msg.encode(file.encoding) + except (AttributeError, TypeError, UnicodeEncodeError): + return self.ascii_msg else: - return True - return False + return self.unicode_msg class Spinner(object): CLEAR_LINE = "\033[K" max_width = 120 - UNICODE_FRAMES = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"] - ASCII_FRAMES = ["|", "-", "+", "x", "*"] + FRAMES = SpinnerMessage("⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏", "|-+x*") + OK_FLAG = SpinnerMessage("✔ OK", "[ OK ]") + FAIL_FLAG = SpinnerMessage("✖ FAIL", "[FAIL]") + SKIP_FLAG = SpinnerMessage("⚠ SKIP", "[SKIP]") def __init__(self, enabled=True, refresh_rate=0.1): self.refresh_rate = refresh_rate self.enabled = enabled self._file = sys.stdout - self.frames = ( - self.UNICODE_FRAMES - if _file_support_encoding(self.UNICODE_FRAMES, sys.stdout) - else self.ASCII_FRAMES - ) + self.frames = self.FRAMES.for_file(self._file) self.stream = py.io.TerminalWriter(file=self._file) self._envs = OrderedDict() self._frame_index = 0 @@ -105,13 +103,13 @@ class Spinner(object): self._envs[name] = datetime.now() def succeed(self, key): - self.finalize(key, "✔ OK", green=True) + self.finalize(key, self.OK_FLAG.for_file(self._file), green=True) def fail(self, key): - self.finalize(key, "✖ FAIL", red=True) + self.finalize(key, self.FAIL_FLAG.for_file(self._file), red=True) def skip(self, key): - self.finalize(key, "⚠ SKIP", white=True) + self.finalize(key, self.SKIP_FLAG.for_file(self._file), white=True) def finalize(self, key, status, **kwargs): start_at = self._envs[key] diff --git a/tests/unit/util/test_spinner.py b/tests/unit/util/test_spinner.py index 7c64ac45..2511d584 100644 --- a/tests/unit/util/test_spinner.py +++ b/tests/unit/util/test_spinner.py @@ -113,6 +113,31 @@ def test_spinner_stdout_not_unicode(mocker, capfd): assert all(f in written for f in spin.frames) +@freeze_time("2012-01-14") +def test_spinner_report_not_unicode(mocker, capfd): + stdout = mocker.patch("tox.util.spinner.sys.stdout") + stdout.encoding = "ascii" + # Disable color to simplify parsing output strings + stdout.isatty = lambda: False + with spinner.Spinner(refresh_rate=100) as spin: + spin.stream.write(os.linesep) + spin.add("ok!") + spin.add("fail!") + spin.add("skip!") + spin.succeed("ok!") + spin.fail("fail!") + spin.skip("skip!") + lines = "".join(args[0] for args, _ in stdout.write.call_args_list).split(os.linesep) + del lines[0] + expected = [ + "\r{}[ OK ] ok! in 0.0 seconds".format(spin.CLEAR_LINE), + "\r{}[FAIL] fail! in 0.0 seconds".format(spin.CLEAR_LINE), + "\r{}[SKIP] skip! in 0.0 seconds".format(spin.CLEAR_LINE), + "\r{}".format(spin.CLEAR_LINE), + ] + assert lines == expected + + @pytest.mark.parametrize( "seconds, expected", [ |
