xonsh/tests/test_pyghooks.py

385 lines
12 KiB
Python

"""Tests pygments hooks."""
import pytest
import os
import stat
import pathlib
from tempfile import TemporaryDirectory
from xonsh.platform import ON_WINDOWS
from xonsh.pyghooks import (
XonshStyle,
Color,
Token,
color_name_to_pygments_code,
code_by_name,
color_file,
file_color_tokens,
get_style_by_name,
register_custom_pygments_style,
)
from xonsh.environ import LsColors
@pytest.fixture
def xs_LS_COLORS(xession):
"""Xonsh environment including LS_COLORS"""
e = xession.env
lsc = LsColors(LsColors.default_settings)
xession.env["LS_COLORS"] = lsc
xession.shell.shell_type = "prompt_toolkit"
xession.shell.shell.styler = XonshStyle() # default style
yield xession
xession.env = e
DEFAULT_STYLES = {
# Reset
Color.RESET: "noinherit", # Text Reset
# Regular Colors
Color.BLACK: "ansiblack",
Color.BLUE: "ansiblue",
Color.CYAN: "ansicyan",
Color.GREEN: "ansigreen",
Color.PURPLE: "ansimagenta",
Color.RED: "ansired",
Color.WHITE: "ansigray",
Color.YELLOW: "ansiyellow",
Color.INTENSE_BLACK: "ansibrightblack",
Color.INTENSE_BLUE: "ansibrightblue",
Color.INTENSE_CYAN: "ansibrightcyan",
Color.INTENSE_GREEN: "ansibrightgreen",
Color.INTENSE_PURPLE: "ansibrightmagenta",
Color.INTENSE_RED: "ansibrightred",
Color.INTENSE_WHITE: "ansiwhite",
Color.INTENSE_YELLOW: "ansibrightyellow",
}
@pytest.mark.parametrize(
"name, exp",
[
("RESET", "noinherit"),
("RED", "ansired"),
("BACKGROUND_RED", "bg:ansired"),
("BACKGROUND_INTENSE_RED", "bg:ansibrightred"),
("BOLD_RED", "bold ansired"),
("UNDERLINE_RED", "underline ansired"),
("BOLD_UNDERLINE_RED", "bold underline ansired"),
("UNDERLINE_BOLD_RED", "underline bold ansired"),
# test unsupported modifiers
("BOLD_FAINT_RED", "bold ansired"),
("BOLD_SLOWBLINK_RED", "bold ansired"),
("BOLD_FASTBLINK_RED", "bold ansired"),
("BOLD_INVERT_RED", "bold ansired"),
("BOLD_CONCEAL_RED", "bold ansired"),
("BOLD_STRIKETHROUGH_RED", "bold ansired"),
# test hexes
("#000", "#000"),
("#000000", "#000000"),
("BACKGROUND_#000", "bg:#000"),
("BACKGROUND_#000000", "bg:#000000"),
("BG#000", "bg:#000"),
("bg#000000", "bg:#000000"),
],
)
def test_color_name_to_pygments_code(name, exp):
styles = DEFAULT_STYLES.copy()
obs = color_name_to_pygments_code(name, styles)
assert obs == exp
@pytest.mark.parametrize(
"name, exp",
[
("RESET", "noinherit"),
("RED", "ansired"),
("BACKGROUND_RED", "bg:ansired"),
("BACKGROUND_INTENSE_RED", "bg:ansibrightred"),
("BOLD_RED", "bold ansired"),
("UNDERLINE_RED", "underline ansired"),
("BOLD_UNDERLINE_RED", "bold underline ansired"),
("UNDERLINE_BOLD_RED", "underline bold ansired"),
# test unsupported modifiers
("BOLD_FAINT_RED", "bold ansired"),
("BOLD_SLOWBLINK_RED", "bold ansired"),
("BOLD_FASTBLINK_RED", "bold ansired"),
("BOLD_INVERT_RED", "bold ansired"),
("BOLD_CONCEAL_RED", "bold ansired"),
("BOLD_STRIKETHROUGH_RED", "bold ansired"),
# test hexes
("#000", "#000"),
("#000000", "#000000"),
("BACKGROUND_#000", "bg:#000"),
("BACKGROUND_#000000", "bg:#000000"),
("BG#000", "bg:#000"),
("bg#000000", "bg:#000000"),
],
)
def test_code_by_name(name, exp):
styles = DEFAULT_STYLES.copy()
obs = code_by_name(name, styles)
assert obs == exp
@pytest.mark.parametrize(
"in_tuple, exp_ct, exp_ansi_colors",
[
(("RESET",), Color.RESET, "noinherit"),
(("GREEN",), Color.GREEN, "ansigreen"),
(("BOLD_RED",), Color.BOLD_RED, "bold ansired"),
(
("BACKGROUND_BLACK", "BOLD_GREEN"),
Color.BACKGROUND_BLACK__BOLD_GREEN,
"bg:ansiblack bold ansigreen",
),
],
)
def test_color_token_by_name(in_tuple, exp_ct, exp_ansi_colors, xs_LS_COLORS):
from xonsh.pyghooks import XonshStyle, color_token_by_name
xs = XonshStyle()
styles = xs.styles
ct = color_token_by_name(in_tuple, styles)
ansi_colors = styles[ct] # if keyerror, ct was not cached
assert ct == exp_ct, "returned color token is right"
assert ansi_colors == exp_ansi_colors, "color token mapped to correct color string"
def test_XonshStyle_init_file_color_tokens(xs_LS_COLORS, monkeypatch):
keys = list(file_color_tokens)
for n in keys:
monkeypatch.delitem(file_color_tokens, n)
xs = XonshStyle()
assert xs.styles
assert type(file_color_tokens) is dict
assert set(file_color_tokens.keys()) == set(xs_LS_COLORS.env["LS_COLORS"].keys())
# parameterized tests for file colorization
# note 'ca' is checked by standalone test.
# requires privilege to create a file with capabilities
if ON_WINDOWS:
# file coloring support is very limited on Windows, only test the cases we can easily make work
# If you care about file colors, use Windows Subsystem for Linux, or another OS.
_cf = {
"fi": "regular",
"di": "simple_dir",
"ln": "sym_link",
"pi": None,
"so": None,
"do": None,
# bug ci failures: 'bd': '/dev/sda',
# bug ci failures:'cd': '/dev/tty',
"or": "orphan",
"mi": None, # never used
"su": None,
"sg": None,
"ca": None, # Separate special case test,
"tw": None,
"ow": None,
"st": None,
"ex": None, # executable is a filetype test on Windows.
"*.emf": "foo.emf",
"*.zip": "foo.zip",
"*.ogg": "foo.ogg",
"mh": "hard_link",
}
else:
# full-fledged, VT100 based infrastructure
_cf = {
"fi": "regular",
"di": "simple_dir",
"ln": "sym_link",
"pi": "pipe",
"so": None,
"do": None,
# bug ci failures: 'bd': '/dev/sda',
# bug ci failures:'cd': '/dev/tty',
"or": "orphan",
"mi": None, # never used
"su": "set_uid",
"sg": "set_gid",
"ca": None, # Separate special case test,
"tw": "sticky_ow_dir",
"ow": "other_writable_dir",
"st": "sticky_dir",
"ex": "executable",
"*.emf": "foo.emf",
"*.zip": "foo.zip",
"*.ogg": "foo.ogg",
"mh": "hard_link",
}
@pytest.fixture(scope="module")
def colorizable_files():
"""populate temp dir with sample files.
(too hard to emit indivual test cases when fixture invoked in mark.parametrize)"""
with TemporaryDirectory() as tempdir:
for k, v in _cf.items():
if v is None:
continue
if v.startswith("/"):
file_path = v
else:
file_path = tempdir + "/" + v
try:
os.lstat(file_path)
except FileNotFoundError:
if file_path.endswith("_dir"):
os.mkdir(file_path)
else:
open(file_path, "a").close()
if k in ("di", "fi"):
pass
elif k == "ex":
os.chmod(file_path, stat.S_IRWXU) # tmpdir on windows need u+w
elif k == "ln": # cook ln test case.
os.chmod(file_path, stat.S_IRWXU) # link to *executable* file
os.rename(file_path, file_path + "_target")
os.symlink(file_path + "_target", file_path)
elif k == "or":
os.rename(file_path, file_path + "_target")
os.symlink(file_path + "_target", file_path)
os.remove(file_path + "_target")
elif k == "pi": # not on Windows
os.remove(file_path)
os.mkfifo(file_path)
elif k == "su":
os.chmod(file_path, stat.S_ISUID)
elif k == "sg":
os.chmod(file_path, stat.S_ISGID)
elif k == "st":
os.chmod(
file_path, stat.S_ISVTX | stat.S_IRUSR | stat.S_IWUSR
) # TempDir requires o:r
elif k == "tw":
os.chmod(
file_path,
stat.S_ISVTX | stat.S_IWOTH | stat.S_IRUSR | stat.S_IWUSR,
)
elif k == "ow":
os.chmod(file_path, stat.S_IWOTH | stat.S_IRUSR | stat.S_IWUSR)
elif k == "mh":
os.rename(file_path, file_path + "_target")
os.link(file_path + "_target", file_path)
else:
pass # cauterize those elseless ifs!
os.symlink(file_path, file_path + "_symlink")
yield tempdir
pass # tempdir get cleaned up here.
@pytest.mark.parametrize(
"key,file_path",
[(key, file_path) for key, file_path in _cf.items() if file_path],
)
def test_colorize_file(key, file_path, colorizable_files, xs_LS_COLORS):
"""test proper file codes with symlinks colored normally"""
ffp = colorizable_files + "/" + file_path
stat_result = os.lstat(ffp)
color_token, color_key = color_file(ffp, stat_result)
assert color_key == key, "File classified as expected kind"
assert color_token == file_color_tokens[key], "Color token is as expected"
@pytest.mark.parametrize(
"key,file_path",
[(key, file_path) for key, file_path in _cf.items() if file_path],
)
def test_colorize_file_symlink(key, file_path, colorizable_files, xs_LS_COLORS):
"""test proper file codes with symlinks colored target."""
xs_LS_COLORS.env["LS_COLORS"]["ln"] = "target"
ffp = colorizable_files + "/" + file_path + "_symlink"
stat_result = os.lstat(ffp)
assert stat.S_ISLNK(stat_result.st_mode)
_, color_key = color_file(ffp, stat_result)
try:
tar_stat_result = os.stat(ffp) # stat the target of the link
tar_ffp = str(pathlib.Path(ffp).resolve())
_, tar_color_key = color_file(tar_ffp, tar_stat_result)
if tar_color_key.startswith("*"):
tar_color_key = (
"fi" # all the *.* zoo, link is colored 'fi', not target type.
)
except FileNotFoundError: # orphan symlinks always colored 'or'
tar_color_key = "or" # Fake if for missing file
assert color_key == tar_color_key, "File classified as expected kind, via symlink"
import xonsh.lazyimps
def test_colorize_file_ca(xs_LS_COLORS, monkeypatch):
def mock_os_listxattr(*args, **kwards):
return ["security.capability"]
monkeypatch.setattr(xonsh.pyghooks, "os_listxattr", mock_os_listxattr)
with TemporaryDirectory() as tmpdir:
file_path = tmpdir + "/cap_file"
open(file_path, "a").close()
os.chmod(
file_path, stat.S_IRWXU
) # ca overrides ex, leave file deletable on Windows
color_token, color_key = color_file(file_path, os.lstat(file_path))
assert color_key == "ca"
@pytest.mark.parametrize(
"name, styles, refrules",
[
("test1", {}, {}), # empty styles
(
"test2",
{Token.Literal.String.Single: "#ff0000"},
{Token.Literal.String.Single: "#ff0000"},
), # Token
(
"test3",
{"Token.Literal.String.Single": "#ff0000"},
{Token.Literal.String.Single: "#ff0000"},
), # str key
(
"test4",
{"Literal.String.Single": "#ff0000"},
{Token.Literal.String.Single: "#ff0000"},
), # short str key
(
"test5",
{"completion-menu.completion.current": "#00ff00"},
{Token.PTK.CompletionMenu.Completion.Current: "#00ff00"},
), # ptk style
(
"test6",
{"RED": "#ff0000"},
{Token.Color.RED: "#ff0000"},
), # short color name
],
)
def test_register_custom_pygments_style(name, styles, refrules):
register_custom_pygments_style(name, styles)
style = get_style_by_name(name)
# registration succeeded
assert style is not None
# check rules
for rule, color in refrules.items():
assert rule in style.styles
assert style.styles[rule] == color