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