huge functions refactoring

This commit is contained in:
David Halter
2012-09-15 15:44:51 +02:00
parent c3eab377f7
commit e184bb441c
4 changed files with 383 additions and 397 deletions

View File

@@ -12,8 +12,7 @@ import keywords
from _compatibility import next from _compatibility import next
__all__ = ['complete', 'goto', 'get_definition', 'related_names', __all__ = ['Script', 'NotFoundError', 'set_debug_function']
'NotFoundError', 'set_debug_function', 'get_in_function_call']
class NotFoundError(Exception): class NotFoundError(Exception):
@@ -133,16 +132,16 @@ class CallDef(object):
self.index) self.index)
def _get_completion_parts(path): class Script(object):
""" """ TODO doc """
Returns the parts for the completion def __init__(self, source, line, column, source_path):
:return: tuple - (path, dot, like) self.pos = line, column
""" self.module = modules.ModuleWithCursor(source_path, source=source,
match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S) position=self.pos)
return match.groups() self.parser = self.module.parser
self.source_path = source_path
def complete(self):
def complete(source, line, column, source_path):
""" """
An auto completer for python files. An auto completer for python files.
@@ -158,16 +157,14 @@ def complete(source, line, column, source_path):
:return: list of Completion objects. :return: list of Completion objects.
:rtype: list :rtype: list
""" """
pos = (line, column) path = self.module.get_path_until_cursor()
f = modules.ModuleWithCursor(source_path, source=source, position=pos) path, dot, like = self._get_completion_parts(path)
path = f.get_path_until_cursor()
path, dot, like = _get_completion_parts(path)
try: try:
scopes = _prepare_goto(pos, source_path, f, path, True) scopes = self._prepare_goto(path, True)
except NotFoundError: except NotFoundError:
scope_generator = evaluate.get_names_for_scope(f.parser.user_scope, scope_generator = evaluate.get_names_for_scope(self.parser.user_scope,
pos) self.pos)
completions = [] completions = []
for scope, name_list in scope_generator: for scope, name_list in scope_generator:
for c in name_list: for c in name_list:
@@ -194,18 +191,14 @@ def complete(source, line, column, source_path):
needs_dot = not dot and path needs_dot = not dot and path
c = [Completion(c, needs_dot, len(like), s) for c, s in set(completions)] c = [Completion(c, needs_dot, len(like), s) for c, s in set(completions)]
call_def = _get_in_function_call(f, pos)
_clear_caches() _clear_caches()
return c, call_def return c
def _prepare_goto(self, goto_path, is_like_search=False):
def _prepare_goto(position, source_path, module, goto_path, scope = self.parser.user_scope
is_like_search=False):
scope = module.parser.user_scope
debug.dbg('start: %s in %s' % (goto_path, scope)) debug.dbg('start: %s in %s' % (goto_path, scope))
user_stmt = module.parser.user_stmt user_stmt = self.parser.user_stmt
if not user_stmt and len(goto_path.split('\n')) > 1: if not user_stmt and len(goto_path.split('\n')) > 1:
# If the user_stmt is not defined and the goto_path is multi line, # If the user_stmt is not defined and the goto_path is multi line,
# something's strange. Most probably the backwards tokenizer matched to # something's strange. Most probably the backwards tokenizer matched to
@@ -219,25 +212,25 @@ def _prepare_goto(position, source_path, module, goto_path,
for i in import_names: for i in import_names:
for name_part in i.names: for name_part in i.names:
count += 1 count += 1
if position <= name_part.end_pos: if self.pos <= name_part.end_pos:
kill_count += 1 kill_count += 1
scopes = [imports.ImportPath(user_stmt, is_like_search, scopes = [imports.ImportPath(user_stmt, is_like_search,
kill_count=kill_count, direct_resolve=True)] kill_count=kill_count, direct_resolve=True)]
else: else:
# just parse one statement, take it and evaluate it # just parse one statement, take it and evaluate it
r = parsing.PyFuzzyParser(goto_path, source_path, no_docstr=True) r = parsing.PyFuzzyParser(goto_path, self.source_path, no_docstr=True)
try: try:
stmt = r.module.statements[0] stmt = r.module.statements[0]
except IndexError: except IndexError:
raise NotFoundError() raise NotFoundError()
stmt.start_pos = position stmt.start_pos = self.pos
stmt.parent = weakref.ref(scope) stmt.parent = weakref.ref(scope)
scopes = evaluate.follow_statement(stmt) scopes = evaluate.follow_statement(stmt)
return scopes return scopes
def get_definition(source, line, column, source_path): def get_definition(self):
""" """
Returns the definitions of a the path under the cursor. Returns the definitions of a the path under the cursor.
This is not a goto function! This follows complicated paths and returns the This is not a goto function! This follows complicated paths and returns the
@@ -262,35 +255,30 @@ def get_definition(source, line, column, source_path):
scopes.update(resolve_import_paths(set(s.follow()))) scopes.update(resolve_import_paths(set(s.follow())))
return scopes return scopes
pos = (line, column) goto_path = self.module.get_path_under_cursor()
f = modules.ModuleWithCursor(source_path, source=source, position=pos)
goto_path = f.get_path_under_cursor()
context = f.get_context() context = self.module.get_context()
if next(context) in ('class', 'def'): if next(context) in ('class', 'def'):
scopes = set([f.parser.user_scope]) scopes = set([self.module.parser.user_scope])
elif not goto_path: elif not goto_path:
op = f.get_operator_under_cursor() op = self.module.get_operator_under_cursor()
scopes = set([keywords.get_operator(op, pos)] if op else []) scopes = set([keywords.get_operator(op, self.pos)] if op else [])
else: else:
scopes = set(_prepare_goto(pos, source_path, f, goto_path)) scopes = set(self._prepare_goto(goto_path))
scopes = resolve_import_paths(scopes) scopes = resolve_import_paths(scopes)
# add keywords # add keywords
scopes |= keywords.get_keywords(string=goto_path, pos=pos) scopes |= keywords.get_keywords(string=goto_path, pos=self.pos)
d = set([Definition(s) for s in scopes]) d = set([Definition(s) for s in scopes])
_clear_caches() _clear_caches()
return sorted(d, key=lambda x: (x.module_path, x.start_pos)) return sorted(d, key=lambda x: (x.module_path, x.start_pos))
def goto(source, line, column, source_path): def goto(self):
pos = (line, column) goto_path = self.module.get_path_under_cursor()
f = modules.ModuleWithCursor(source_path, source=source, position=pos) goto_path, dot, search_name = self._get_completion_parts(goto_path)
goto_path = f.get_path_under_cursor()
goto_path, dot, search_name = _get_completion_parts(goto_path)
# define goto path the right way # define goto path the right way
if not dot: if not dot:
@@ -299,11 +287,11 @@ def goto(source, line, column, source_path):
else: else:
search_name_new = search_name search_name_new = search_name
context = f.get_context() context = self.module.get_context()
if next(context) in ('class', 'def'): if next(context) in ('class', 'def'):
definitions = set([f.parser.user_scope]) definitions = set([self.module.parser.user_scope])
else: else:
scopes = _prepare_goto(pos, source_path, f, goto_path) scopes = self._prepare_goto(goto_path)
definitions = evaluate.goto(scopes, search_name_new) definitions = evaluate.goto(scopes, search_name_new)
d = [Definition(d) for d in set(definitions)] d = [Definition(d) for d in set(definitions)]
@@ -311,18 +299,15 @@ def goto(source, line, column, source_path):
return sorted(d, key=lambda x: (x.module_path, x.start_pos)) return sorted(d, key=lambda x: (x.module_path, x.start_pos))
def related_names(source, line, column, source_path): def related_names(self):
""" """
Returns `dynamic.RelatedName` objects, which contain all names, that are Returns `dynamic.RelatedName` objects, which contain all names, that are
defined by the same variable, function, class or import. defined by the same variable, function, class or import.
This function can be used either to show all the usages of a variable or This function can be used either to show all the usages of a variable or
for renaming purposes. for renaming purposes.
""" """
pos = (line, column) goto_path = self.module.get_path_under_cursor()
f = modules.ModuleWithCursor(source_path, source=source, position=pos) goto_path, dot, search_name = self._get_completion_parts(goto_path)
goto_path = f.get_path_under_cursor()
goto_path, dot, search_name = _get_completion_parts(goto_path)
# define goto path the right way # define goto path the right way
if not dot: if not dot:
@@ -331,21 +316,21 @@ def related_names(source, line, column, source_path):
else: else:
search_name_new = search_name search_name_new = search_name
context = f.get_context() context = self.module.get_context()
if next(context) in ('class', 'def'): if next(context) in ('class', 'def'):
if isinstance(f.parser.user_scope, parsing.Function): if isinstance(self.module.parser.user_scope, parsing.Function):
e = evaluate.Function(f.parser.user_scope) e = evaluate.Function(self.module.parser.user_scope)
else: else:
e = evaluate.Class(f.parser.user_scope) e = evaluate.Class(self.module.parser.user_scope)
definitions = [e] definitions = [e]
elif isinstance(f.parser.user_stmt, (parsing.Param, parsing.Import)): elif isinstance(self.module.parser.user_stmt, (parsing.Param, parsing.Import)):
definitions = [f.parser.user_stmt] definitions = [self.module.parser.user_stmt]
else: else:
scopes = _prepare_goto(pos, source_path, f, goto_path) scopes = self._prepare_goto(goto_path)
definitions = evaluate.goto(scopes, search_name_new) definitions = evaluate.goto(scopes, search_name_new)
module = set([d.get_parent_until() for d in definitions]) module = set([d.get_parent_until() for d in definitions])
module.add(f.parser.module) module.add(self.module.parser.module)
names = dynamic.related_names(definitions, search_name, module) names = dynamic.related_names(definitions, search_name, module)
for d in definitions: for d in definitions:
@@ -361,13 +346,13 @@ def related_names(source, line, column, source_path):
if not d.assignment_details: if not d.assignment_details:
add_array(d.get_assignment_calls()) add_array(d.get_assignment_calls())
elif isinstance(d, parsing.Import): elif isinstance(d, parsing.Import):
is_user = d == f.parser.user_stmt is_user = d == self.module.parser.user_stmt
check_names = [d.namespace, d.alias, d.from_ns] if is_user \ check_names = [d.namespace, d.alias, d.from_ns] if is_user \
else d.get_defined_names() else d.get_defined_names()
for name in check_names: for name in check_names:
if name: if name:
for n in name.names: for n in name.names:
if n.start_pos <= pos <= n.end_pos or not is_user: if n.start_pos <= self.pos <= n.end_pos or not is_user:
names.append(dynamic.RelatedName(n, d)) names.append(dynamic.RelatedName(n, d))
elif isinstance(d, parsing.Name): elif isinstance(d, parsing.Name):
names.append(dynamic.RelatedName(d.names[0], d)) names.append(dynamic.RelatedName(d.names[0], d))
@@ -377,25 +362,7 @@ def related_names(source, line, column, source_path):
_clear_caches() _clear_caches()
return sorted(names, key=lambda x: (x.module_path, x.start_pos)) return sorted(names, key=lambda x: (x.module_path, x.start_pos))
def get_in_function_call(self):
def set_debug_function(func_cb):
"""
You can define a callback debug function to get all the debug messages.
:param func_cb: The callback function for debug messages, with n params.
"""
debug.debug_function = func_cb
def _clear_caches():
evaluate.clear_caches()
def get_in_function_call(source, line, column, source_path):
pos = (line, column)
f = modules.ModuleWithCursor(source_path, source=source, position=pos)
return _get_in_function_call(f, pos)
def _get_in_function_call(module, pos):
def scan_array_for_pos(arr, pos): def scan_array_for_pos(arr, pos):
""" Returns the function Call that match search_name in an Array. """ """ Returns the function Call that match search_name in an Array. """
index = None index = None
@@ -422,12 +389,12 @@ def _get_in_function_call(module, pos):
s = s.next s = s.next
return call, index return call, index
user_stmt = module.parser.user_stmt user_stmt = self.parser.user_stmt
if user_stmt is None or not isinstance(user_stmt, parsing.Statement): if user_stmt is None or not isinstance(user_stmt, parsing.Statement):
return None return None
ass = user_stmt.get_assignment_calls() ass = user_stmt.get_assignment_calls()
call, index = scan_array_for_pos(ass, pos) call, index = scan_array_for_pos(ass, self.pos)
if call is None: if call is None:
return None return None
@@ -439,3 +406,23 @@ def _get_in_function_call(module, pos):
return None return None
executable = origins[0] # just take entry zero, because we need just one. executable = origins[0] # just take entry zero, because we need just one.
return CallDef(executable, index) return CallDef(executable, index)
def _get_completion_parts(self, path):
"""
Returns the parts for the completion
:return: tuple - (path, dot, like)
"""
match = re.match(r'^(.*?)(\.|)(\w?[\w\d]*)$', path, flags=re.S)
return match.groups()
def _clear_caches():
evaluate.clear_caches()
def set_debug_function(func_cb):
"""
You can define a callback debug function to get all the debug messages.
:param func_cb: The callback function for debug messages, with n params.
"""
debug.debug_function = func_cb

View File

@@ -16,10 +16,10 @@ if exists("g:loaded_jedi") || &cp
endif endif
let g:loaded_jedi = 1 let g:loaded_jedi = 1
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
" completion " completion
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
function! jedi#complete(findstart, base) function! jedi#complete(findstart, base)
python << PYTHONEOF python << PYTHONEOF
if 1: if 1:
@@ -32,7 +32,6 @@ if 1:
count += 1 count += 1
vim.command('return %i' % (column - count)) vim.command('return %i' % (column - count))
else: else:
buf_path = vim.current.buffer.name
base = vim.eval('a:base') base = vim.eval('a:base')
source = '' source = ''
for i, line in enumerate(vim.current.buffer): for i, line in enumerate(vim.current.buffer):
@@ -45,7 +44,10 @@ if 1:
# here again, the hacks, because jedi has a different interface than vim # here again, the hacks, because jedi has a different interface than vim
column += len(base) column += len(base)
try: try:
completions, call_def = functions.complete(source, row, column, buf_path) script = get_script(source=source, column=column)
completions = script.complete()
call_def = script.get_in_function_call()
out = [] out = []
for c in completions: for c in completions:
d = dict(word=c.word[:len(base)] + c.complete, d = dict(word=c.word[:len(base)] + c.complete,
@@ -72,21 +74,16 @@ if 1:
PYTHONEOF PYTHONEOF
endfunction endfunction
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
" func_def " func_def
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
function jedi#show_func_def() function jedi#show_func_def()
python << PYTHONEOF python show_func_def(get_script().get_in_function_call())
if 1:
row, column = vim.current.window.cursor
source = '\n'.join(vim.current.buffer)
buf_path = vim.current.buffer.name
call_def = functions.get_in_function_call(source, row, column, buf_path)
show_func_def(call_def)
PYTHONEOF
return '' return ''
endfunction endfunction
function jedi#clear_func_def() function jedi#clear_func_def()
python << PYTHONEOF python << PYTHONEOF
if 1: if 1:
@@ -106,6 +103,7 @@ function! jedi#goto()
python _goto() python _goto()
endfunction endfunction
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
" get_definition " get_definition
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
@@ -113,6 +111,7 @@ function! jedi#get_definition()
python _goto(is_definition=True) python _goto(is_definition=True)
endfunction endfunction
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
" related_names " related_names
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
@@ -120,6 +119,7 @@ function! jedi#related_names()
python _goto(is_related_name=True) python _goto(is_related_name=True)
endfunction endfunction
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
" rename " rename
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
@@ -143,7 +143,6 @@ if 1:
# reset autocommand # reset autocommand
vim.command('autocmd! jedi_rename InsertLeave') vim.command('autocmd! jedi_rename InsertLeave')
current_buf = vim.current.buffer.name
replace = vim.eval("expand('<cword>')") replace = vim.eval("expand('<cword>')")
vim.command('normal! u') # undo new word vim.command('normal! u') # undo new word
vim.command('normal! u') # 2u didn't work... vim.command('normal! u') # 2u didn't work...
@@ -167,6 +166,7 @@ if 1:
PYTHONEOF PYTHONEOF
endfunction endfunction
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
" show_pydoc " show_pydoc
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
@@ -231,6 +231,7 @@ PYTHONEOF
let b:current_syntax = "rst" let b:current_syntax = "rst"
endfunction endfunction
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
" helper functions " helper functions
" ------------------------------------------------------------------------ " ------------------------------------------------------------------------
@@ -245,6 +246,7 @@ function! jedi#new_buffer(path)
endif endif
endfunction endfunction
function! jedi#tabnew(path) function! jedi#tabnew(path)
python << PYTHONEOF python << PYTHONEOF
if 1: if 1:
@@ -271,6 +273,7 @@ if 1:
PYTHONEOF PYTHONEOF
endfunction endfunction
function! s:add_goto_window() function! s:add_goto_window()
set lazyredraw set lazyredraw
cclose cclose
@@ -283,6 +286,7 @@ function! s:add_goto_window()
redraw! redraw!
endfunction endfunction
function! jedi#goto_window_on_enter() function! jedi#goto_window_on_enter()
let l:list = getqflist() let l:list = getqflist()
let l:data = l:list[line('.') - 1] let l:data = l:list[line('.') - 1]
@@ -296,6 +300,7 @@ function! jedi#goto_window_on_enter()
endif endif
endfunction endfunction
function! jedi#syn_stack() function! jedi#syn_stack()
if !exists("*synstack") if !exists("*synstack")
return [] return []
@@ -303,6 +308,7 @@ function! jedi#syn_stack()
return map(synstack(line('.'), col('.') - 1), 'synIDattr(v:val, "name")') return map(synstack(line('.'), col('.') - 1), 'synIDattr(v:val, "name")')
endfunc endfunc
function! jedi#do_popup_on_dot() function! jedi#do_popup_on_dot()
let highlight_groups = jedi#syn_stack() let highlight_groups = jedi#syn_stack()
for a in highlight_groups for a in highlight_groups
@@ -435,21 +441,31 @@ class PythonToVimStr(str):
def __repr__(self): def __repr__(self):
return '"%s"' % self.replace('"', r'\"') return '"%s"' % self.replace('"', r'\"')
def echo_highlight(msg): def echo_highlight(msg):
vim.command('echohl WarningMsg | echo "%s" | echohl None' % msg) vim.command('echohl WarningMsg | echo "%s" | echohl None' % msg)
def get_script(source=None, column=None):
if source is None:
source = '\n'.join(vim.current.buffer)
row = vim.current.window.cursor[0]
if column is None:
column = vim.current.window.cursor[1]
buf_path = vim.current.buffer.name
return functions.Script(source, row, column, buf_path)
def _goto(is_definition=False, is_related_name=False, no_output=False): def _goto(is_definition=False, is_related_name=False, no_output=False):
definitions = [] definitions = []
row, column = vim.current.window.cursor script = get_script()
buf_path = vim.current.buffer.name
source = '\n'.join(vim.current.buffer)
try: try:
if is_related_name: if is_related_name:
definitions = functions.related_names(source, row, column, buf_path) definitions = script.related_names()
elif is_definition: elif is_definition:
definitions = functions.get_definition(source, row, column, buf_path) definitions = script.get_definition()
else: else:
definitions = functions.goto(source, row, column, buf_path) definitions = script.goto()
except functions.NotFoundError: except functions.NotFoundError:
echo_highlight("Cannot follow nothing. Put your cursor on a valid name.") echo_highlight("Cannot follow nothing. Put your cursor on a valid name.")
except Exception: except Exception:
@@ -512,7 +528,7 @@ def show_func_def(call_def, completion_lines=0):
params = [p.get_code().replace('\n', '') for p in call_def.params] params = [p.get_code().replace('\n', '') for p in call_def.params]
try: try:
params[call_def.index] = '*%s*' % params[call_def.index] params[call_def.index] = '*%s*' % params[call_def.index]
except IndexError: except (IndexError, TypeError):
pass pass
text = " (%s) " % ', '.join(params) text = " (%s) " % ', '.join(params)
@@ -524,8 +540,6 @@ def show_func_def(call_def, completion_lines=0):
repl = ("%s" + regex + "%s") % (line[:insert_column], repl = ("%s" + regex + "%s") % (line[:insert_column],
line[insert_column:end_column], text, line[end_column:]) line[insert_column:end_column], text, line[end_column:])
vim.eval('setline(%s, "%s")' % (row_to_replace, repl)) vim.eval('setline(%s, "%s")' % (row_to_replace, repl))
#vim.command(r"%ss/^.\{%s\}/\1%s/g" % (row_to_replace, column, text))
PYTHONEOF PYTHONEOF
" vim: set et ts=4: " vim: set et ts=4:

View File

@@ -2,7 +2,9 @@
import os import os
import sys import sys
import unittest import unittest
from os.path import abspath, dirname
sys.path.append(abspath(dirname(abspath(__file__)) + '/..'))
os.chdir(os.path.dirname(os.path.abspath(__file__)) + '/..') os.chdir(os.path.dirname(os.path.abspath(__file__)) + '/..')
sys.path.append('.') sys.path.append('.')
@@ -13,15 +15,18 @@ import functions
class TestRegression(unittest.TestCase): class TestRegression(unittest.TestCase):
def get_def(self, src, pos): def get_def(self, src, pos):
return functions.get_definition(src, pos[0], pos[1], '') script = functions.Script(src, pos[0], pos[1], '')
return script.get_definition()
def complete(self, src, pos): def complete(self, src, pos):
return functions.complete(src, pos[0], pos[1], '')[0] script = functions.Script(src, pos[0], pos[1], '')
return script.complete()
def get_in_function_call(self, src, pos=None): def get_in_function_call(self, src, pos=None):
if pos is None: if pos is None:
pos = 1, len(src) pos = 1, len(src)
return functions.get_in_function_call(src, pos[0], pos[1], '') script = functions.Script(src, pos[0], pos[1], '')
return script.get_in_function_call()
def test_get_definition_cursor(self): def test_get_definition_cursor(self):
@@ -103,7 +108,7 @@ class TestRegression(unittest.TestCase):
s = ("def abc(): pass\n" s = ("def abc(): pass\n"
"abc.d.a.abc.d" "abc.d.a.abc.d"
) )
functions.related_names(s, 2, 2, '/') functions.Script(s, 2, 2, '/').related_names()
def test_get_in_function_call(self): def test_get_in_function_call(self):
s = "isinstance(a, abs(" s = "isinstance(a, abs("

View File

@@ -16,25 +16,14 @@ import debug
sys.path.pop() # pop again, because it might affect the completion sys.path.pop() # pop again, because it might affect the completion
def run_completion_test(correct, source, line_nr, index, line, path): def run_completion_test(script, correct, line_nr):
""" """
Runs tests for completions. Runs tests for completions.
Return if the test was a fail or not, with 1 for fail and 0 for success. Return if the test was a fail or not, with 1 for fail and 0 for success.
""" """
# lines start with 1 and column is just the last (makes no completions = script.complete()
# difference for testing) #import cProfile; cProfile.run('script.complete()')
try:
completions, call_def = functions.complete(source, line_nr, index,
path)
#import cProfile as profile
#profile.run('functions.complete("""%s""", %i, %i, "%s")'
# % (source, line_nr, len(line), path))
except Exception:
print(traceback.format_exc())
print('test @%s: %s' % (line_nr - 1, line))
return 1
else:
# TODO remove set! duplicates should not be normal
comp_str = set([c.word for c in completions]) comp_str = set([c.word for c in completions])
if comp_str != set(literal_eval(correct)): if comp_str != set(literal_eval(correct)):
print('Solution @%s not right, received %s, wanted %s'\ print('Solution @%s not right, received %s, wanted %s'\
@@ -43,45 +32,12 @@ def run_completion_test(correct, source, line_nr, index, line, path):
return 0 return 0
def run_definition_test(correct, source, line_nr, index, line, correct_start, def run_definition_test(script, should_str, line_nr):
path):
""" """
Runs tests for definitions. Runs tests for definitions.
Return if the test was a fail or not, with 1 for fail and 0 for success. Return if the test was a fail or not, with 1 for fail and 0 for success.
""" """
def defs(line_nr, indent): result = script.get_definition()
return set(functions.get_definition(source, line_nr, indent, path))
try:
result = defs(line_nr, index)
except Exception:
print(traceback.format_exc())
print('test @%s: %s' % (line_nr - 1, line))
return 1
else:
should_be = set()
number = 0
for index in re.finditer('(?: +|$)', correct):
if correct == ' ':
continue
# -1 for the comment, +3 because of the comment start `#? `
start = index.start()
if print_debug:
functions.set_debug_function(None)
number += 1
try:
should_be |= defs(line_nr - 1, start + correct_start)
except Exception:
print(traceback.format_exc())
print('could not resolve %s indent %s' % (line_nr - 1, start))
return 1
if print_debug:
functions.set_debug_function(debug.print_to_stdout)
# because the objects have different ids, `repr` it, then compare it.
should_str = set(r.desc_with_module for r in should_be)
if len(should_str) < number:
print('Solution @%s not right, too few test results: %s' \
% (line_nr - 1, should_str))
return 1
is_str = set(r.desc_with_module for r in result) is_str = set(r.desc_with_module for r in result)
if is_str != should_str: if is_str != should_str:
print('Solution @%s not right, received %s, wanted %s' \ print('Solution @%s not right, received %s, wanted %s' \
@@ -90,7 +46,7 @@ def run_definition_test(correct, source, line_nr, index, line, correct_start,
return 0 return 0
def run_goto_test(correct, source, line_nr, index, line, path): def run_goto_test(script, correct, line_nr):
""" """
Runs tests for gotos. Runs tests for gotos.
Tests look like this: Tests look like this:
@@ -107,13 +63,7 @@ def run_goto_test(correct, source, line_nr, index, line, path):
Return if the test was a fail or not, with 1 for fail and 0 for success. Return if the test was a fail or not, with 1 for fail and 0 for success.
""" """
try: result = script.goto()
result = functions.goto(source, line_nr, index, path)
except Exception:
print(traceback.format_exc())
print('test @%s: %s' % (line_nr - 1, line))
return 1
else:
comp_str = str(sorted(r.description for r in result)) comp_str = str(sorted(r.description for r in result))
if comp_str != correct: if comp_str != correct:
print('Solution @%s not right, received %s, wanted %s'\ print('Solution @%s not right, received %s, wanted %s'\
@@ -122,7 +72,7 @@ def run_goto_test(correct, source, line_nr, index, line, path):
return 0 return 0
def run_related_name_test(correct, source, line_nr, index, line, path): def run_related_name_test(script, correct, line_nr):
""" """
Runs tests for gotos. Runs tests for gotos.
Tests look like this: Tests look like this:
@@ -132,13 +82,7 @@ def run_related_name_test(correct, source, line_nr, index, line, path):
Return if the test was a fail or not, with 1 for fail and 0 for success. Return if the test was a fail or not, with 1 for fail and 0 for success.
""" """
try: result = script.related_names()
result = functions.related_names(source, line_nr, index, path)
except Exception:
print(traceback.format_exc())
print('test @%s: %s' % (line_nr - 1, line))
return 1
else:
correct = correct.strip() correct = correct.strip()
comp_str = set('(%s,%s)' % r.start_pos for r in result) comp_str = set('(%s,%s)' % r.start_pos for r in result)
correct = set(correct.split(' ')) if correct else set() correct = set(correct.split(' ')) if correct else set()
@@ -164,9 +108,40 @@ def run_test(source, f_name, lines_to_execute):
>>> #? int() >>> #? int()
>>> ab = 3; ab >>> ab = 3; ab
""" """
def get_defs(correct, correct_start, path):
def defs(line_nr, indent):
script = functions.Script(source, line_nr, indent, path)
return set(script.get_definition())
should_be = set()
number = 0
for index in re.finditer('(?: +|$)', correct):
if correct == ' ':
continue
# -1 for the comment, +3 because of the comment start `#? `
start = index.start()
if print_debug:
functions.set_debug_function(None)
number += 1
try:
should_be |= defs(line_nr - 1, start + correct_start)
except Exception:
raise Exception('could not resolve %s indent %s'
% (line_nr - 1, start))
if print_debug:
functions.set_debug_function(debug.print_to_stdout)
# because the objects have different ids, `repr` it, then compare it.
should_str = set(r.desc_with_module for r in should_be)
if len(should_str) < number:
raise Exception('Solution @%s not right, too few test results: %s'
% (line_nr - 1, should_str))
return should_str
fails = 0 fails = 0
tests = 0 tests = 0
correct = None correct = None
test_type = None
start = None
for line_nr, line in enumerate(BytesIO(source.encode())): for line_nr, line in enumerate(BytesIO(source.encode())):
line = unicode(line) line = unicode(line)
line_nr += 1 line_nr += 1
@@ -181,16 +156,21 @@ def run_test(source, f_name, lines_to_execute):
# if a list is wanted, use the completion test, otherwise the # if a list is wanted, use the completion test, otherwise the
# get_definition test # get_definition test
path = completion_test_dir + os.path.sep + f_name path = completion_test_dir + os.path.sep + f_name
args = (correct, source, line_nr, index, line, path) try:
script = functions.Script(source, line_nr, index, path)
if test_type == '!': if test_type == '!':
fails += run_goto_test(*args) fails += run_goto_test(script, correct, line_nr)
elif test_type == '<': elif test_type == '<':
fails += run_related_name_test(*args) fails += run_related_name_test(script, correct, line_nr)
elif correct.startswith('['): elif correct.startswith('['):
fails += run_completion_test(*args) fails += run_completion_test(script, correct, line_nr)
else: else:
fails += run_definition_test(correct, source, line_nr, index, should_str = get_defs(correct, start, path)
line, start, path) fails += run_definition_test(script, should_str, line_nr)
except Exception:
print(traceback.format_exc())
print('test @%s: %s' % (line_nr - 1, line))
fails += 1
correct = None correct = None
tests += 1 tests += 1
else: else: