Skip to content

Commit bf3f720

Browse files
committed
pytest: rewrite in bash, support toplevel funcs, avoid nondef ones and classes
1 parent 436b0cb commit bf3f720

File tree

4 files changed

+76
-25
lines changed

4 files changed

+76
-25
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ repos:
6464
- id: mypy
6565
args: [--config-file=test/setup.cfg]
6666
# Intentionally not run on helpers/python (support very old versions)
67-
exclude: completions/
67+
exclude: completions/|test/fixtures/pytest/
6868

6969
- repo: https://github.com/asottile/pyupgrade
7070
rev: v2.7.2

completions/pytest

+26-19
Original file line numberDiff line numberDiff line change
@@ -93,29 +93,36 @@ _pytest()
9393

9494
if [[ $cur == *.py::*:* ]]; then
9595
local file=${cur/.py:*/.py}
96-
local class=${cur#*.py::}
96+
local class=${cur#*.py::} in_class=false
97+
local line
9798
class=${class%%:*}
98-
local -a methods=(
99-
"$(awk "
100-
/^[ \t]*class[ \t]+${class}[ \t:(]/ {flag=1; next}
101-
\$1 == \"class\" {flag=0}
102-
flag && \$1 == \"def\" {
103-
sub(\"\\\(.*\",\"\",\$2); print \$2
104-
}
105-
flag && \$1 == \"async\" && \$2 == \"def\" {
106-
sub(\"\\\(.*\",\"\",\$3); print \$3
107-
}
108-
" <"$file")")
109-
COMPREPLY=($(compgen -P "$file::$class::" -W "${methods[@]}" -- "${cur##*:?(:)}"))
99+
while IFS= read -r line; do
100+
if [[ $line =~ ^class[[:space:]]+${class}[[:space:]:\(] ]]; then
101+
in_class=true
102+
elif [[ $line =~ ^class[[:space:]] ]]; then
103+
in_class=false
104+
fi
105+
if $in_class && [[ $line =~ ^[[:space:]]+(async[[:space:]]+)?def[[:space:]]+(test_[A-Za-z0-9_]+) ]]; then
106+
COMPREPLY+=(${BASH_REMATCH[2]})
107+
fi
108+
done 2>/dev/null <"$file"
109+
((!${#COMPREPLY[@]})) ||
110+
COMPREPLY=($(compgen -P "$file::$class::" -W '${COMPREPLY[@]}' \
111+
-- "${cur##*:?(:)}"))
110112
__ltrim_colon_completions "$cur"
111113
return
112114
elif [[ $cur == *.py:* ]]; then
113-
local file="${cur/.py:*/.py}"
114-
local -a classes=(
115-
"$(awk '$1 == "class" {
116-
sub("[:(].*","",$2); print $2
117-
}' 2>/dev/null <"$file")")
118-
COMPREPLY=($(compgen -P "$file::" -W "${classes[@]}" -- "${cur##*.py:?(:)}"))
115+
local file="${cur/.py:*/.py}" line
116+
while IFS= read -r line; do
117+
if [[ $line =~ ^class[[:space:]]+(Test[A-Za-z0-9_]+) ]]; then
118+
COMPREPLY+=(${BASH_REMATCH[1]})
119+
elif [[ $line =~ ^(async[[:space:]]+)?def[[:space:]]+(test_[A-Za-z0-9_]+) ]]; then
120+
COMPREPLY+=(${BASH_REMATCH[2]})
121+
fi
122+
done 2>/dev/null <"$file"
123+
((!${#COMPREPLY[@]})) ||
124+
COMPREPLY=($(compgen -P "$file::" -W '${COMPREPLY[@]}' \
125+
-- "${cur##*.py:?(:)}"))
119126
__ltrim_colon_completions "$cur"
120127
return
121128
fi

test/fixtures/pytest/test_async.py

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""Async function pytest completion fixture."""
2+
3+
4+
async def test_positive():
5+
pass
6+
7+
8+
async def non_test_negative():
9+
pass
10+
11+
12+
class Testing:
13+
async def test_positive(self):
14+
pass
15+
16+
async def non_test_negative(self):
17+
pass

test/t/test_pytest.py

+32-5
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,38 @@ def test_2(self, completion):
1313
assert completion
1414

1515
@pytest.mark.complete("pytest ../t/test_pytest.py:")
16-
def test_classes(self, completion):
17-
assert completion == ":TestPytest"
16+
def test_classes_and_functions(self, completion):
17+
assert completion == ":TestPytest :test_function_canary".split()
1818

1919
@pytest.mark.complete("pytest ../t/test_pytest.py::TestPytest::")
2020
def test_class_methods(self, completion):
21-
methods = inspect.getmembers(self, predicate=inspect.ismethod)
22-
assert len(completion) == len(methods)
23-
assert completion == [x[0] for x in methods]
21+
methods = [
22+
x[0]
23+
for x in inspect.getmembers(self, predicate=inspect.ismethod)
24+
if x[0].startswith("test_")
25+
]
26+
assert completion == methods
27+
28+
@pytest.mark.complete("pytest pytest/test_async.py:")
29+
def test_classes_and_async_functions(self, completion):
30+
assert completion == ":Testing :test_positive".split()
31+
32+
@pytest.mark.complete("pytest pytest/test_async.py::Testing::")
33+
def test_async_class_methods(self, completion):
34+
assert completion == "test_positive"
35+
36+
def non_test_cananary_method(self):
37+
pass
38+
39+
40+
def test_function_canary():
41+
pass
42+
43+
44+
def non_test_canary():
45+
pass
46+
47+
48+
class NonTestCanaryClass:
49+
def test_is_this_function_not(self):
50+
pass

0 commit comments

Comments
 (0)