1from tcunittest import TeamcityTestRunner, TeamcityTestResult
2from tcmessages import TeamcityServiceMessages
3import sys
4from pycharm_run_utils import adjust_django_sys_path
5from django.test.utils import get_runner
6
7adjust_django_sys_path()
8
9from django.conf import settings
10
11def is_nosetest(settings):
12  """
13  Checks if Django configured to work with nosetest
14
15  :param settings: django settings
16  :return: True if django should works with NoseTest runner of its inheritor
17  """
18  try:
19    runner = get_runner(settings)
20    from django_nose import NoseTestSuiteRunner
21    if issubclass(runner, NoseTestSuiteRunner):
22        return True
23  except (AttributeError, ImportError):
24    pass
25  return False
26
27
28from django.test.testcases import TestCase
29from django import VERSION
30
31if is_nosetest(settings):
32  from nose_utils import TeamcityNoseRunner
33
34# See: https://docs.djangoproject.com/en/1.8/releases/1.7/#django-utils-unittest
35# django.utils.unittest provided uniform access to the unittest2 library on all Python versions.
36# Since unittest2 became the standard library's unittest module in Python 2.7,
37# and Django 1.7 drops support for older Python versions, this module isn't useful anymore.
38# It has been deprecated. Use unittest instead.
39if VERSION >= (1,7):
40  import unittest
41else:
42  from django.utils import unittest
43
44def get_test_suite_runner():
45  if hasattr(settings, "TEST_RUNNER"):
46    from django.test.utils import get_runner
47
48    class TempSettings:
49      TEST_RUNNER = settings.TEST_RUNNER
50
51    return get_runner(TempSettings)
52
53try:
54  if VERSION >= (1,6):
55    from django.test.runner import DiscoverRunner as DjangoSuiteRunner
56  else:
57    from django.test.simple import DjangoTestSuiteRunner as DjangoSuiteRunner
58
59  from inspect import isfunction
60
61  SUITE_RUNNER = get_test_suite_runner()
62  if isfunction(SUITE_RUNNER):
63    import sys
64
65    sys.stderr.write(
66      "WARNING: TEST_RUNNER variable is ignored. PyCharm test runner supports "
67      "only class-like TEST_RUNNER valiables. Use Tools->run manage.py tasks.\n")
68    SUITE_RUNNER = None
69  BaseSuiteRunner = SUITE_RUNNER or DjangoSuiteRunner
70
71  class BaseRunner(TeamcityTestRunner, BaseSuiteRunner):
72    def __init__(self, stream=sys.stdout, **options):
73      TeamcityTestRunner.__init__(self, stream)
74      BaseSuiteRunner.__init__(self, **options)
75
76except ImportError:
77  # for Django <= 1.1 compatibility
78  class BaseRunner(TeamcityTestRunner):
79    def __init__(self, stream=sys.stdout, **options):
80      TeamcityTestRunner.__init__(self, stream)
81
82
83def strclass(cls):
84  if not cls.__name__:
85    return cls.__module__
86  return "%s.%s" % (cls.__module__, cls.__name__)
87
88class DjangoTeamcityTestResult(TeamcityTestResult):
89  def __init__(self, *args, **kwargs):
90    super(DjangoTeamcityTestResult, self).__init__(**kwargs)
91
92  def _getSuite(self, test):
93    if hasattr(test, "suite"):
94      suite = strclass(test.suite)
95      suite_location = test.suite.location
96      location = test.suite.abs_location
97      if hasattr(test, "lineno"):
98        location = location + ":" + str(test.lineno)
99      else:
100        location = location + ":" + str(test.test.lineno)
101    else:
102
103      suite = strclass(test.__class__)
104      suite_location = "django_testid://" + suite
105      location = "django_testid://" + str(test.id())
106
107    return (suite, location, suite_location)
108
109
110class DjangoTeamcityTestRunner(BaseRunner):
111  def __init__(self, stream=sys.stdout, **options):
112    super(DjangoTeamcityTestRunner, self).__init__(stream, **options)
113    self.options = options
114
115  def _makeResult(self, **kwargs):
116    return DjangoTeamcityTestResult(self.stream, **kwargs)
117
118  def build_suite(self, *args, **kwargs):
119    EXCLUDED_APPS = getattr(settings, 'TEST_EXCLUDE', [])
120    suite = super(DjangoTeamcityTestRunner, self).build_suite(*args, **kwargs)
121    if not args[0] and not getattr(settings, 'RUN_ALL_TESTS', False):
122      tests = []
123      for case in suite:
124        pkg = case.__class__.__module__.split('.')[0]
125        if pkg not in EXCLUDED_APPS:
126          tests.append(case)
127      suite._tests = tests
128    return suite
129
130  def run_suite(self, suite, **kwargs):
131    if is_nosetest(settings):
132      from django_nose.plugin import DjangoSetUpPlugin, ResultPlugin
133      from django_nose.runner import _get_plugins_from_settings
134      from nose.config import Config
135      import nose
136
137      result_plugin = ResultPlugin()
138      plugins_to_add = [DjangoSetUpPlugin(self), result_plugin]
139
140      config = Config(plugins=nose.core.DefaultPluginManager())
141      config.plugins.addPlugins(extraplugins=plugins_to_add)
142
143      for plugin in _get_plugins_from_settings():
144        plugins_to_add.append(plugin)
145      nose.core.TestProgram(argv=suite, exit=False, addplugins=plugins_to_add,
146                            testRunner=TeamcityNoseRunner(config=config))
147      return result_plugin.result
148
149    else:
150      self.options.update(kwargs)
151      return TeamcityTestRunner.run(self, suite, **self.options)
152
153  def run_tests(self, test_labels, extra_tests=None, **kwargs):
154    if is_nosetest(settings):
155      return super(DjangoTeamcityTestRunner, self).run_tests(test_labels, extra_tests)
156    return super(DjangoTeamcityTestRunner, self).run_tests(test_labels, extra_tests, **kwargs)
157
158
159def partition_suite(suite, classes, bins):
160  """
161  Partitions a test suite by test type.
162
163  classes is a sequence of types
164  bins is a sequence of TestSuites, one more than classes
165
166  Tests of type classes[i] are added to bins[i],
167  tests with no match found in classes are place in bins[-1]
168  """
169  for test in suite:
170    if isinstance(test, unittest.TestSuite):
171      partition_suite(test, classes, bins)
172    else:
173      for i in range(len(classes)):
174        if isinstance(test, classes[i]):
175          bins[i].addTest(test)
176          break
177      else:
178        bins[-1].addTest(test)
179
180
181def reorder_suite(suite, classes):
182  """
183  Reorders a test suite by test type.
184
185  classes is a sequence of types
186
187  All tests of type clases[0] are placed first, then tests of type classes[1], etc.
188  Tests with no match in classes are placed last.
189  """
190  class_count = len(classes)
191  bins = [unittest.TestSuite() for i in range(class_count + 1)]
192  partition_suite(suite, classes, bins)
193  for i in range(class_count):
194    bins[0].addTests(bins[i + 1])
195  return bins[0]
196
197
198def run_the_old_way(extra_tests, kwargs, test_labels, verbosity):
199  from django.test.simple import build_suite, build_test, get_app, get_apps, \
200    setup_test_environment, teardown_test_environment
201
202  setup_test_environment()
203  settings.DEBUG = False
204  suite = unittest.TestSuite()
205  if test_labels:
206    for label in test_labels:
207      if '.' in label:
208        suite.addTest(build_test(label))
209      else:
210        app = get_app(label)
211        suite.addTest(build_suite(app))
212  else:
213    for app in get_apps():
214      suite.addTest(build_suite(app))
215  for test in extra_tests:
216    suite.addTest(test)
217  suite = reorder_suite(suite, (TestCase,))
218  old_name = settings.DATABASE_NAME
219  from django.db import connection
220
221  connection.creation.create_test_db(verbosity, autoclobber=False)
222  result = DjangoTeamcityTestRunner().run(suite, **kwargs)
223  connection.creation.destroy_test_db(old_name, verbosity)
224  teardown_test_environment()
225  return len(result.failures) + len(result.errors)
226
227
228def run_tests(test_labels, verbosity=1, interactive=False, extra_tests=[],
229              **kwargs):
230  """
231  Run the unit tests for all the test labels in the provided list.
232  Labels must be of the form:
233   - app.TestClass.test_method
234      Run a single specific test method
235   - app.TestClass
236      Run all the test methods in a given class
237   - app
238      Search for doctests and unittests in the named application.
239
240  When looking for tests, the test runner will look in the models and
241  tests modules for the application.
242
243  A list of 'extra' tests may also be provided; these tests
244  will be added to the test suite.
245
246  Returns the number of tests that failed.
247  """
248  options = {
249    'verbosity': verbosity,
250    'interactive': interactive
251  }
252  options.update(kwargs)
253  TeamcityServiceMessages(sys.stdout).testMatrixEntered()
254  return DjangoTeamcityTestRunner(**options).run_tests(test_labels,
255                                                       extra_tests=extra_tests, **options)