1import sys 2import datetime 3import os 4helpers_dir = os.getenv("PYCHARM_HELPERS_DIR", sys.path[0]) 5if sys.path[0] != helpers_dir: 6 sys.path.insert(0, helpers_dir) 7 8from tcunittest import TeamcityTestResult 9from tcmessages import TeamcityServiceMessages 10 11from pycharm_run_utils import import_system_module 12from pycharm_run_utils import adjust_sys_path, debug, getModuleName, PYTHON_VERSION_MAJOR 13 14adjust_sys_path() 15 16re = import_system_module("re") 17doctest = import_system_module("doctest") 18traceback = import_system_module("traceback") 19 20class TeamcityDocTestResult(TeamcityTestResult): 21 """ 22 DocTests Result extends TeamcityTestResult, 23 overrides some methods, specific for doc tests, 24 such as getTestName, getTestId. 25 """ 26 def getTestName(self, test): 27 name = self.current_suite.name + test.source 28 return name 29 30 def getSuiteName(self, suite): 31 if test.source.rfind(".") == -1: 32 name = self.current_suite.name + test.source 33 else: 34 name = test.source 35 return name 36 37 def getTestId(self, test): 38 file = os.path.realpath(self.current_suite.filename) if self.current_suite.filename else "" 39 line_no = test.lineno 40 if self.current_suite.lineno: 41 line_no += self.current_suite.lineno 42 return "file://" + file + ":" + str(line_no) 43 44 def getSuiteLocation(self): 45 file = os.path.realpath(self.current_suite.filename) if self.current_suite.filename else "" 46 location = "file://" + file 47 if self.current_suite.lineno: 48 location += ":" + str(self.current_suite.lineno) 49 return location 50 51 def startTest(self, test): 52 setattr(test, "startTime", datetime.datetime.now()) 53 id = self.getTestId(test) 54 self.messages.testStarted(self.getTestName(test), location=id) 55 56 def startSuite(self, suite): 57 self.current_suite = suite 58 self.messages.testSuiteStarted(suite.name, location=self.getSuiteLocation()) 59 60 def stopSuite(self, suite): 61 self.messages.testSuiteFinished(suite.name) 62 63 def addFailure(self, test, err = '', expected=None, actual=None): 64 self.messages.testFailed(self.getTestName(test), expected=expected, actual=actual, 65 message='Failure', details=err, duration=int(self.__getDuration(test))) 66 67 def addError(self, test, err = ''): 68 self.messages.testError(self.getTestName(test), 69 message='Error', details=err, duration=self.__getDuration(test)) 70 71 def stopTest(self, test): 72 duration = self.__getDuration(test) 73 self.messages.testFinished(self.getTestName(test), duration=int(duration)) 74 75 def __getDuration(self, test): 76 start = getattr(test, "startTime", datetime.datetime.now()) 77 d = datetime.datetime.now() - start 78 duration = d.microseconds / 1000 + d.seconds * 1000 + d.days * 86400000 79 return duration 80 81 82class DocTestRunner(doctest.DocTestRunner): 83 """ 84 Special runner for doctests, 85 overrides __run method to report results using TeamcityDocTestResult 86 """ 87 def __init__(self, verbose=None, optionflags=0): 88 doctest.DocTestRunner.__init__(self, verbose, optionflags) 89 self.stream = sys.stdout 90 self.result = TeamcityDocTestResult(self.stream) 91 #self.result.messages.testMatrixEntered() 92 self._tests = [] 93 94 def addTests(self, tests): 95 self._tests.extend(tests) 96 97 def addTest(self, test): 98 self._tests.append(test) 99 100 def countTests(self): 101 return len(self._tests) 102 103 def start(self): 104 for test in self._tests: 105 self.run(test) 106 107 def __run(self, test, compileflags, out): 108 failures = tries = 0 109 110 original_optionflags = self.optionflags 111 SUCCESS, FAILURE, BOOM = range(3) # `outcome` state 112 check = self._checker.check_output 113 self.result.startSuite(test) 114 for examplenum, example in enumerate(test.examples): 115 116 quiet = (self.optionflags & doctest.REPORT_ONLY_FIRST_FAILURE and 117 failures > 0) 118 119 self.optionflags = original_optionflags 120 if example.options: 121 for (optionflag, val) in example.options.items(): 122 if val: 123 self.optionflags |= optionflag 124 else: 125 self.optionflags &= ~optionflag 126 127 if hasattr(doctest, 'SKIP'): 128 if self.optionflags & doctest.SKIP: 129 continue 130 131 tries += 1 132 if not quiet: 133 self.report_start(out, test, example) 134 135 filename = '<doctest %s[%d]>' % (test.name, examplenum) 136 137 try: 138 exec(compile(example.source, filename, "single", 139 compileflags, 1), test.globs) 140 self.debugger.set_continue() # ==== Example Finished ==== 141 exception = None 142 except KeyboardInterrupt: 143 raise 144 except: 145 exception = sys.exc_info() 146 self.debugger.set_continue() # ==== Example Finished ==== 147 148 got = self._fakeout.getvalue() # the actual output 149 self._fakeout.truncate(0) 150 outcome = FAILURE # guilty until proved innocent or insane 151 152 if exception is None: 153 if check(example.want, got, self.optionflags): 154 outcome = SUCCESS 155 156 else: 157 exc_msg = traceback.format_exception_only(*exception[:2])[-1] 158 if not quiet: 159 got += doctest._exception_traceback(exception) 160 161 if example.exc_msg is None: 162 outcome = BOOM 163 164 elif check(example.exc_msg, exc_msg, self.optionflags): 165 outcome = SUCCESS 166 167 elif self.optionflags & doctest.IGNORE_EXCEPTION_DETAIL: 168 m1 = re.match(r'[^:]*:', example.exc_msg) 169 m2 = re.match(r'[^:]*:', exc_msg) 170 if m1 and m2 and check(m1.group(0), m2.group(0), 171 self.optionflags): 172 outcome = SUCCESS 173 174 # Report the outcome. 175 if outcome is SUCCESS: 176 self.result.startTest(example) 177 self.result.stopTest(example) 178 elif outcome is FAILURE: 179 self.result.startTest(example) 180 err = self._failure_header(test, example) +\ 181 self._checker.output_difference(example, got, self.optionflags) 182 expected = getattr(example, "want", None) 183 self.result.addFailure(example, err, expected=expected, actual=got) 184 185 elif outcome is BOOM: 186 self.result.startTest(example) 187 err=self._failure_header(test, example) +\ 188 'Exception raised:\n' + doctest._indent(doctest._exception_traceback(exception)) 189 self.result.addError(example, err) 190 191 else: 192 assert False, ("unknown outcome", outcome) 193 194 self.optionflags = original_optionflags 195 196 self.result.stopSuite(test) 197 198 199modules = {} 200 201 202 203runner = DocTestRunner() 204 205 206def _load_file(moduleName, fileName): 207 if sys.version_info >= (3, 3): 208 from importlib import machinery 209 return machinery.SourceFileLoader(moduleName, fileName).load_module() 210 else: 211 import imp 212 return imp.load_source(moduleName, fileName) 213 214def loadSource(fileName): 215 """ 216 loads source from fileName, 217 we can't use tat function from utrunner, because of we 218 store modules in global variable. 219 """ 220 baseName = os.path.basename(fileName) 221 moduleName = os.path.splitext(baseName)[0] 222 223 # for users wanted to run simple doctests under django 224 #because of django took advantage of module name 225 settings_file = os.getenv('DJANGO_SETTINGS_MODULE') 226 if settings_file and moduleName=="models": 227 baseName = os.path.realpath(fileName) 228 moduleName = ".".join((baseName.split(os.sep)[-2], "models")) 229 230 if moduleName in modules: # add unique number to prevent name collisions 231 cnt = 2 232 prefix = moduleName 233 while getModuleName(prefix, cnt) in modules: 234 cnt += 1 235 moduleName = getModuleName(prefix, cnt) 236 debug("/ Loading " + fileName + " as " + moduleName) 237 module = _load_file(moduleName, fileName) 238 modules[moduleName] = module 239 return module 240 241def testfile(filename): 242 if PYTHON_VERSION_MAJOR == 3: 243 text, filename = doctest._load_testfile(filename, None, False, "utf-8") 244 else: 245 text, filename = doctest._load_testfile(filename, None, False) 246 247 name = os.path.basename(filename) 248 globs = {'__name__': '__main__'} 249 250 parser = doctest.DocTestParser() 251 # Read the file, convert it to a test, and run it. 252 test = parser.get_doctest(text, globs, name, filename, 0) 253 if test.examples: 254 runner.addTest(test) 255 256def testFilesInFolder(folder): 257 return testFilesInFolderUsingPattern(folder) 258 259def testFilesInFolderUsingPattern(folder, pattern = ".*"): 260 ''' loads modules from folder , 261 check if module name matches given pattern''' 262 modules = [] 263 prog = re.compile(pattern) 264 265 for root, dirs, files in os.walk(folder): 266 for name in files: 267 path = os.path.join(root, name) 268 if prog.match(name): 269 if name.endswith(".py"): 270 modules.append(loadSource(path)) 271 elif not name.endswith(".pyc") and not name.endswith("$py.class") and os.path.isfile(path): 272 testfile(path) 273 274 return modules 275 276if __name__ == "__main__": 277 finder = doctest.DocTestFinder() 278 279 for arg in sys.argv[1:]: 280 arg = arg.strip() 281 if len(arg) == 0: 282 continue 283 284 a = arg.split("::") 285 if len(a) == 1: 286 # From module or folder 287 a_splitted = a[0].split(";") 288 if len(a_splitted) != 1: 289 # means we have pattern to match against 290 if a_splitted[0].endswith("/"): 291 debug("/ from folder " + a_splitted[0] + ". Use pattern: " + a_splitted[1]) 292 modules = testFilesInFolderUsingPattern(a_splitted[0], a_splitted[1]) 293 else: 294 if a[0].endswith("/"): 295 debug("/ from folder " + a[0]) 296 modules = testFilesInFolder(a[0]) 297 else: 298 # from file 299 debug("/ from module " + a[0]) 300 # for doctests from non-python file 301 if a[0].rfind(".py") == -1: 302 testfile(a[0]) 303 modules = [] 304 else: 305 modules = [loadSource(a[0])] 306 307 # for doctests 308 for module in modules: 309 tests = finder.find(module, module.__name__) 310 for test in tests: 311 if test.examples: 312 runner.addTest(test) 313 314 elif len(a) == 2: 315 # From testcase 316 debug("/ from class " + a[1] + " in " + a[0]) 317 try: 318 module = loadSource(a[0]) 319 except SyntaxError: 320 raise NameError('File "%s" is not python file' % (a[0], )) 321 if hasattr(module, a[1]): 322 testcase = getattr(module, a[1]) 323 tests = finder.find(testcase, getattr(testcase, "__name__", None)) 324 runner.addTests(tests) 325 else: 326 raise NameError('Module "%s" has no class "%s"' % (a[0], a[1])) 327 else: 328 # From method in class or from function 329 try: 330 module = loadSource(a[0]) 331 except SyntaxError: 332 raise NameError('File "%s" is not python file' % (a[0], )) 333 if a[1] == "": 334 # test function, not method 335 debug("/ from method " + a[2] + " in " + a[0]) 336 if hasattr(module, a[2]): 337 testcase = getattr(module, a[2]) 338 tests = finder.find(testcase, getattr(testcase, "__name__", None)) 339 runner.addTests(tests) 340 else: 341 raise NameError('Module "%s" has no method "%s"' % (a[0], a[2])) 342 else: 343 debug("/ from method " + a[2] + " in class " + a[1] + " in " + a[0]) 344 if hasattr(module, a[1]): 345 testCaseClass = getattr(module, a[1]) 346 if hasattr(testCaseClass, a[2]): 347 testcase = getattr(testCaseClass, a[2]) 348 name = getattr(testcase, "__name__", None) 349 if not name: 350 name = testCaseClass.__name__ 351 tests = finder.find(testcase, name) 352 runner.addTests(tests) 353 else: 354 raise NameError('Class "%s" has no function "%s"' % (testCaseClass, a[2])) 355 else: 356 raise NameError('Module "%s" has no class "%s"' % (module, a[1])) 357 358 debug("/ Loaded " + str(runner.countTests()) + " tests") 359 TeamcityServiceMessages(sys.stdout).testCount(runner.countTests()) 360 runner.start() 361