xonsh/tests/test_tools.py
Andy Kipp bb394a8e84
feat: add superhelp and additional context via new FuncAlias (#5366)
### Goals

* Make callable aliases transparent.
* Catch errors in callable aliases and show the name of the source.
* Show additional attributes: thredable, capturable.
* Closes #5266

## Exception

### Before

```xsh
aliases['cd']
# <function xonsh.dirstack.cd>

aliases['trace']
# <function xonsh.aliases.trace>

aliases['null'] = lambda: 1/0
null
# ZeroDivisionError: division by zero

@aliases.register('catch')
@aliases.register('me')
@aliases.register('if')
@aliases.register('you')
@aliases.register('can')
def _exc(args, stdin, stdout):
    for line in stdin.readlines():
        print(line.strip() + '!', file=stdout, flush=True)
    return 1/0 if 'i' in $__ALIAS_NAME else 0

echo hey | catch | me | if | you | can
# ZeroDivisionError: division by zero      <--- ???
# hey!!!!!
```

### After

```xsh 
aliases['cd']
# FuncAlias({'name': 'cd', 'func': 'cd'})

aliases['trace']
# FuncAlias({'name': 'trace', 'func': 'trace', '__xonsh_threadable__': False})

$XONSH_SHOW_TRACEBACK=False
$RAISE_SUBPROC_ERROR = False
aliases['null'] = lambda: 1/0
null
#Exception in thread {'cls': 'ProcProxyThread', 'name': 'Thread-15', 'func': FuncAlias({'name': 'null', 'func': '<lambda>'}), 'alias': 'null', 'pid': None}
#ZeroDivisionError: division by zero



@aliases.register('catch')
@aliases.register('me')
@aliases.register('if')
@aliases.register('you')
@aliases.register('can')
def _exc(args, stdin, stdout):
    for line in stdin.readlines():
        print(line.strip() + '!', file=stdout, flush=True)
    return 1/0 if 'i' in $__ALIAS_NAME else 0
echo hey | catch | me | if | you | can
# Exception in thread {'cls': 'ProcProxyThread', 'name': 'Thread-8', 'func': FuncAlias({'name': 'if', 'func': '_exc'}), 'alias': 'if', 'pid': None}
# ZeroDivisionError: division by zero
# hey!!!!!
```

## Superhelp

### Before
```xsh
@aliases.register("hello")
def _alias_hello():
    """Show world."""
    print('world')

hello?
# No manual entry for hello
```

### After
```xsh
@aliases.register("hello")
def _alias_hello():
    """Show world."""
    print('world')

hello?
# FuncAlias({'name': 'hello', 'func': '_alias_hello'}):
# Show world.
```



## For community
⬇️ **Please click the 👍 reaction instead of leaving a `+1` or 👍
comment**

---------

Co-authored-by: a <1@1.1>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2024-05-13 09:11:58 -04:00

2138 lines
53 KiB
Python

"""Tests xonsh tools."""
import datetime as dt
import os
import pathlib
import re
import stat
import subprocess
import warnings
from tempfile import TemporaryDirectory
import pytest
from xonsh import __version__
from xonsh.lexer import Lexer
from xonsh.platform import HAS_PYGMENTS, ON_WINDOWS, PYTHON_VERSION_INFO
from xonsh.pytest.tools import skip_if_on_windows
from xonsh.tools import (
EnvPath,
all_permutations,
always_false,
always_true,
argvquote,
balanced_parens,
bool_or_int_to_str,
bool_or_none_to_str,
bool_to_str,
check_for_partial_string,
check_quotes,
deprecated,
dynamic_cwd_tuple_to_str,
ends_with_colon_token,
ensure_slice,
ensure_string,
ensure_timestamp,
env_path_to_str,
escape_windows_cmd_string,
executables_in,
expand_case_matching,
expand_path,
expandvars,
find_next_break,
get_line_continuation,
get_logical_line,
get_portions,
iglobpath,
is_balanced,
is_bool,
is_bool_or_int,
is_bool_or_none,
is_callable,
is_completion_mode,
is_completions_display_value,
is_dynamic_cwd_width,
is_env_path,
is_float,
is_int,
is_int_as_str,
is_logfile_opt,
is_nonstring_seq_of_strings,
is_path,
is_regex,
is_slice_as_str,
is_string,
is_string_or_callable,
is_string_seq,
is_tok_color_dict,
is_writable_file,
logfile_opt_to_str,
path_to_str,
pathsep_to_seq,
pathsep_to_set,
pathsep_to_upper_seq,
print_exception,
register_custom_style,
replace_logical_line,
seq_to_pathsep,
seq_to_upper_pathsep,
set_to_pathsep,
simple_random_choice,
str_to_env_path,
str_to_path,
subexpr_before_unbalanced,
subexpr_from_unbalanced,
subproc_toks,
swap_values,
to_bool,
to_bool_or_int,
to_bool_or_none,
to_completion_mode,
to_completions_display_value,
to_dynamic_cwd_tuple,
to_int_or_none,
to_logfile_opt,
)
LEXER = Lexer()
LEXER.build()
INDENT = " "
TOOLS_ENV = {"EXPAND_ENV_VARS": True, "XONSH_ENCODING_ERRORS": "strict"}
ENCODE_ENV_ONLY = {"XONSH_ENCODING_ERRORS": "strict"}
PATHEXT_ENV = {"PATHEXT": [".COM", ".EXE", ".BAT"]}
def test_random_choice():
lst = [1, 2, 3]
r = simple_random_choice(lst)
assert r in lst
with pytest.raises(ValueError):
simple_random_choice(range(1010101))
def test_subproc_toks_x():
exp = "![x]"
obs = subproc_toks("x", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_ls_l():
exp = "![ls -l]"
obs = subproc_toks("ls -l", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_git():
s = 'git commit -am "hello doc"'
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_git_semi():
s = 'git commit -am "hello doc"'
exp = f"![{s}];"
obs = subproc_toks(s + ";", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_git_nl():
s = 'git commit -am "hello doc"'
exp = f"![{s}]\n"
obs = subproc_toks(s + "\n", lexer=LEXER, returnline=True)
assert exp == obs
def test_bash_macro():
s = "bash -c ! export var=42; echo $var"
exp = f"![{s}]\n"
obs = subproc_toks(s + "\n", lexer=LEXER, returnline=True)
assert exp == obs
def test_python_macro():
s = 'python -c ! import os; print(os.path.abspath("/"))'
exp = f"![{s}]\n"
obs = subproc_toks(s + "\n", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_indent_ls():
s = "ls -l"
exp = INDENT + f"![{s}]"
obs = subproc_toks(INDENT + s, mincol=len(INDENT), lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_indent_ls_nl():
s = "ls -l"
exp = INDENT + f"![{s}]\n"
obs = subproc_toks(
INDENT + s + "\n", mincol=len(INDENT), lexer=LEXER, returnline=True
)
assert exp == obs
def test_subproc_toks_indent_ls_no_min():
s = "ls -l"
exp = INDENT + f"![{s}]"
obs = subproc_toks(INDENT + s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_indent_ls_no_min_nl():
s = "ls -l"
exp = INDENT + f"![{s}]\n"
obs = subproc_toks(INDENT + s + "\n", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_indent_ls_no_min_semi():
s = "ls"
exp = INDENT + f"![{s}];"
obs = subproc_toks(INDENT + s + ";", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_indent_ls_no_min_semi_nl():
s = "ls"
exp = INDENT + f"![{s}];\n"
obs = subproc_toks(INDENT + s + ";\n", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_ls_comment():
s = "ls -l"
com = " # lets list"
exp = f"![{s}]{com}"
obs = subproc_toks(s + com, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_ls_42_comment():
s = "ls 42"
com = " # lets list"
exp = f"![{s}]{com}"
obs = subproc_toks(s + com, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_ls_str_comment():
s = 'ls "wakka"'
com = " # lets list"
exp = f"![{s}]{com}"
obs = subproc_toks(s + com, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_indent_ls_comment():
ind = " "
s = "ls -l"
com = " # lets list"
exp = f"{ind}![{s}]{com}"
obs = subproc_toks(ind + s + com, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_indent_ls_str():
ind = " "
s = 'ls "wakka"'
com = " # lets list"
exp = f"{ind}![{s}]{com}"
obs = subproc_toks(ind + s + com, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_ls_l_semi_ls_first():
lsdl = "ls -l"
ls = "ls"
s = f"{lsdl}; {ls}"
exp = f"![{lsdl}]; {ls}"
obs = subproc_toks(s, lexer=LEXER, maxcol=6, returnline=True)
assert exp == obs
def test_subproc_toks_ls_l_semi_ls_second():
lsdl = "ls -l"
ls = "ls"
s = f"{lsdl}; {ls}"
exp = f"{lsdl}; ![{ls}]"
obs = subproc_toks(s, lexer=LEXER, mincol=7, returnline=True)
assert exp == obs
def test_subproc_toks_hello_mom_first():
fst = "echo 'hello'"
sec = "echo 'mom'"
s = f"{fst}; {sec}"
exp = f"![{fst}]; {sec}"
obs = subproc_toks(s, lexer=LEXER, maxcol=len(fst) + 1, returnline=True)
assert exp == obs
def test_subproc_toks_hello_mom_second():
fst = "echo 'hello'"
sec = "echo 'mom'"
s = f"{fst}; {sec}"
exp = f"{fst}; ![{sec}]"
obs = subproc_toks(s, lexer=LEXER, mincol=len(fst), returnline=True)
assert exp == obs
def test_subproc_toks_hello_bad_leading_single_quotes():
obs = subproc_toks('echo "hello', lexer=LEXER, returnline=True)
assert obs is None
def test_subproc_toks_hello_bad_trailing_single_quotes():
obs = subproc_toks('echo hello"', lexer=LEXER, returnline=True)
assert obs is None
def test_subproc_toks_hello_bad_leading_triple_quotes():
obs = subproc_toks('echo """hello', lexer=LEXER, returnline=True)
assert obs is None
def test_subproc_toks_hello_bad_trailing_triple_quotes():
obs = subproc_toks('echo hello"""', lexer=LEXER, returnline=True)
assert obs is None
def test_subproc_toks_hello_mom_triple_quotes_nl():
s = 'echo """hello\nmom"""'
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_comment():
exp = None
obs = subproc_toks("# I am a comment", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_not():
exp = "not ![echo mom]"
obs = subproc_toks("not echo mom", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_paren():
exp = "(![echo mom])"
obs = subproc_toks("(echo mom)", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_paren_ws():
exp = "(![echo mom]) "
obs = subproc_toks("(echo mom) ", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_not_paren():
exp = "not (![echo mom])"
obs = subproc_toks("not (echo mom)", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_and_paren():
exp = "True and (![echo mom])"
obs = subproc_toks("True and (echo mom)", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_paren_and_paren():
exp = "(![echo a]) and (echo b)"
obs = subproc_toks("(echo a) and (echo b)", maxcol=9, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_semicolon_only():
exp = None
obs = subproc_toks(";", lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_pyeval():
s = "echo @(1+1)"
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_pyeval_multiline_string():
s = 'echo @("""hello\nmom""")'
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_twopyeval():
s = "echo @(1+1) @(40 + 2)"
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_pyeval_parens():
s = "echo @(1+1)"
inp = f"({s})"
exp = f"(![{s}])"
obs = subproc_toks(inp, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_twopyeval_parens():
s = "echo @(1+1) @(40+2)"
inp = f"({s})"
exp = f"(![{s}])"
obs = subproc_toks(inp, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_pyeval_nested():
s = "echo @(min(1, 42))"
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
@pytest.mark.parametrize(
"phrase",
[
"xandy",
"xory",
"xand",
"andy",
"xor",
"ory",
"x-and",
"x-or",
"and-y",
"or-y",
"x-and-y",
"x-or-y",
"in/and/path",
"in/or/path",
],
)
def test_subproc_toks_and_or(phrase):
s = "echo " + phrase
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_pyeval_nested_parens():
s = "echo @(min(1, 42))"
inp = f"({s})"
exp = f"(![{s}])"
obs = subproc_toks(inp, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_capstdout():
s = "echo $(echo bat)"
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_capproc():
s = "echo !(echo bat)"
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_pyeval_redirect():
s = 'echo @("foo") > bar'
inp = f"{s}"
exp = f"![{s}]"
obs = subproc_toks(inp, lexer=LEXER, returnline=True)
assert exp == obs
def test_subproc_toks_greedy_parens():
s = "(sort)"
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True, greedy=True)
assert exp == obs
def test_subproc_toks_greedy_parens_inp():
s = "(sort) < input.txt"
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True, greedy=True)
assert exp == obs
def test_subproc_toks_greedy_parens_statements():
s = '(echo "abc"; sleep 1; echo "def")'
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True, greedy=True)
assert exp == obs
def test_subproc_toks_greedy_parens_statements_with_grep():
s = '(echo "abc"; sleep 1; echo "def") | grep'
exp = f"![{s}]"
obs = subproc_toks(s, lexer=LEXER, returnline=True, greedy=True)
assert exp == obs
LOGICAL_LINE_CASES = [
("""x = 14 + 2""", 0, "x = 14 + 2", 1),
(
"""x = \\
14 \\
+ 2
""",
0,
"x = 14 + 2",
3,
),
(
"""y = 16
14 \\
+ 2
""",
1,
"14 + 2",
2,
),
(
'''x = """wow
mom"""
''',
0,
'x = """wow\nmom"""',
2,
),
# test from start
(
"echo --option1 value1 \\\n"
" --option2 value2 \\\n"
" --optionZ valueZ",
0,
"echo --option1 value1 --option2 value2 --optionZ valueZ",
3,
),
# test from second line
(
"echo --option1 value1 \\\n"
" --option2 value2 \\\n"
" --optionZ valueZ",
1,
"echo --option1 value1 --option2 value2 --optionZ valueZ",
3,
),
('"""\n', 0, '"""', 1),
]
@pytest.mark.parametrize("src, idx, exp_line, exp_n", LOGICAL_LINE_CASES)
def test_get_logical_line(src, idx, exp_line, exp_n, xession):
lines = src.splitlines()
line, n, start = get_logical_line(lines, idx)
assert exp_line == line
assert exp_n == n
@pytest.mark.parametrize("src, idx, exp_line, exp_n", LOGICAL_LINE_CASES)
def test_replace_logical_line(src, idx, exp_line, exp_n, xession):
lines = src.splitlines()
logical = exp_line
while idx > 0 and lines[idx - 1].endswith("\\"):
idx -= 1
replace_logical_line(lines, logical, idx, exp_n)
exp = src.replace("\\\n", "").strip()
lc = get_line_continuation() + "\n"
obs = "\n".join(lines).replace(lc, "").strip()
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("f(1,10),x.y", True),
('"x"', True),
("'y'", True),
('b"x"', True),
("r'y'", True),
("f'z'", True),
('"""hello\nmom"""', True),
],
)
def test_check_quotes(inp, exp):
obs = check_quotes(inp)
assert exp is obs
@pytest.mark.parametrize("inp", ["f(1,10),x.y"])
def test_is_balanced_parens(inp):
obs = is_balanced(inp, "(", ")")
assert obs
@pytest.mark.parametrize("inp", ["f(x.", "f(1,x." "f((1,10),x.y"])
def test_is_not_balanced_parens(inp):
obs = is_balanced(inp, "(", ")")
assert not obs
@pytest.mark.parametrize(
"inp, exp", [("f(x.", "x."), ("f(1,x.", "x."), ("f((1,10),x.y", "x.y")]
)
def test_subexpr_from_unbalanced_parens(inp, exp):
obs = subexpr_from_unbalanced(inp, "(", ")")
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("f(x.", "f"),
("f(1,x.", "f"),
("f((1,10),x.y", "f"),
("wakka().f((1,10),x.y", ".f"),
("wakka(f((1,10),x.y", "f"),
("wakka(jawakka().f((1,10),x.y", ".f"),
("wakka(jawakka().f((1,10),x.y)", "wakka"),
],
)
def test_subexpr_before_unbalanced_parens(inp, exp):
obs = subexpr_before_unbalanced(inp, "(", ")")
assert exp == obs
@pytest.mark.parametrize(
"line, exp",
[
("", True),
("wakka jawaka", True),
("rm *; echo hello world", True),
("()", True),
("f()", True),
("echo * yo ; echo eggs", True),
("(", False),
(")", False),
("(cmd;", False),
("cmd;)", False),
],
)
def test_balanced_parens(line, exp):
obs = balanced_parens(line, lexer=LEXER)
if exp:
assert obs
else:
assert not obs
@pytest.mark.parametrize(
"line, exp",
[
("if 1:", True),
("elif 2: #comment", True),
("elif 3: #colon comment:", True),
("else: ", True),
("for s in '#not-a-comment':", True),
("", False),
("#comment", False),
("#colon comment:", False),
("print('hello')", False),
("print('hello') #colon comment:", False),
],
)
def test_ends_with_colon_token(line, exp):
obs = ends_with_colon_token(line, lexer=LEXER)
if exp:
assert obs
else:
assert not obs
@pytest.mark.parametrize(
"line, mincol, exp",
[
("ls && echo a", 0, 4),
("ls && echo a", 6, None),
("ls && echo a || echo b", 6, 14),
("(ls) && echo a", 1, 4),
("not ls && echo a", 0, 8),
("not (ls) && echo a", 0, 8),
("bash -c ! export var=42; echo $var", 0, 35),
('python -c ! import os; print(os.path.abspath("/"))', 0, 51),
("echo * yo ; echo eggs", 0, 11),
],
)
def test_find_next_break(line, mincol, exp):
obs = find_next_break(line, mincol=mincol, lexer=LEXER)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(42, True),
(42.0, False),
("42", False),
("42.0", False),
([42], False),
([], False),
(None, False),
("", False),
],
)
def test_is_int(inp, exp):
obs = is_int(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(42.0, True),
(42.000101010010101010101001010101010001011100001101101011100, True),
(42, False),
("42", False),
("42.0", False),
([42], False),
([], False),
(None, False),
("", False),
(False, False),
(True, False),
],
)
def test_is_float(inp, exp):
obs = is_float(inp)
assert exp == obs
def test_is_string_true():
assert is_string("42.0")
def test_is_string_false():
assert not is_string(42.0)
def test_is_callable_true():
assert is_callable(lambda: 42.0)
def test_is_callable_false():
assert not is_callable(42.0)
@pytest.mark.parametrize("inp", ["42.0", lambda: 42.0])
def test_is_string_or_callable_true(inp):
assert is_string_or_callable(inp)
def test_is_string_or_callable_false():
assert not is_string(42.0)
@pytest.mark.parametrize("inp", [42, "42"])
def test_always_true(inp):
assert always_true(inp)
@pytest.mark.parametrize("inp,exp", [(42, 42), ("42", 42), ("None", None)])
def test_to_optional_int(inp, exp):
assert to_int_or_none(inp) == exp
@pytest.mark.parametrize("inp", [42, "42"])
def test_always_false(inp):
assert not always_false(inp)
@pytest.mark.parametrize("inp, exp", [(42, "42"), ("42", "42")])
def test_ensure_string(inp, exp):
obs = ensure_string(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("", set()),
("a", {"a"}),
(os.pathsep.join(["a", "b"]), {"a", "b"}),
(os.pathsep.join(["a", "b", "c"]), {"a", "b", "c"}),
],
)
def test_pathsep_to_set(inp, exp):
obs = pathsep_to_set(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(set(), ""),
({"a"}, "a"),
({"a", "b"}, os.pathsep.join(["a", "b"])),
({"a", "b", "c"}, os.pathsep.join(["a", "b", "c"])),
],
)
def test_set_to_pathsep(inp, exp):
obs = set_to_pathsep(inp, sort=(len(inp) > 1))
assert exp == obs
@pytest.mark.parametrize("inp", ["42.0", ["42.0"]])
def test_is_string_seq_true(inp):
assert is_string_seq(inp)
def test_is_string_seq_false():
assert not is_string_seq([42.0])
def test_is_nonstring_seq_of_strings_true():
assert is_nonstring_seq_of_strings(["42.0"])
def test_is_nonstring_seq_of_strings_false():
assert not is_nonstring_seq_of_strings([42.0])
@pytest.mark.parametrize(
"inp, exp",
[
("", []),
("a", ["a"]),
(os.pathsep.join(["a", "b"]), ["a", "b"]),
(os.pathsep.join(["a", "b", "c"]), ["a", "b", "c"]),
],
)
def test_pathsep_to_seq(inp, exp):
obs = pathsep_to_seq(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
([], ""),
(["a"], "a"),
(["a", "b"], os.pathsep.join(["a", "b"])),
(["a", "b", "c"], os.pathsep.join(["a", "b", "c"])),
],
)
def test_seq_to_pathsep(inp, exp):
obs = seq_to_pathsep(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("", []),
("a", ["A"]),
(os.pathsep.join(["a", "B"]), ["A", "B"]),
(os.pathsep.join(["A", "b", "c"]), ["A", "B", "C"]),
],
)
def test_pathsep_to_upper_seq(inp, exp):
obs = pathsep_to_upper_seq(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
([], ""),
(["a"], "A"),
(["a", "b"], os.pathsep.join(["A", "B"])),
(["a", "B", "c"], os.pathsep.join(["A", "B", "C"])),
],
)
def test_seq_to_upper_pathsep(inp, exp):
obs = seq_to_upper_pathsep(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp", [(pathlib.Path("/home/wakka"), True), ("/home/jawaka", False)]
)
def test_is_path(inp, exp):
obs = is_path(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("/home/wakka", False),
(["/home/jawaka"], False),
(EnvPath(["/home/jawaka"]), True),
(EnvPath(["jawaka"]), True),
(EnvPath(b"jawaka:wakka"), True),
],
)
def test_is_env_path(inp, exp):
obs = is_env_path(inp)
assert exp == obs
@pytest.mark.parametrize("inp, exp", [("/tmp", pathlib.Path("/tmp")), ("", None)])
def test_str_to_path(inp, exp):
obs = str_to_path(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("/home/wakka", ["/home/wakka"]),
("/home/wakka" + os.pathsep + "/home/jawaka", ["/home/wakka", "/home/jawaka"]),
(b"/home/wakka", ["/home/wakka"]),
],
)
def test_str_to_env_path(inp, exp):
obs = str_to_env_path(inp)
assert exp == obs.paths
@pytest.mark.parametrize(
"inp, exp",
[
pytest.param(
pathlib.Path("///tmp"),
"/tmp",
marks=pytest.mark.skipif(
ON_WINDOWS and PYTHON_VERSION_INFO > (3, 11),
reason="Python 3.12 on windows changed its behavior of resolving additional slashes in paths",
),
),
],
)
def test_path_to_str(inp, exp):
obs = path_to_str(inp)
if ON_WINDOWS:
exp = exp.replace("/", "\\")
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(["/home/wakka"], "/home/wakka"),
(["/home/wakka", "/home/jawaka"], "/home/wakka" + os.pathsep + "/home/jawaka"),
],
)
def test_env_path_to_str(inp, exp):
obs = env_path_to_str(inp)
assert exp == obs
@pytest.mark.parametrize(
"left, right, exp",
[
(
EnvPath(["/home/wakka"]),
["/home/jawaka"],
EnvPath(["/home/wakka", "/home/jawaka"]),
),
(["a"], EnvPath(["b"]), EnvPath(["a", "b"])),
(EnvPath(["c"]), EnvPath(["d"]), EnvPath(["c", "d"])),
],
)
def test_env_path_add(left, right, exp):
obs = left + right
assert is_env_path(obs)
assert exp == obs
def test_env_path_add_replace_no_dupes_front_replace_existing():
# Test replaces without dupes when added to front when adding existing entry
path = EnvPath(
[os.sep.join(["home", "wakka"]), os.sep.join(["home", "wakka", "bin"])]
)
path.add(os.sep.join(["home", "wakka", "bin"]), front=True, replace=True)
assert path == [
os.sep.join(["home", "wakka", "bin"]),
os.sep.join(["home", "wakka"]),
]
def test_env_path_add_replace_no_dupes_front_replace_multiple():
# Test replaces without dupes when added to front when multiple existing occurrences
path = EnvPath(
[
os.sep.join(["home", "wakka"]),
os.sep.join(["home", "wakka", "bin"]),
os.sep.join(["home", "wakka", "bin"]),
]
)
path.add(os.sep.join(["home", "wakka", "bin"]), front=True, replace=True)
assert path == [
os.sep.join(["home", "wakka", "bin"]),
os.sep.join(["home", "wakka"]),
]
def test_env_path_add_replace_no_dupes_back_replace_multiple():
# Test replaces without dupes when not added to front
path = EnvPath(
[
os.sep.join(["home", "wakka"]),
os.sep.join(["home", "wakka", "bin"]),
os.sep.join(["home", "wakka", "bin"]),
]
)
path.add(os.sep.join(["home", "wakka", "bin"]), front=False, replace=True)
assert path == [
os.sep.join(["home", "wakka"]),
os.sep.join(["home", "wakka", "bin"]),
]
def test_env_path_add_pathlib():
os.sep.join(["home", "wakka", "bin"])
path = EnvPath(
[
os.sep.join(["home", "wakka"]),
os.sep.join(["home", "wakka", "bin"]),
os.sep.join(["home", "wakka", "bin"]),
]
)
path.add(
pathlib.Path(os.sep.join(["home", "wakka", "bin"])),
front=False,
replace=True,
)
assert path == [
os.sep.join(["home", "wakka"]),
os.sep.join(["home", "wakka", "bin"]),
]
def test_env_path_append_remove_pathlib_path():
path = EnvPath()
# Append-remove
path.append(os.sep.join(["home", "dino"]))
path.remove(os.sep.join(["home", "dino"]))
path.append(os.sep.join(["~", "dino"]))
path.remove(pathlib.Path(os.sep.join(["~", "dino"])))
path.append(pathlib.Path(os.sep.join(["~", "dino"])))
path.remove(pathlib.Path(os.sep.join(["~", "dino"])))
path.append(pathlib.Path(os.sep.join(["~", "dino"])))
path.remove(os.sep.join(["~", "dino"]))
path.append(
pathlib.Path(os.sep.join([str(pathlib.Path("~").expanduser()), "dino"]))
)
path.remove(os.sep.join(["~", "dino"]))
path.append(pathlib.Path(os.sep.join(["~", "dino"])))
path.remove(os.sep.join([str(pathlib.Path("~").expanduser()), "dino"]))
# Insert-remove
path.insert(0, os.sep.join(["home", "dino"]))
path.remove(os.sep.join(["home", "dino"]))
path.insert(0, os.sep.join(["~", "dino"]))
path.remove(pathlib.Path(os.sep.join(["~", "dino"])))
path.insert(0, pathlib.Path(os.sep.join(["~", "dino"])))
path.remove(pathlib.Path(os.sep.join(["~", "dino"])))
path.prepend(pathlib.Path(os.sep.join(["~", "dino"])))
path.remove(os.sep.join(["~", "dino"]))
assert path == []
# helper
def expand(path):
return os.path.expanduser(os.path.expandvars(path))
@pytest.mark.parametrize("env", [TOOLS_ENV, ENCODE_ENV_ONLY])
@pytest.mark.parametrize(
"inp, exp",
[
("xonsh_dir", "xonsh_dir"),
(".", "."),
("../", "../"),
("~/", "~/"),
(b"~/../", "~/../"),
],
)
def test_env_path_getitem(inp, exp, xession, env):
xession.env = env
obs = EnvPath(inp)[0] # call to __getitem__
if env.get("EXPAND_ENV_VARS"):
assert expand(exp) == obs
else:
assert exp == obs
@pytest.mark.parametrize("env", [TOOLS_ENV, ENCODE_ENV_ONLY])
@pytest.mark.parametrize(
"inp, exp",
[
(
os.pathsep.join(["xonsh_dir", "../", ".", "~/"]),
["xonsh_dir", "../", ".", "~/"],
),
(
"/home/wakka" + os.pathsep + "/home/jakka" + os.pathsep + "~/",
["/home/wakka", "/home/jakka", "~/"],
),
],
)
def test_env_path_multipath(inp, exp, xession, env):
# cases that involve path-separated strings
xession.env = env
if env == TOOLS_ENV:
obs = [i for i in EnvPath(inp)]
assert [expand(i) for i in exp] == obs
else:
obs = [i for i in EnvPath(inp)]
assert [i for i in exp] == obs
@pytest.mark.parametrize(
"inp, exp",
[
(pathlib.Path("/home/wakka"), ["/home/wakka".replace("/", os.sep)]),
(pathlib.Path("~/"), ["~"]),
(pathlib.Path("."), ["."]),
(
["/home/wakka", pathlib.Path("/home/jakka"), "~/"],
["/home/wakka", "/home/jakka".replace("/", os.sep), "~/"],
),
(["/home/wakka", pathlib.Path("../"), "../"], ["/home/wakka", "..", "../"]),
(["/home/wakka", pathlib.Path("~/"), "~/"], ["/home/wakka", "~", "~/"]),
],
)
def test_env_path_with_pathlib_path_objects(inp, exp, xession):
xession.env = TOOLS_ENV
# iterate over EnvPath to acquire all expanded paths
obs = [i for i in EnvPath(inp)]
assert [expand(i) for i in exp] == obs
@pytest.mark.parametrize("inp", ["42.0", [42.0]])
def test_is_nonstring_seq_of_strings_false1(inp):
assert not is_nonstring_seq_of_strings(inp)
# helper
def mkpath(*paths):
"""Build os-dependent paths properly."""
return os.sep + os.sep.join(paths)
@pytest.mark.parametrize(
"inp, exp",
[
(
[mkpath("home", "wakka"), mkpath("home", "jakka"), mkpath("home", "yakka")],
[mkpath("home", "wakka"), mkpath("home", "jakka")],
)
],
)
def test_env_path_slice_get_all_except_last_element(inp, exp):
obs = EnvPath(inp)[:-1]
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(
[mkpath("home", "wakka"), mkpath("home", "jakka"), mkpath("home", "yakka")],
[mkpath("home", "jakka"), mkpath("home", "yakka")],
)
],
)
def test_env_path_slice_get_all_except_first_element(inp, exp):
obs = EnvPath(inp)[1:]
assert exp == obs
@pytest.mark.parametrize(
"inp, exp_a, exp_b",
[
(
[
mkpath("home", "wakka"),
mkpath("home", "jakka"),
mkpath("home", "yakka"),
mkpath("home", "takka"),
],
[mkpath("home", "wakka"), mkpath("home", "yakka")],
[mkpath("home", "jakka"), mkpath("home", "takka")],
)
],
)
def test_env_path_slice_path_with_step(inp, exp_a, exp_b):
obs_a = EnvPath(inp)[0::2]
assert exp_a == obs_a
obs_b = EnvPath(inp)[1::2]
assert exp_b == obs_b
@pytest.mark.parametrize(
"inp, exp",
[
(
[
mkpath("home", "wakka"),
mkpath("home", "xakka"),
mkpath("other", "zakka"),
mkpath("another", "akka"),
mkpath("home", "bakka"),
],
[mkpath("other", "zakka"), mkpath("another", "akka")],
)
],
)
def test_env_path_keep_only_non_home_paths(inp, exp):
obs = EnvPath(inp)[2:4]
assert exp == obs
@pytest.mark.parametrize("inp", [True, False])
def test_is_bool_true(inp):
assert is_bool(inp)
@pytest.mark.parametrize("inp", [1, "yooo hooo!"])
def test_is_bool_false(inp):
assert not is_bool(inp)
@pytest.mark.parametrize(
"inp, exp",
[
(True, True),
(False, False),
(None, False),
("", False),
("0", False),
("False", False),
("NONE", False),
("TRUE", True),
("1", True),
(0, False),
(1, True),
],
)
def test_to_bool(inp, exp):
obs = to_bool(inp)
assert exp == obs
@pytest.mark.parametrize("inp, exp", [(True, "1"), (False, "")])
def test_bool_to_str(inp, exp):
assert bool_to_str(inp) == exp
@pytest.mark.parametrize(
"inp, exp",
[(True, True), (False, True), (1, True), (0, True), ("Yolo", False), (1.0, False)],
)
def test_is_bool_or_int(inp, exp):
obs = is_bool_or_int(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(True, True),
(False, False),
(1, 1),
(0, 0),
("", False),
(0.0, False),
(1.0, True),
("T", True),
("f", False),
("0", 0),
("10", 10),
],
)
def test_to_bool_or_int(inp, exp):
obs = to_bool_or_int(inp)
assert exp == obs
@pytest.mark.parametrize("inp, exp", [(True, "1"), (False, ""), (1, "1"), (0, "0")])
def test_bool_or_int_to_str(inp, exp):
obs = bool_or_int_to_str(inp)
assert exp == obs
@pytest.mark.parametrize("inp", [True, False, None])
def test_is_bool_or_none_true(inp):
assert is_bool_or_none(inp)
@pytest.mark.parametrize("inp", [1, "yooo hooo!"])
def test_is_bool_or_none_false(inp):
assert not is_bool_or_none(inp)
@pytest.mark.parametrize(
"inp, exp",
[
(True, True),
(False, False),
(None, None),
("", False),
("0", False),
("False", False),
("NONE", None),
("TRUE", True),
("1", True),
(0, False),
(1, True),
],
)
def test_to_bool_or_none(inp, exp):
obs = to_bool_or_none(inp)
assert exp == obs
@pytest.mark.parametrize("inp, exp", [(True, "1"), (False, ""), (None, "None")])
def test_bool_or_none_to_str(inp, exp):
assert bool_or_none_to_str(inp) == exp
@pytest.mark.parametrize(
"inp, exp",
[
(42, slice(42, 43)),
(0, slice(0, 1)),
(None, slice(None, None, None)),
(slice(1, 2), slice(1, 2)),
("-1", slice(-1, None, None)),
("42", slice(42, 43)),
("-42", slice(-42, -41)),
("1:2:3", slice(1, 2, 3)),
("1::3", slice(1, None, 3)),
(":", slice(None, None, None)),
("1:", slice(1, None, None)),
("[1:2:3]", slice(1, 2, 3)),
("(1:2:3)", slice(1, 2, 3)),
((4, 8, 10), slice(4, 8, 10)),
([10, 20], slice(10, 20)),
],
)
def test_ensure_slice(inp, exp):
obs = ensure_slice(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
((range(50), slice(25, 40)), list(i for i in range(25, 40))),
(
([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [slice(1, 4), slice(6, None)]),
[2, 3, 4, 7, 8, 9, 10],
),
(([1, 2, 3, 4, 5], [slice(-2, None), slice(-5, -3)]), [4, 5, 1, 2]),
],
)
def test_get_portions(inp, exp):
obs = get_portions(*inp)
assert list(obs) == exp
@pytest.mark.parametrize(
"inp",
[
"42.3",
"3:asd5:1",
"test",
"6.53:100:5",
"4:-",
"2:15-:3",
"50:-:666",
object(),
[1, 5, 3, 4],
("foo"),
],
)
def test_ensure_slice_invalid(inp):
with pytest.raises(ValueError):
ensure_slice(inp)
@pytest.mark.parametrize(
"inp, exp",
[
("42", True),
("42.0", False),
(42, False),
([42], False),
([], False),
(None, False),
("", False),
(False, False),
(True, False),
],
)
def test_is_int_as_str(inp, exp):
obs = is_int_as_str(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("20", False),
("20%", False),
((20, "c"), False),
((20.0, "m"), False),
((20.0, "c"), True),
((20.0, "%"), True),
],
)
def test_is_dynamic_cwd_width(inp, exp):
obs = is_dynamic_cwd_width(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(42, False),
(None, False),
("42", False),
("-42", False),
(slice(1, 2, 3), False),
([], False),
(False, False),
(True, False),
("1:2:3", True),
("1::3", True),
("1:", True),
(":", True),
("[1:2:3]", True),
("(1:2:3)", True),
("r", False),
("r:11", False),
],
)
def test_is_slice_as_str(inp, exp):
obs = is_slice_as_str(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("throwback.log", True),
("", True),
(None, True),
(True, False),
(False, False),
(42, False),
([1, 2, 3], False),
((1, 2), False),
(("wrong", "parameter"), False),
pytest.param("/dev/null", True, marks=skip_if_on_windows),
],
)
def test_is_logfile_opt(inp, exp):
obs = is_logfile_opt(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(True, None),
(False, None),
(1, None),
(None, None),
("throwback.log", "throwback.log"),
pytest.param("/dev/null", "/dev/null", marks=skip_if_on_windows),
pytest.param(
"/dev/nonexistent_dev",
(
"/dev/nonexistent_dev"
if is_writable_file("/dev/nonexistent_dev")
else None
),
marks=skip_if_on_windows,
),
("~/log", os.path.expanduser("~/log")),
],
)
def test_to_logfile_opt(inp, exp):
obs = to_logfile_opt(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
(None, ""),
("", ""),
("throwback.log", "throwback.log"),
("/dev/null", "/dev/null"),
],
)
def test_logfile_opt_to_str(inp, exp):
obs = logfile_opt_to_str(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("20", (20.0, "c")),
("20%", (20.0, "%")),
((20, "c"), (20.0, "c")),
((20, "%"), (20.0, "%")),
((20.0, "c"), (20.0, "c")),
((20.0, "%"), (20.0, "%")),
("inf", (float("inf"), "c")),
],
)
def test_to_dynamic_cwd_tuple(inp, exp):
obs = to_dynamic_cwd_tuple(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[((20.0, "c"), "20.0"), ((20.0, "%"), "20.0%"), ((float("inf"), "c"), "inf")],
)
def test_dynamic_cwd_tuple_to_str(inp, exp):
obs = dynamic_cwd_tuple_to_str(inp)
assert exp == obs
@pytest.mark.parametrize(
"st, esc",
[
("", ""),
("foo", "foo"),
("foo&bar", "foo^&bar"),
('foo$?-/_"\\', 'foo$?-/_^"\\'),
("^&<>|", "^^^&^<^>^|"),
("()<>", "^(^)^<^>"),
],
)
def test_escape_windows_cmd_string(st, esc):
obs = escape_windows_cmd_string(st)
assert esc == obs
@pytest.mark.parametrize(
"st, esc, forced",
[
("", '""', None),
("foo", "foo", '"foo"'),
(
r'arg1 "hallo, "world"" "\some\path with\spaces")',
r'"arg1 \"hallo, \"world\"\" \"\some\path with\spaces\")"',
None,
),
(
r'"argument"2" argument3 argument4',
r'"\"argument\"2\" argument3 argument4"',
None,
),
(r'"\foo\bar bar\foo\" arg', r'"\"\foo\bar bar\foo\\\" arg"', None),
(
r"\\machine\dir\file.bat",
r"\\machine\dir\file.bat",
r'"\\machine\dir\file.bat"',
),
(
r'"\\machine\dir space\file.bat"',
r'"\"\\machine\dir space\file.bat\""',
None,
),
],
)
def test_argvquote(st, esc, forced):
obs = argvquote(st)
assert esc == obs
if forced is None:
forced = esc
obs = argvquote(st, force=True)
assert forced == obs
@pytest.mark.parametrize("inp", ["no string here", ""])
def test_partial_string_none(inp):
assert check_for_partial_string(inp) == (None, None, None)
@pytest.mark.parametrize(
"leaders", [(("", 0), ("not empty", 9)), (("not empty", 9), ("", 0))]
)
@pytest.mark.parametrize("prefix", ["b", "rb", "r"])
@pytest.mark.parametrize("quote", ['"', '"""'])
def test_partial_string(leaders, prefix, quote):
(l, l_len), (f, f_len) = leaders
s = prefix + quote
t = s + "test string" + quote
t_len = len(t)
# single string
test_string = l + t + f
obs = check_for_partial_string(test_string)
exp = l_len, l_len + t_len, s
assert obs == exp
# single partial
test_string = l + f + s + "test string"
obs = check_for_partial_string(test_string)
exp = l_len + f_len, None, s
assert obs == exp
# two strings
test_string = l + t + f + l + t + f
obs = check_for_partial_string(test_string)
exp = (l_len + t_len + f_len + l_len), (l_len + t_len + f_len + l_len + t_len), s
assert obs == exp
# one string, one partial
test_string = l + t + f + l + s + "test string"
obs = check_for_partial_string(test_string)
exp = l_len + t_len + f_len + l_len, None, s
assert obs == exp
def test_executables_in(xession):
expected = set()
types = ("file", "directory", "brokensymlink")
if ON_WINDOWS:
# Don't test symlinks on windows since it requires admin
types = ("file", "directory")
executables = (True, False)
with TemporaryDirectory() as test_path:
for _type in types:
for executable in executables:
fname = f"{_type}_{executable}"
if _type == "none":
continue
if _type == "file" and executable:
ext = ".exe" if ON_WINDOWS else ""
expected.add(fname + ext)
else:
ext = ""
path = os.path.join(test_path, fname + ext)
if _type == "file":
with open(path, "w") as f:
f.write(fname)
elif _type == "directory":
os.mkdir(path)
elif _type == "brokensymlink":
tmp_path = os.path.join(test_path, "i_wont_exist")
with open(tmp_path, "w") as f:
f.write("deleteme")
os.symlink(tmp_path, path)
os.remove(tmp_path)
if executable and not _type == "brokensymlink":
os.chmod(path, stat.S_IXUSR | stat.S_IRUSR | stat.S_IWUSR)
if ON_WINDOWS:
xession.env = PATHEXT_ENV
result = set(executables_in(test_path))
else:
result = set(executables_in(test_path))
assert expected == result
@pytest.mark.parametrize(
"inp, exp",
[
("yo", "[Yy][Oo]"),
("[a-f]123e", "[a-f]123[Ee]"),
("${HOME}/yo", "${HOME}/[Yy][Oo]"),
("./yo/mom", "./[Yy][Oo]/[Mm][Oo][Mm]"),
("Eßen", "[Ee][Ss]?[Ssß][Ee][Nn]"),
],
)
def test_expand_case_matching(inp, exp):
obs = expand_case_matching(inp)
assert exp == obs
@pytest.mark.parametrize(
"inp, exp",
[
("foo", "foo"),
("$foo $bar", "bar $bar"),
("$unk $foo $bar", "$unk bar $bar"),
("$foobar", "$foobar"),
("$foo $spam", "bar eggs"),
("$unk $foo $spam", "$unk bar eggs"),
("$unk $foo $unk $spam $unk", "$unk bar $unk eggs $unk"),
("$an_int$spam$a_bool", "42eggsTrue"),
("$unk$an_int$spam$a_bool", "$unk42eggsTrue"),
("bar$foo$spam$foo $an_int $none", "barbareggsbar 42 "),
("$unk bar$foo$spam$foo $an_int $none", "$unk barbareggsbar 42 "),
("$foo/bar", "bar/bar"),
("$unk/$foo/bar", "$unk/bar/bar"),
("${'foo'} $spam", "bar eggs"),
("$unk ${'unk'} ${'foo'} $spam", "$unk ${'unk'} bar eggs"),
("${'foo'} ${'a_bool'}", "bar True"),
("${'foo'}bar", "barbar"),
("${'foo'}/bar", "bar/bar"),
("${'unk'}/${'foo'}/bar", "${'unk'}/bar/bar"),
("${\"foo'}", "${\"foo'}"),
("$?bar", "$?bar"),
("$foo}bar", "bar}bar"),
("${'foo", "${'foo"),
(b"foo", "foo"),
(b"$foo bar", "bar bar"),
(b"$unk $foo bar", "$unk bar bar"),
(b"${'foo'}bar", "barbar"),
(b"${'unk'}${'foo'}bar", "${'unk'}barbar"),
],
)
def test_expandvars(inp, exp, xession):
"""Tweaked for xonsh cases from CPython `test_genericpath.py`"""
xession.env.update(
dict({"foo": "bar", "spam": "eggs", "a_bool": True, "an_int": 42, "none": None})
)
assert expandvars(inp) == exp
@pytest.mark.parametrize(
"inp, fmt, exp",
[
(572392800.0, None, 572392800.0),
("42.1459", None, 42.1459),
(
dt.datetime(2016, 8, 2, 13, 24),
None,
dt.datetime(2016, 8, 2, 13, 24).timestamp(),
),
("2016-8-10 16:14", None, dt.datetime(2016, 8, 10, 16, 14).timestamp()),
(
"2016/8/10 16:14:40",
"%Y/%m/%d %H:%M:%S",
dt.datetime(2016, 8, 10, 16, 14, 40).timestamp(),
),
],
)
def test_ensure_timestamp(inp, fmt, exp, xession):
xession.env["XONSH_DATETIME_FORMAT"] = "%Y-%m-%d %H:%M"
obs = ensure_timestamp(inp, fmt)
assert exp == obs
@pytest.mark.parametrize("expand_user", [True, False])
@pytest.mark.parametrize(
"inp, expand_env_vars, exp_end",
[
("~/test.txt", True, "/test.txt"),
("~/$foo", True, "/bar"),
("~/test/$a_bool", True, "/test/True"),
("~/test/$an_int", True, "/test/42"),
("~/test/$none", True, "/test/"),
("~/$foo", False, "/$foo"),
],
)
def test_expand_path(expand_user, inp, expand_env_vars, exp_end, xession):
if os.sep != "/":
inp = inp.replace("/", os.sep)
exp_end = exp_end.replace("/", os.sep)
xession.env.update({"foo": "bar", "a_bool": True, "an_int": 42, "none": None})
xession.env["EXPAND_ENV_VARS"] = expand_env_vars
path = expand_path(inp, expand_user=expand_user)
if expand_user:
home_path = os.path.expanduser("~")
assert path == home_path + exp_end
else:
assert path == "~" + exp_end
def test_swap_values():
orig = {"x": 1}
updates = {"x": 42, "y": 43}
with swap_values(orig, updates):
assert orig["x"] == 42
assert orig["y"] == 43
assert orig["x"] == 1
assert "y" not in orig
@pytest.mark.parametrize(
"arguments, expected_docstring",
[
(
{"deprecated_in": "0.5.10", "removed_in": "0.6.0"},
"my_function has been deprecated in version 0.5.10 and will be removed "
"in version 0.6.0",
),
(
{"deprecated_in": "0.5.10"},
"my_function has been deprecated in version 0.5.10",
),
(
{"removed_in": "0.6.0"},
"my_function has been deprecated and will be removed in version 0.6.0",
),
({}, "my_function has been deprecated"),
],
)
def test_deprecated_docstrings_with_empty_docstring(arguments, expected_docstring):
@deprecated(**arguments)
def my_function():
pass
assert my_function.__doc__ == expected_docstring
@pytest.mark.parametrize(
"arguments, expected_docstring",
[
(
{"deprecated_in": "0.5.10", "removed_in": "0.6.0"},
"Does nothing.\n\nmy_function has been deprecated in version 0.5.10 and "
"will be removed in version 0.6.0",
),
(
{"deprecated_in": "0.5.10"},
"Does nothing.\n\nmy_function has been deprecated in version 0.5.10",
),
(
{"removed_in": "0.6.0"},
"Does nothing.\n\nmy_function has been deprecated and will be removed "
"in version 0.6.0",
),
({}, "Does nothing.\n\nmy_function has been deprecated"),
],
)
def test_deprecated_docstrings_with_nonempty_docstring(arguments, expected_docstring):
@deprecated(**arguments)
def my_function():
"""Does nothing."""
pass
assert my_function.__doc__ == expected_docstring
def test_deprecated_warning_raised():
@deprecated()
def my_function():
pass
with warnings.catch_warnings(record=True) as warning:
warnings.simplefilter("always")
my_function()
assert issubclass(warning.pop().category, DeprecationWarning)
def test_deprecated_warning_contains_message():
@deprecated()
def my_function():
pass
with warnings.catch_warnings(record=True) as warning:
warnings.simplefilter("always")
my_function()
assert str(warning.pop().message) == "my_function has been deprecated"
@pytest.mark.parametrize("expired_version", ["0.1.0", __version__])
def test_deprecated_past_expiry_raises_assertion_error(expired_version):
@deprecated(removed_in=expired_version)
def my_function():
pass
with pytest.raises(AssertionError):
my_function()
@skip_if_on_windows
def test_iglobpath_no_dotfiles(xession):
d = os.path.dirname(__file__)
g = d + "/*"
files = list(iglobpath(g, include_dotfiles=False))
assert d + "/.somedotfile" not in files
@skip_if_on_windows
def test_iglobpath_dotfiles(xession):
d = os.path.dirname(__file__)
g = d + "/*"
files = list(iglobpath(g, include_dotfiles=True))
assert d + "/.somedotfile" in files
@skip_if_on_windows
def test_iglobpath_no_dotfiles_recursive(xession):
d = os.path.dirname(__file__)
g = d + "/**"
files = list(iglobpath(g, include_dotfiles=False))
assert d + "/bin/.someotherdotfile" not in files
@skip_if_on_windows
def test_iglobpath_dotfiles_recursive(xession):
d = os.path.dirname(__file__)
g = d + "/**"
files = list(iglobpath(g, include_dotfiles=True))
assert d + "/bin/.someotherdotfile" in files
def test_iglobpath_empty_str(monkeypatch, xession):
# makes sure that iglobpath works, even when os.scandir() and os.listdir()
# fail to return valid results, like an empty filename
def mockscandir(path):
yield ""
if hasattr(os, "scandir"):
monkeypatch.setattr(os, "scandir", mockscandir)
def mocklistdir(path):
return [""]
monkeypatch.setattr(os, "listdir", mocklistdir)
paths = list(iglobpath("some/path"))
assert len(paths) == 0
def test_all_permutations():
obs = {"".join(p) for p in all_permutations("ABC")}
exp = {
"A",
"B",
"C",
"AB",
"AC",
"BA",
"BC",
"CA",
"CB",
"ACB",
"CBA",
"BAC",
"CAB",
"BCA",
"ABC",
}
assert obs == exp
@pytest.mark.parametrize(
"name, styles, refrules",
[
("test1", {}, {}), # empty styles
(
"test2",
{"Token.Literal.String.Single": "#ff0000"},
{"Token.Literal.String.Single": "#ff0000"},
), # str key
(
"test3",
{"Literal.String.Single": "#ff0000"},
{"Token.Literal.String.Single": "#ff0000"},
), # short str key
(
"test4",
{"RED": "#ff0000"},
{"Token.Color.RED": "#ff0000"},
), # color
],
)
def test_register_custom_style(name, styles, refrules):
style = register_custom_style(name, styles)
if HAS_PYGMENTS:
assert style is not None
for rule, color in style.styles.items():
if str(rule) in refrules:
assert refrules[str(rule)] == color
@pytest.mark.parametrize(
"val, exp",
[
("default", True),
("menu-complete", True),
("def", False),
("xonsh", False),
("men", False),
],
)
def test_is_completion_mode(val, exp):
assert is_completion_mode(val) is exp
@pytest.mark.parametrize(
"val, exp",
[
("", "default"),
(None, "default"),
("default", "default"),
("DEfaULT", "default"),
("m", "menu-complete"),
("mEnu_COMPlete", "menu-complete"),
("menu-complete", "menu-complete"),
],
)
def test_to_completion_mode(val, exp):
assert to_completion_mode(val) == exp
@pytest.mark.parametrize(
"val",
[
"de",
"defa_ult",
"men_",
"menu_",
],
)
def test_to_completion_mode_fail(val):
with pytest.warns(RuntimeWarning):
obs = to_completion_mode(val)
assert obs == "default"
@pytest.mark.parametrize(
"val, exp",
[
("none", True),
("single", True),
("multi", True),
("", False),
(None, False),
("argle", False),
],
)
def test_is_completions_display_value(val, exp):
assert is_completions_display_value(val) == exp
@pytest.mark.parametrize(
"val, exp",
[
("none", "none"),
(False, "none"),
("false", "none"),
("single", "single"),
("readline", "readline"), # todo: check this
("multi", "multi"),
(True, "multi"),
("TRUE", "multi"),
],
)
def test_to_completions_display_value(val, exp):
assert to_completions_display_value(val) == exp
@pytest.mark.parametrize("val", [1, "", "argle"])
def test_to_completions_display_value_fail(val):
with pytest.warns(RuntimeWarning):
obs = to_completions_display_value(val)
assert obs == "multi"
def test_is_regex_true():
assert is_regex("cat")
def test_is_regex_false():
assert not is_regex("**")
from xonsh.style_tools import Token
@pytest.mark.parametrize(
"val, exp",
[
(
{
Token.Literal.String: "bold ansigreen",
"Token.Name.Tag": "underline ansiblue",
},
True,
),
(
{
Token.Literal.String: "bold ansigreen",
1: "bold ansigreen",
},
False,
),
({1: "bold ansigreen"}, False),
(
{Token.Literal.String: "123"},
False,
),
(
{"Token.Name.Tag": 123},
False,
),
],
)
def test_is_tok_color_dict(val, exp):
assert is_tok_color_dict(val) == exp
def test_print_exception_msg(xession, capsys):
xession.env["COLOR_INPUT"] = False
try:
a = 1 / 0
a += 1
except ZeroDivisionError:
print_exception(msg="MSG")
cap = capsys.readouterr()
assert cap.err.endswith("MSG\n"), f"captured_stderr = {cap.captured_stderr!r}"
def test_print_exception_error(xession, capsys):
xession.env["COLOR_INPUT"] = False
with xession.env.swap(XONSH_SHOW_TRACEBACK=False):
try:
raise subprocess.CalledProcessError(1, ["ls", "nofile"], output="nooutput")
except subprocess.CalledProcessError:
print_exception(msg="MSG")
cap = capsys.readouterr()
match = "subprocess.CalledProcessError: Command .* returned non-zero exit status .*\nMSG\n"
assert re.match(
match,
cap.err,
re.MULTILINE | re.DOTALL,
), f"\nAssert: {cap.err!r},\nexpected: {match!r}"
with xession.env.swap(XONSH_SHOW_TRACEBACK=True):
try:
raise subprocess.CalledProcessError(1, ["ls", "nofile"], output="nooutput")
except subprocess.CalledProcessError:
print_exception(msg="MSG")
cap = capsys.readouterr()
match = ".*Traceback.*subprocess.CalledProcessError: Command .* returned non-zero exit status .*MSG\n"
assert re.match(
match,
cap.err,
re.MULTILINE | re.DOTALL,
), f"\nAssert: {cap.err!r},\nexpected {match!r}"