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