-
Notifications
You must be signed in to change notification settings - Fork 6
/
find_test.py
74 lines (58 loc) · 1.87 KB
/
find_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import re
# DEF_MATCH can be either 'def' or defining a 'class'!
DEF_MATCH = re.compile(r'(\s*)(?:def|async def|class)\s+(\w+)[(:)]')
CLASS_MATCH = re.compile(r'(\s*)class\s+(\w+)[(:)]')
NAME_MATCH = re.compile(r'(^[tT]est|.*[tT]ests?$)')
def get_test_under_cursor(text):
"""Given source code find the test above the cursor."""
lines = text.splitlines()
try:
indent, fnname = _find_test_def(lines)
except TypeError:
return None
if indent == '':
return fnname
try:
found = [fnname] + _find_class_ancestors(lines, indent)
except TypeError:
return None
return '::'.join(reversed(found))
def _find_test_def(lines):
"""Given lines of code return test name and its indentation.
**Mutates input lines**
"""
while True:
try:
line = lines.pop()
except IndexError:
return None
match = DEF_MATCH.match(line)
if match:
indent, fnname = match.group(1), match.group(2)
if NAME_MATCH.match(fnname):
return indent, fnname
def _find_class_ancestors(lines, indent):
"""Given lines find defining classes.
**Mutates input lines**
"""
found = []
while True:
try:
line = lines.pop()
except IndexError:
break
match = CLASS_MATCH.match(line)
if match:
curindent, clname = match.group(1), match.group(2)
if curindent < indent:
clname_lower = clname.lower()
if (clname_lower.startswith('test') or
clname_lower.endswith('test') or
clname_lower.endswith('tests')):
found.append(clname)
indent = curindent
else:
return None
if indent == '':
break
return found