mirror of
https://github.com/davidhalter/jedi.git
synced 2025-12-07 06:24:27 +08:00
175 lines
5.3 KiB
Python
Executable File
175 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python
|
|
"""
|
|
|jedi| is mostly being tested by what I would call "Blackbox Tests". These
|
|
tests are just testing the interface and do input/output testing. This makes a
|
|
lot of sense for |jedi|. Jedi supports so many different code structures, that
|
|
it is just stupid to write 200'000 unittests in the manner of
|
|
``regression.py``. Also, it is impossible to do doctests/unittests on most of
|
|
the internal data structures. That's why |jedi| uses mostly these kind of
|
|
tests.
|
|
|
|
There are different kind of tests:
|
|
|
|
- complete / definitions ``#?``
|
|
- goto: ``#!``
|
|
- related names: ``#<``
|
|
|
|
How to run tests?
|
|
+++++++++++++++++
|
|
|
|
Basically ``run.py`` searches the ``completion`` directory for files with lines
|
|
starting with the symbol above. There is also support for third party
|
|
libraries. In a normal test run (``./run.py``) they are not being executed, you
|
|
have to provide a ``--thirdparty`` option.
|
|
|
|
Now it's much more important, that you know how test only one file (``./run.py
|
|
classes``, where ``classes`` is the name of the file to test) or even one test
|
|
(``./run.py classes 90``, which would just execute the test on line 90).
|
|
|
|
If you want to debug a test, just use the --debug option.
|
|
|
|
Auto-Completion
|
|
+++++++++++++++
|
|
|
|
Uses comments to specify a test in the next line. The comment says, which
|
|
results are expected. The comment always begins with `#?`. The last row
|
|
symbolizes the cursor.
|
|
|
|
For example::
|
|
|
|
#? ['real']
|
|
a = 3; a.rea
|
|
|
|
Because it follows ``a.rea`` and a is an ``int``, which has a ``real``
|
|
property.
|
|
|
|
Definition
|
|
++++++++++
|
|
|
|
Definition tests use the same symbols like completion tests. This is
|
|
possible because the completion tests are defined with a list::
|
|
|
|
#? int()
|
|
ab = 3; ab
|
|
|
|
Goto
|
|
++++
|
|
|
|
Tests look like this::
|
|
|
|
abc = 1
|
|
#! ['abc=1']
|
|
abc
|
|
|
|
Additionally it is possible to add a number which describes to position of
|
|
the test (otherwise it's just end of line)::
|
|
|
|
#! 2 ['abc=1']
|
|
abc
|
|
|
|
Related Names
|
|
+++++++++++++
|
|
|
|
Tests look like this::
|
|
|
|
abc = 1
|
|
#< abc@1,0 abc@3,0
|
|
abc
|
|
"""
|
|
import os
|
|
import sys
|
|
import re
|
|
|
|
from . import base # required to setup import path
|
|
import jedi
|
|
from jedi._compatibility import unicode, StringIO, reduce, is_py25
|
|
|
|
|
|
sys.path.pop(0) # pop again, because it might affect the completion
|
|
|
|
|
|
TEST_COMPLETIONS = 0
|
|
TEST_DEFINITIONS = 1
|
|
TEST_ASSIGNMENTS = 2
|
|
TEST_USAGES = 3
|
|
|
|
|
|
class IntegrationTestCase(object):
|
|
|
|
def __init__(self, test_type, correct, line_nr, column, start, line,
|
|
path=None):
|
|
self.test_type = test_type
|
|
self.correct = correct
|
|
self.line_nr = line_nr
|
|
self.column = column
|
|
self.start = start
|
|
self.line = line
|
|
self.path = path
|
|
|
|
def __repr__(self):
|
|
name = os.path.basename(self.path) if self.path else None
|
|
return '<%s: %s:%s:%s>' % (self.__class__.__name__,
|
|
name, self.line_nr - 1, self.line.rstrip())
|
|
|
|
def script(self):
|
|
return jedi.Script(self.source, self.line_nr, self.column, self.path)
|
|
|
|
|
|
def collect_file_tests(lines, lines_to_execute):
|
|
makecase = lambda t: IntegrationTestCase(t, correct, line_nr, column,
|
|
start, line)
|
|
start = None
|
|
correct = None
|
|
test_type = None
|
|
for line_nr, line in enumerate(lines):
|
|
line_nr += 1 # py2.5 doesn't know about the additional enumerate param
|
|
line = unicode(line)
|
|
if correct:
|
|
r = re.match('^(\d+)\s*(.*)$', correct)
|
|
if r:
|
|
column = int(r.group(1))
|
|
correct = r.group(2)
|
|
start += r.regs[2][0] # second group, start index
|
|
else:
|
|
column = len(line) - 1 # -1 for the \n
|
|
if test_type == '!':
|
|
yield makecase(TEST_ASSIGNMENTS)
|
|
elif test_type == '<':
|
|
yield makecase(TEST_USAGES)
|
|
elif correct.startswith('['):
|
|
yield makecase(TEST_COMPLETIONS)
|
|
else:
|
|
yield makecase(TEST_DEFINITIONS)
|
|
correct = None
|
|
else:
|
|
try:
|
|
r = re.search(r'(?:^|(?<=\s))#([?!<])\s*([^\n]+)', line)
|
|
# test_type is ? for completion and ! for goto
|
|
test_type = r.group(1)
|
|
correct = r.group(2)
|
|
start = r.start()
|
|
except AttributeError:
|
|
correct = None
|
|
else:
|
|
# skip the test, if this is not specified test
|
|
if lines_to_execute and line_nr not in lines_to_execute:
|
|
correct = None
|
|
|
|
|
|
def collect_dir_tests(base_dir, test_files, thirdparty=False):
|
|
for f_name in os.listdir(base_dir):
|
|
files_to_execute = [a for a in test_files.items() if a[0] in f_name]
|
|
lines_to_execute = reduce(lambda x, y: x + y[1], files_to_execute, [])
|
|
if f_name.endswith(".py") and (not test_files or files_to_execute):
|
|
# for python2.5 certain tests are not being done, because it
|
|
# only has these features partially.
|
|
if is_py25 and f_name in ['generators.py', 'types.py']:
|
|
continue
|
|
path = os.path.join(base_dir, f_name)
|
|
source = open(path).read()
|
|
for case in collect_file_tests(StringIO(source),
|
|
lines_to_execute):
|
|
case.path = path
|
|
case.source = source
|
|
yield case
|