Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASCII output for --parallel report lines #2164

Merged
merged 2 commits into from Aug 21, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CONTRIBUTORS
Expand Up @@ -20,6 +20,7 @@ Bastien Vallet
Benoit Pierre
Bernat Gabor
Brett Langdon
Brett Smith
Bruno Oliveira
Carl Meyer
Charles Brunet
Expand Down
1 change: 1 addition & 0 deletions 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`
40 changes: 19 additions & 21 deletions src/tox/util/spinner.py
Expand Up @@ -5,7 +5,7 @@
import os
import sys
import threading
from collections import OrderedDict
from collections import OrderedDict, namedtuple
from datetime import datetime

import py
Expand All @@ -19,34 +19,32 @@ class _CursorInfo(ctypes.Structure):
_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
Expand Down Expand Up @@ -105,13 +103,13 @@ def add(self, name):
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]
Expand Down
25 changes: 25 additions & 0 deletions tests/unit/util/test_spinner.py
Expand Up @@ -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",
[
Expand Down