#!/usr/bin/env python import os import sys import re import traceback from os.path import abspath, dirname sys.path.append(abspath(dirname(abspath(__file__)) + '/..')) os.chdir(dirname(abspath(__file__)) + '/..') from _compatibility import unicode, BytesIO, reduce, literal_eval, is_py25 import functions import debug sys.path.pop() # pop again, because it might affect the completion def run_completion_test(correct, source, line_nr, index, line, path): """ Runs tests for completions. 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 # difference for testing) try: completions = 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]) if comp_str != set(literal_eval(correct)): print('Solution @%s not right, received %s, wanted %s'\ % (line_nr - 1, comp_str, correct)) return 1 return 0 def run_definition_test(correct, source, line_nr, index, line, correct_start, path): """ Runs tests for definitions. Return if the test was a fail or not, with 1 for fail and 0 for success. """ def defs(line_nr, indent): 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) if is_str != should_str: print('Solution @%s not right, received %s, wanted %s' \ % (line_nr - 1, is_str, should_str)) return 1 return 0 def run_goto_test(correct, source, line_nr, index, line, path): """ Runs tests for gotos. 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 For the tests the important things in the end are the positions. Return if the test was a fail or not, with 1 for fail and 0 for success. """ try: 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)) if comp_str != correct: print('Solution @%s not right, received %s, wanted %s'\ % (line_nr - 1, comp_str, correct)) return 1 return 0 def run_related_name_test(correct, source, line_nr, index, line, path): """ Runs tests for gotos. Tests look like this: >>> abc = 1 >>> #< abc@1,0 abc@3,0 >>> abc Return if the test was a fail or not, with 1 for fail and 0 for success. """ try: 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() comp_str = set('(%s,%s)' % r.start_pos for r in result) correct = set(correct.split(' ')) if correct else set() if comp_str != correct: print('Solution @%s not right, received %s, wanted %s'\ % (line_nr - 1, comp_str, correct)) return 1 return 0 def run_test(source, f_name, lines_to_execute): """ This is the completion test for some cases. The tests are not unit test like, they are rather integration tests. It uses comments to specify a test in the next line. The comment also says, which results are expected. The comment always begins with `#?`. The last row symbolizes the cursor. For example: >>> #? ['ab'] >>> ab = 3; a >>> #? int() >>> ab = 3; ab """ fails = 0 tests = 0 correct = None for line_nr, line in enumerate(BytesIO(source.encode())): line = unicode(line) line_nr += 1 if correct: r = re.match('^(\d+)\s*(.*)$', correct) if r: index = int(r.group(1)) correct = r.group(2) start += r.regs[2][0] # second group, start index else: index = len(line) - 1 # -1 for the \n # if a list is wanted, use the completion test, otherwise the # get_definition test path = completion_test_dir + os.path.sep + f_name args = (correct, source, line_nr, index, line, path) if test_type == '!': fails += run_goto_test(*args) elif test_type == '<': fails += run_related_name_test(*args) elif correct.startswith('['): fails += run_completion_test(*args) else: fails += run_definition_test(correct, source, line_nr, index, line, start, path) correct = None tests += 1 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: # reset the test, if only one specific test is wanted if lines_to_execute and line_nr not in lines_to_execute: correct = None return tests, fails def test_dir(completion_test_dir, thirdparty=False): global tests_fail for f_name in os.listdir(completion_test_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 if thirdparty: try: # there is always an underline at the end. # It looks like: completion/thirdparty/pylab_.py __import__(f_name.replace('_.py', '')) except ImportError: summary.append('Thirdparty-Library %s not found.' % f_name) continue path = os.path.join(completion_test_dir, f_name) f = open(path) num_tests, fails = run_test(f.read(), f_name, lines_to_execute) s = 'run %s tests with %s fails (%s)' % (num_tests, fails, f_name) tests_fail += fails print(s) summary.append(s) # Sorry I didn't use argparse here. It's because argparse is not in the # stdlib in 2.5. args = sys.argv[1:] try: i = args.index('--thirdparty') thirdparty = True args = args[:i] + args[i + 1:] except ValueError: thirdparty = False print_debug = False try: i = args.index('--debug') args = args[:i] + args[i + 1:] except ValueError: pass else: print_debug = True functions.set_debug_function(debug.print_to_stdout) # get test list, that should be executed test_files = {} last = None for arg in args: if arg.isdigit(): if last is None: continue test_files[last].append(int(arg)) else: test_files[arg] = [] last = arg # completion tests: completion_test_dir = 'test/completion' summary = [] tests_fail = 0 # execute tests test_dir(completion_test_dir) if test_files or thirdparty: completion_test_dir += '/thirdparty' test_dir(completion_test_dir, thirdparty=True) print('\nSummary: (%s fails)' % tests_fail) for s in summary: print(s) exit_code = 1 if tests_fail else 0 if sys.hexversion < 0x02060000 and tests_fail <= 5: # Python 2.5 has major incompabillities (e.g. no property.setter), # therefore it is not possible to pass all tests. exit_code = 0 sys.exit(exit_code)