Skip to content

Commit

Permalink
refactor print colors utils
Browse files Browse the repository at this point in the history
  • Loading branch information
giampaolo committed Feb 15, 2020
1 parent a826d41 commit a8c9e87
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 208 deletions.
4 changes: 2 additions & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
include .cirrus.yml
include .coveragerc
include .gitignore
include CREDITS
Expand Down Expand Up @@ -114,15 +115,14 @@ include scripts/internal/README
include scripts/internal/bench_oneshot.py
include scripts/internal/bench_oneshot_2.py
include scripts/internal/check_broken_links.py
include scripts/internal/download_exes.py
include scripts/internal/fix_flake8.py
include scripts/internal/generate_manifest.py
include scripts/internal/print_access_denied.py
include scripts/internal/print_announce.py
include scripts/internal/print_api_speed.py
include scripts/internal/print_timeline.py
include scripts/internal/purge_installation.py
include scripts/internal/scriptutils.py
include scripts/internal/win_download_wheels.py
include scripts/internal/winmake.py
include scripts/iotop.py
include scripts/killall.py
Expand Down
18 changes: 11 additions & 7 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,24 @@ DEPS = \
check-manifest \
coverage \
flake8 \
futures \
ipaddress \
mock==1.0.1 \
pyperf \
requests \
setuptools \
sphinx \
twine \
unittest2 \
virtualenv \
wheel

ifeq ($(PYTHON), $(filter $(PYTHON), python python2 python2.7))
DEPS += \
futures \
ipaddress \
mock==1.0.1 \
unittest2
endif

# In not in a virtualenv, add --user options for install commands.
INSTALL_OPTS = `$(PYTHON) -c "import sys; print('' if hasattr(sys, 'real_prefix') else '--user')"`
INSTALL_OPTS = `$(PYTHON) -c \
"import sys; print('' if hasattr(sys, 'real_prefix') else '--user')"`
TEST_PREFIX = PYTHONWARNINGS=all PSUTIL_TESTING=1 PSUTIL_DEBUG=1

all: test
Expand Down Expand Up @@ -197,7 +201,7 @@ wheel: ## Generate wheel.
$(PYTHON) setup.py bdist_wheel

win-download-wheels: ## Download wheels hosted on appveyor.
$(TEST_PREFIX) $(PYTHON) scripts/internal/download_exes.py --user giampaolo --project psutil
$(TEST_PREFIX) $(PYTHON) scripts/internal/win_download_wheels.py --user giampaolo --project psutil

upload-src: ## Upload source tarball on https://pypi.org/project/psutil/
${MAKE} sdist
Expand Down
7 changes: 7 additions & 0 deletions docs/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .

DEPS = sphinx


.PHONY: help
help:
@echo "Please use \`make <target>' where <target> is one of"
Expand Down Expand Up @@ -224,3 +227,7 @@ dummy:
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
@echo
@echo "Build finished. Dummy builder generates no files."

.PHONY: setup-dev-env
setup-dev-env: ## Install GIT hooks, pip, test deps (also upgrades them).
$(PYTHON) -m pip install --user --upgrade --trusted-host files.pythonhosted.org $(DEPS)
77 changes: 60 additions & 17 deletions psutil/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,9 @@
PY3 = sys.version_info[0] == 3

__all__ = [
# constants
# OS constants
'FREEBSD', 'BSD', 'LINUX', 'NETBSD', 'OPENBSD', 'MACOS', 'OSX', 'POSIX',
'SUNOS', 'WINDOWS',
'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
# connection constants
'CONN_CLOSE', 'CONN_CLOSE_WAIT', 'CONN_CLOSING', 'CONN_ESTABLISHED',
'CONN_FIN_WAIT1', 'CONN_FIN_WAIT2', 'CONN_LAST_ACK', 'CONN_LISTEN',
Expand All @@ -58,6 +57,8 @@
'STATUS_RUNNING', 'STATUS_SLEEPING', 'STATUS_STOPPED', 'STATUS_SUSPENDED',
'STATUS_TRACING_STOP', 'STATUS_WAITING', 'STATUS_WAKE_KILL',
'STATUS_WAKING', 'STATUS_ZOMBIE', 'STATUS_PARKED',
# other constants
'ENCODING', 'ENCODING_ERRS', 'AF_INET6',
# named tuples
'pconn', 'pcputimes', 'pctxsw', 'pgids', 'pio', 'pionice', 'popenfile',
'pthread', 'puids', 'sconn', 'scpustats', 'sdiskio', 'sdiskpart',
Expand All @@ -66,7 +67,9 @@
'conn_tmap', 'deprecated_method', 'isfile_strict', 'memoize',
'parse_environ_block', 'path_exists_strict', 'usage_percent',
'supports_ipv6', 'sockfam_to_enum', 'socktype_to_enum', "wrap_numbers",
'bytes2human', 'conn_to_ntuple', 'hilite', 'debug',
'bytes2human', 'conn_to_ntuple', 'debug',
# shell utils
'hilite', 'term_supports_colors', 'print_color',
]


Expand Down Expand Up @@ -757,38 +760,78 @@ def decode(s):
return s


def _term_supports_colors(file=sys.stdout):
if hasattr(_term_supports_colors, "ret"):
return _term_supports_colors.ret
# =====================================================================
# --- shell utils
# =====================================================================


@memoize
def term_supports_colors(file=sys.stdout):
if os.name == 'nt':
return True
try:
import curses
assert file.isatty()
curses.setupterm()
assert curses.tigetnum("colors") > 0
except Exception:
_term_supports_colors.ret = False
return False
else:
_term_supports_colors.ret = True
return _term_supports_colors.ret
return True


def hilite(s, ok=True, bold=False):
def hilite(s, color="green", bold=False):
"""Return an highlighted version of 'string'."""
if not _term_supports_colors():
if not term_supports_colors():
return s
attr = []
if ok is None: # no color
pass
elif ok: # green
attr.append('32')
else: # red
attr.append('31')
colors = dict(green='32', red='91', brown='33')
colors[None] = '29'
try:
color = colors[color]
except KeyError:
raise ValueError("invalid color %r; choose between %r" % (
list(colors.keys())))
attr.append(color)
if bold:
attr.append('1')
return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)


def print_color(s, color="green", bold=False, file=sys.stdout):
"""Print a colorized version of string."""
if not term_supports_colors():
print(s, file=file)
elif POSIX:
print(hilite(s, color, bold), file=file)
else:
import ctypes

DEFAULT_COLOR = 7
GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
SetConsoleTextAttribute = \
ctypes.windll.Kernel32.SetConsoleTextAttribute

colors = dict(green=2, red=4, brown=6)
colors[None] = DEFAULT_COLOR
try:
color = colors[color]
except KeyError:
raise ValueError("invalid color %r; choose between %r" % (
color, list(colors.keys())))
if bold and color <= 7:
color += 8

handle_id = -12 if file is sys.stderr else -11
GetStdHandle.restype = ctypes.c_ulong
handle = GetStdHandle(handle_id)
SetConsoleTextAttribute(handle, color)
try:
print(s, file=file)
finally:
SetConsoleTextAttribute(handle, DEFAULT_COLOR)


if bool(os.getenv('PSUTIL_DEBUG', 0)):
import inspect

Expand Down
100 changes: 25 additions & 75 deletions psutil/tests/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
# found in the LICENSE file.

"""
Unit test runner, providing colourized output and printing failures
on KeyboardInterrupt.
Unit test runner, providing new features on top of unittest module:
- colourized output (error, skip)
- print failures/tracebacks on CTRL+C
- re-run failed tests only (make test-failed)
"""

from __future__ import print_function
import atexit
import os
import sys
import unittest
Expand All @@ -23,103 +24,47 @@
ctypes = None

import psutil
from psutil._common import memoize
from psutil._common import hilite
from psutil._common import print_color
from psutil._common import term_supports_colors
from psutil.tests import safe_rmpath
from psutil.tests import TOX


HERE = os.path.abspath(os.path.dirname(__file__))
VERBOSITY = 1 if TOX else 2
FAILED_TESTS_FNAME = '.failed-tests.txt'
if os.name == 'posix':
GREEN = 1
RED = 2
BROWN = 94
else:
GREEN = 2
RED = 4
BROWN = 6
DEFAULT_COLOR = 7


def term_supports_colors(file=sys.stdout):
if os.name == 'nt':
return ctypes is not None
try:
import curses
assert file.isatty()
curses.setupterm()
assert curses.tigetnum("colors") > 0
except Exception:
return False
else:
return True


def hilite(s, color, bold=False):
"""Return an highlighted version of 'string'."""
attr = []
if color == GREEN:
attr.append('32')
elif color == RED:
attr.append('91')
elif color == BROWN:
attr.append('33')
else:
raise ValueError("unrecognized color")
if bold:
attr.append('1')
return '\x1b[%sm%s\x1b[0m' % (';'.join(attr), s)


@memoize
def _stderr_handle():
GetStdHandle = ctypes.windll.Kernel32.GetStdHandle
STD_ERROR_HANDLE_ID = ctypes.c_ulong(0xfffffff4)
GetStdHandle.restype = ctypes.c_ulong
handle = GetStdHandle(STD_ERROR_HANDLE_ID)
atexit.register(ctypes.windll.Kernel32.CloseHandle, handle)
return handle


def win_colorprint(printer, s, color, bold=False):
if bold and color <= 7:
color += 8
handle = _stderr_handle()
SetConsoleTextAttribute = ctypes.windll.Kernel32.SetConsoleTextAttribute
SetConsoleTextAttribute(handle, color)
try:
printer(s)
finally:
SetConsoleTextAttribute(handle, DEFAULT_COLOR)


# =====================================================================
# --- unittest subclasses
# =====================================================================


class ColouredResult(TextTestResult):

def _color_print(self, s, color, bold=False):
if os.name == 'posix':
self.stream.writeln(hilite(s, color, bold=bold))
else:
win_colorprint(self.stream.writeln, s, color, bold=bold)
def _print_color(self, s, color, bold=False):
file = sys.stderr if color == "red" else sys.stdout
print_color(s, color, bold=bold, file=file)

def addSuccess(self, test):
TestResult.addSuccess(self, test)
self._color_print("OK", GREEN)
self._print_color("OK", "green")

def addError(self, test, err):
TestResult.addError(self, test, err)
self._color_print("ERROR", RED, bold=True)
self._print_color("ERROR", "red", bold=True)

def addFailure(self, test, err):
TestResult.addFailure(self, test, err)
self._color_print("FAIL", RED)
self._print_color("FAIL", "red")

def addSkip(self, test, reason):
TestResult.addSkip(self, test, reason)
self._color_print("skipped: %s" % reason, BROWN)
self._print_color("skipped: %s" % reason, "brown")

def printErrorList(self, flavour, errors):
flavour = hilite(flavour, RED, bold=flavour == 'ERROR')
flavour = hilite(flavour, "red", bold=flavour == 'ERROR')
TextTestResult.printErrorList(self, flavour, errors)


Expand All @@ -133,6 +78,11 @@ def _makeResult(self):
return self.result


# =====================================================================
# --- public API
# =====================================================================


def setup_tests():
if 'PSUTIL_TESTING' not in os.environ:
# This won't work on Windows but set_testing() below will do it.
Expand Down
1 change: 0 additions & 1 deletion psutil/tests/test_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,6 @@ def test_terminal(self):
@skip_on_not_implemented(only_if=LINUX)
def test_io_counters(self):
p = psutil.Process()

# test reads
io1 = p.io_counters()
with open(PYTHON_EXE, 'rb') as f:
Expand Down
7 changes: 3 additions & 4 deletions scripts/internal/print_access_denied.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
import time

import psutil
from scriptutils import hilite
from psutil._common import print_color


def main():
Expand All @@ -75,13 +75,12 @@ def main():
# print
templ = "%-20s %-5s %-9s %s"
s = templ % ("API", "AD", "Percent", "Outcome")
print(hilite(s, ok=None, bold=True))
print_color(s, color=None, bold=True)
for methname, ads in sorted(d.items(), key=lambda x: (x[1], x[0])):
perc = (ads / tot_procs) * 100
outcome = "SUCCESS" if not ads else "ACCESS DENIED"
s = templ % (methname, ads, "%6.1f%%" % perc, outcome)
s = hilite(s, ok=not ads)
print(s)
print_color(s, "red" if ads else None)
tot_perc = round((tot_ads / tot_calls) * 100, 1)
print("-" * 50)
print("Totals: access-denied=%s (%s%%), calls=%s, processes=%s, "
Expand Down
Loading

0 comments on commit a8c9e87

Please sign in to comment.