blob: bb704db3f240308368055c0242ad127d869f92ff [file] [log] [blame]
Tor Norbye1aa2e092014-08-20 17:01:23 -07001from __future__ import nested_scopes
2
3import fnmatch
4import os.path
5from pydev_runfiles_coverage import StartCoverageSupport
6import pydev_runfiles_unittest
7from pydevd_constants import * #@UnusedWildImport
8import re
9import time
10import unittest
11
12
13#=======================================================================================================================
14# Configuration
15#=======================================================================================================================
16class Configuration:
17
18 def __init__(
19 self,
20 files_or_dirs='',
21 verbosity=2,
22 include_tests=None,
23 tests=None,
24 port=None,
25 files_to_tests=None,
26 jobs=1,
27 split_jobs='tests',
28 coverage_output_dir=None,
29 coverage_include=None,
30 coverage_output_file=None,
31 exclude_files=None,
32 exclude_tests=None,
33 include_files=None,
34 django=False,
35 ):
36 self.files_or_dirs = files_or_dirs
37 self.verbosity = verbosity
38 self.include_tests = include_tests
39 self.tests = tests
40 self.port = port
41 self.files_to_tests = files_to_tests
42 self.jobs = jobs
43 self.split_jobs = split_jobs
44 self.django = django
45
46 if include_tests:
47 assert isinstance(include_tests, (list, tuple))
48
49 if exclude_files:
50 assert isinstance(exclude_files, (list, tuple))
51
52 if exclude_tests:
53 assert isinstance(exclude_tests, (list, tuple))
54
55 self.exclude_files = exclude_files
56 self.include_files = include_files
57 self.exclude_tests = exclude_tests
58
59 self.coverage_output_dir = coverage_output_dir
60 self.coverage_include = coverage_include
61 self.coverage_output_file = coverage_output_file
62
63 def __str__(self):
64 return '''Configuration
65 - files_or_dirs: %s
66 - verbosity: %s
67 - tests: %s
68 - port: %s
69 - files_to_tests: %s
70 - jobs: %s
71 - split_jobs: %s
72
73 - include_files: %s
74 - include_tests: %s
75
76 - exclude_files: %s
77 - exclude_tests: %s
78
79 - coverage_output_dir: %s
80 - coverage_include_dir: %s
81 - coverage_output_file: %s
82
83 - django: %s
84''' % (
85 self.files_or_dirs,
86 self.verbosity,
87 self.tests,
88 self.port,
89 self.files_to_tests,
90 self.jobs,
91 self.split_jobs,
92
93 self.include_files,
94 self.include_tests,
95
96 self.exclude_files,
97 self.exclude_tests,
98
99 self.coverage_output_dir,
100 self.coverage_include,
101 self.coverage_output_file,
102
103 self.django,
104 )
105
106
107#=======================================================================================================================
108# parse_cmdline
109#=======================================================================================================================
110def parse_cmdline(argv=None):
111 """
112 Parses command line and returns test directories, verbosity, test filter and test suites
113
114 usage:
115 runfiles.py -v|--verbosity <level> -t|--tests <Test.test1,Test2> dirs|files
116
117 Multiprocessing options:
118 jobs=number (with the number of jobs to be used to run the tests)
119 split_jobs='module'|'tests'
120 if == module, a given job will always receive all the tests from a module
121 if == tests, the tests will be split independently of their originating module (default)
122
123 --exclude_files = comma-separated list of patterns with files to exclude (fnmatch style)
124 --include_files = comma-separated list of patterns with files to include (fnmatch style)
125 --exclude_tests = comma-separated list of patterns with test names to exclude (fnmatch style)
126
127 Note: if --tests is given, --exclude_files, --include_files and --exclude_tests are ignored!
128 """
129 if argv is None:
130 argv = sys.argv
131
132 verbosity = 2
133 include_tests = None
134 tests = None
135 port = None
136 jobs = 1
137 split_jobs = 'tests'
138 files_to_tests = {}
139 coverage_output_dir = None
140 coverage_include = None
141 exclude_files = None
142 exclude_tests = None
143 include_files = None
144 django = False
145
146 from _pydev_getopt import gnu_getopt
147 optlist, dirs = gnu_getopt(
148 argv[1:], "",
149 [
150 "verbosity=",
151 "tests=",
152
153 "port=",
154 "config_file=",
155
156 "jobs=",
157 "split_jobs=",
158
159 "include_tests=",
160 "include_files=",
161
162 "exclude_files=",
163 "exclude_tests=",
164
165 "coverage_output_dir=",
166 "coverage_include=",
167
168 "django="
169 ]
170 )
171
172 for opt, value in optlist:
173 if opt in ("-v", "--verbosity"):
174 verbosity = value
175
176 elif opt in ("-p", "--port"):
177 port = int(value)
178
179 elif opt in ("-j", "--jobs"):
180 jobs = int(value)
181
182 elif opt in ("-s", "--split_jobs"):
183 split_jobs = value
184 if split_jobs not in ('module', 'tests'):
185 raise AssertionError('Expected split to be either "module" or "tests". Was :%s' % (split_jobs,))
186
187 elif opt in ("-d", "--coverage_output_dir",):
188 coverage_output_dir = value.strip()
189
190 elif opt in ("-i", "--coverage_include",):
191 coverage_include = value.strip()
192
193 elif opt in ("-I", "--include_tests"):
194 include_tests = value.split(',')
195
196 elif opt in ("-E", "--exclude_files"):
197 exclude_files = value.split(',')
198
199 elif opt in ("-F", "--include_files"):
200 include_files = value.split(',')
201
202 elif opt in ("-e", "--exclude_tests"):
203 exclude_tests = value.split(',')
204
205 elif opt in ("-t", "--tests"):
206 tests = value.split(',')
207
208 elif opt in ("--django",):
209 django = value.strip() in ['true', 'True', '1']
210
211 elif opt in ("-c", "--config_file"):
212 config_file = value.strip()
213 if os.path.exists(config_file):
214 f = open(config_file, 'rU')
215 try:
216 config_file_contents = f.read()
217 finally:
218 f.close()
219
220 if config_file_contents:
221 config_file_contents = config_file_contents.strip()
222
223 if config_file_contents:
224 for line in config_file_contents.splitlines():
225 file_and_test = line.split('|')
226 if len(file_and_test) == 2:
227 file, test = file_and_test
228 if DictContains(files_to_tests, file):
229 files_to_tests[file].append(test)
230 else:
231 files_to_tests[file] = [test]
232
233 else:
234 sys.stderr.write('Could not find config file: %s\n' % (config_file,))
235
236 if type([]) != type(dirs):
237 dirs = [dirs]
238
239 ret_dirs = []
240 for d in dirs:
241 if '|' in d:
242 #paths may come from the ide separated by |
243 ret_dirs.extend(d.split('|'))
244 else:
245 ret_dirs.append(d)
246
247 verbosity = int(verbosity)
248
249 if tests:
250 if verbosity > 4:
251 sys.stdout.write('--tests provided. Ignoring --exclude_files, --exclude_tests and --include_files\n')
252 exclude_files = exclude_tests = include_files = None
253
254 config = Configuration(
255 ret_dirs,
256 verbosity,
257 include_tests,
258 tests,
259 port,
260 files_to_tests,
261 jobs,
262 split_jobs,
263 coverage_output_dir,
264 coverage_include,
265 exclude_files=exclude_files,
266 exclude_tests=exclude_tests,
267 include_files=include_files,
268 django=django,
269 )
270
271 if verbosity > 5:
272 sys.stdout.write(str(config) + '\n')
273 return config
274
275
276#=======================================================================================================================
277# PydevTestRunner
278#=======================================================================================================================
279class PydevTestRunner(object):
280 """ finds and runs a file or directory of files as a unit test """
281
282 __py_extensions = ["*.py", "*.pyw"]
283 __exclude_files = ["__init__.*"]
284
285 #Just to check that only this attributes will be written to this file
286 __slots__ = [
287 'verbosity', #Always used
288
289 'files_to_tests', #If this one is given, the ones below are not used
290
291 'files_or_dirs', #Files or directories received in the command line
292 'include_tests', #The filter used to collect the tests
293 'tests', #Strings with the tests to be run
294
295 'jobs', #Integer with the number of jobs that should be used to run the test cases
296 'split_jobs', #String with 'tests' or 'module' (how should the jobs be split)
297
298 'configuration',
299 'coverage',
300 ]
301
302 def __init__(self, configuration):
303 self.verbosity = configuration.verbosity
304
305 self.jobs = configuration.jobs
306 self.split_jobs = configuration.split_jobs
307
308 files_to_tests = configuration.files_to_tests
309 if files_to_tests:
310 self.files_to_tests = files_to_tests
311 self.files_or_dirs = list(files_to_tests.keys())
312 self.tests = None
313 else:
314 self.files_to_tests = {}
315 self.files_or_dirs = configuration.files_or_dirs
316 self.tests = configuration.tests
317
318 self.configuration = configuration
319 self.__adjust_path()
320
321
322 def __adjust_path(self):
323 """ add the current file or directory to the python path """
324 path_to_append = None
325 for n in xrange(len(self.files_or_dirs)):
326 dir_name = self.__unixify(self.files_or_dirs[n])
327 if os.path.isdir(dir_name):
328 if not dir_name.endswith("/"):
329 self.files_or_dirs[n] = dir_name + "/"
330 path_to_append = os.path.normpath(dir_name)
331 elif os.path.isfile(dir_name):
332 path_to_append = os.path.dirname(dir_name)
333 else:
334 if not os.path.exists(dir_name):
335 block_line = '*' * 120
336 sys.stderr.write('\n%s\n* PyDev test runner error: %s does not exist.\n%s\n' % (block_line, dir_name, block_line))
337 return
338 msg = ("unknown type. \n%s\nshould be file or a directory.\n" % (dir_name))
339 raise RuntimeError(msg)
340 if path_to_append is not None:
341 #Add it as the last one (so, first things are resolved against the default dirs and
342 #if none resolves, then we try a relative import).
343 sys.path.append(path_to_append)
344
345 def __is_valid_py_file(self, fname):
346 """ tests that a particular file contains the proper file extension
347 and is not in the list of files to exclude """
348 is_valid_fname = 0
349 for invalid_fname in self.__class__.__exclude_files:
350 is_valid_fname += int(not fnmatch.fnmatch(fname, invalid_fname))
351 if_valid_ext = 0
352 for ext in self.__class__.__py_extensions:
353 if_valid_ext += int(fnmatch.fnmatch(fname, ext))
354 return is_valid_fname > 0 and if_valid_ext > 0
355
356 def __unixify(self, s):
357 """ stupid windows. converts the backslash to forwardslash for consistency """
358 return os.path.normpath(s).replace(os.sep, "/")
359
360 def __importify(self, s, dir=False):
361 """ turns directory separators into dots and removes the ".py*" extension
362 so the string can be used as import statement """
363 if not dir:
364 dirname, fname = os.path.split(s)
365
366 if fname.count('.') > 1:
367 #if there's a file named xxx.xx.py, it is not a valid module, so, let's not load it...
368 return
369
370 imp_stmt_pieces = [dirname.replace("\\", "/").replace("/", "."), os.path.splitext(fname)[0]]
371
372 if len(imp_stmt_pieces[0]) == 0:
373 imp_stmt_pieces = imp_stmt_pieces[1:]
374
375 return ".".join(imp_stmt_pieces)
376
377 else: #handle dir
378 return s.replace("\\", "/").replace("/", ".")
379
380 def __add_files(self, pyfiles, root, files):
381 """ if files match, appends them to pyfiles. used by os.path.walk fcn """
382 for fname in files:
383 if self.__is_valid_py_file(fname):
384 name_without_base_dir = self.__unixify(os.path.join(root, fname))
385 pyfiles.append(name_without_base_dir)
386
387
388 def find_import_files(self):
389 """ return a list of files to import """
390 if self.files_to_tests:
391 pyfiles = self.files_to_tests.keys()
392 else:
393 pyfiles = []
394
395 for base_dir in self.files_or_dirs:
396 if os.path.isdir(base_dir):
397 if hasattr(os, 'walk'):
398 for root, dirs, files in os.walk(base_dir):
399
400 #Note: handling directories that should be excluded from the search because
401 #they don't have __init__.py
402 exclude = {}
403 for d in dirs:
404 for init in ['__init__.py', '__init__.pyo', '__init__.pyc', '__init__.pyw']:
405 if os.path.exists(os.path.join(root, d, init).replace('\\', '/')):
406 break
407 else:
408 exclude[d] = 1
409
410 if exclude:
411 new = []
412 for d in dirs:
413 if d not in exclude:
414 new.append(d)
415
416 dirs[:] = new
417
418 self.__add_files(pyfiles, root, files)
419 else:
420 # jython2.1 is too old for os.walk!
421 os.path.walk(base_dir, self.__add_files, pyfiles)
422
423 elif os.path.isfile(base_dir):
424 pyfiles.append(base_dir)
425
426 if self.configuration.exclude_files or self.configuration.include_files:
427 ret = []
428 for f in pyfiles:
429 add = True
430 basename = os.path.basename(f)
431 if self.configuration.include_files:
432 add = False
433
434 for pat in self.configuration.include_files:
435 if fnmatch.fnmatchcase(basename, pat):
436 add = True
437 break
438
439 if not add:
440 if self.verbosity > 3:
441 sys.stdout.write('Skipped file: %s (did not match any include_files pattern: %s)\n' % (f, self.configuration.include_files))
442
443 elif self.configuration.exclude_files:
444 for pat in self.configuration.exclude_files:
445 if fnmatch.fnmatchcase(basename, pat):
446 if self.verbosity > 3:
447 sys.stdout.write('Skipped file: %s (matched exclude_files pattern: %s)\n' % (f, pat))
448
449 elif self.verbosity > 2:
450 sys.stdout.write('Skipped file: %s\n' % (f,))
451
452 add = False
453 break
454
455 if add:
456 if self.verbosity > 3:
457 sys.stdout.write('Adding file: %s for test discovery.\n' % (f,))
458 ret.append(f)
459
460 pyfiles = ret
461
462
463 return pyfiles
464
465 def __get_module_from_str(self, modname, print_exception, pyfile):
466 """ Import the module in the given import path.
467 * Returns the "final" module, so importing "coilib40.subject.visu"
468 returns the "visu" module, not the "coilib40" as returned by __import__ """
469 try:
470 mod = __import__(modname)
471 for part in modname.split('.')[1:]:
472 mod = getattr(mod, part)
473 return mod
474 except:
475 if print_exception:
476 import pydev_runfiles_xml_rpc
477 import pydevd_io
478 buf_err = pydevd_io.StartRedirect(keep_original_redirection=True, std='stderr')
479 buf_out = pydevd_io.StartRedirect(keep_original_redirection=True, std='stdout')
480 try:
481 import traceback;traceback.print_exc()
482 sys.stderr.write('ERROR: Module: %s could not be imported (file: %s).\n' % (modname, pyfile))
483 finally:
484 pydevd_io.EndRedirect('stderr')
485 pydevd_io.EndRedirect('stdout')
486
487 pydev_runfiles_xml_rpc.notifyTest(
488 'error', buf_out.getvalue(), buf_err.getvalue(), pyfile, modname, 0)
489
490 return None
491
492 def find_modules_from_files(self, pyfiles):
493 """ returns a list of modules given a list of files """
494 #let's make sure that the paths we want are in the pythonpath...
495 imports = [(s, self.__importify(s)) for s in pyfiles]
496
497 system_paths = []
498 for s in sys.path:
499 system_paths.append(self.__importify(s, True))
500
501
502 ret = []
503 for pyfile, imp in imports:
504 if imp is None:
505 continue #can happen if a file is not a valid module
506 choices = []
507 for s in system_paths:
508 if imp.startswith(s):
509 add = imp[len(s) + 1:]
510 if add:
511 choices.append(add)
512 #sys.stdout.write(' ' + add + ' ')
513
514 if not choices:
515 sys.stdout.write('PYTHONPATH not found for file: %s\n' % imp)
516 else:
517 for i, import_str in enumerate(choices):
518 print_exception = i == len(choices) - 1
519 mod = self.__get_module_from_str(import_str, print_exception, pyfile)
520 if mod is not None:
521 ret.append((pyfile, mod, import_str))
522 break
523
524
525 return ret
526
527 #===================================================================================================================
528 # GetTestCaseNames
529 #===================================================================================================================
530 class GetTestCaseNames:
531 """Yes, we need a class for that (cannot use outer context on jython 2.1)"""
532
533 def __init__(self, accepted_classes, accepted_methods):
534 self.accepted_classes = accepted_classes
535 self.accepted_methods = accepted_methods
536
537 def __call__(self, testCaseClass):
538 """Return a sorted sequence of method names found within testCaseClass"""
539 testFnNames = []
540 className = testCaseClass.__name__
541
542 if DictContains(self.accepted_classes, className):
543 for attrname in dir(testCaseClass):
544 #If a class is chosen, we select all the 'test' methods'
545 if attrname.startswith('test') and hasattr(getattr(testCaseClass, attrname), '__call__'):
546 testFnNames.append(attrname)
547
548 else:
549 for attrname in dir(testCaseClass):
550 #If we have the class+method name, we must do a full check and have an exact match.
551 if DictContains(self.accepted_methods, className + '.' + attrname):
552 if hasattr(getattr(testCaseClass, attrname), '__call__'):
553 testFnNames.append(attrname)
554
555 #sorted() is not available in jython 2.1
556 testFnNames.sort()
557 return testFnNames
558
559
560 def _decorate_test_suite(self, suite, pyfile, module_name):
561 if isinstance(suite, unittest.TestSuite):
562 add = False
563 suite.__pydev_pyfile__ = pyfile
564 suite.__pydev_module_name__ = module_name
565
566 for t in suite._tests:
567 t.__pydev_pyfile__ = pyfile
568 t.__pydev_module_name__ = module_name
569 if self._decorate_test_suite(t, pyfile, module_name):
570 add = True
571
572 return add
573
574 elif isinstance(suite, unittest.TestCase):
575 return True
576
577 else:
578 return False
579
580
581
582 def find_tests_from_modules(self, file_and_modules_and_module_name):
583 """ returns the unittests given a list of modules """
584 #Use our own suite!
585 unittest.TestLoader.suiteClass = pydev_runfiles_unittest.PydevTestSuite
586 loader = unittest.TestLoader()
587
588 ret = []
589 if self.files_to_tests:
590 for pyfile, m, module_name in file_and_modules_and_module_name:
591 accepted_classes = {}
592 accepted_methods = {}
593 tests = self.files_to_tests[pyfile]
594 for t in tests:
595 accepted_methods[t] = t
596
597 loader.getTestCaseNames = self.GetTestCaseNames(accepted_classes, accepted_methods)
598
599 suite = loader.loadTestsFromModule(m)
600 if self._decorate_test_suite(suite, pyfile, module_name):
601 ret.append(suite)
602 return ret
603
604
605 if self.tests:
606 accepted_classes = {}
607 accepted_methods = {}
608
609 for t in self.tests:
610 splitted = t.split('.')
611 if len(splitted) == 1:
612 accepted_classes[t] = t
613
614 elif len(splitted) == 2:
615 accepted_methods[t] = t
616
617 loader.getTestCaseNames = self.GetTestCaseNames(accepted_classes, accepted_methods)
618
619
620 for pyfile, m, module_name in file_and_modules_and_module_name:
621 suite = loader.loadTestsFromModule(m)
622 if self._decorate_test_suite(suite, pyfile, module_name):
623 ret.append(suite)
624
625 return ret
626
627
628 def filter_tests(self, test_objs, internal_call=False):
629 """ based on a filter name, only return those tests that have
630 the test case names that match """
631 if not internal_call:
632 if not self.configuration.include_tests and not self.tests and not self.configuration.exclude_tests:
633 #No need to filter if we have nothing to filter!
634 return test_objs
635
636 if self.verbosity > 1:
637 if self.configuration.include_tests:
638 sys.stdout.write('Tests to include: %s\n' % (self.configuration.include_tests,))
639
640 if self.tests:
641 sys.stdout.write('Tests to run: %s\n' % (self.tests,))
642
643 if self.configuration.exclude_tests:
644 sys.stdout.write('Tests to exclude: %s\n' % (self.configuration.exclude_tests,))
645
646 test_suite = []
647 for test_obj in test_objs:
648
649 if isinstance(test_obj, unittest.TestSuite):
650 #Note: keep the suites as they are and just 'fix' the tests (so, don't use the iter_tests).
651 if test_obj._tests:
652 test_obj._tests = self.filter_tests(test_obj._tests, True)
653 if test_obj._tests: #Only add the suite if we still have tests there.
654 test_suite.append(test_obj)
655
656 elif isinstance(test_obj, unittest.TestCase):
657 try:
658 testMethodName = test_obj._TestCase__testMethodName
659 except AttributeError:
660 #changed in python 2.5
661 testMethodName = test_obj._testMethodName
662
663 add = True
664 if self.configuration.exclude_tests:
665 for pat in self.configuration.exclude_tests:
666 if fnmatch.fnmatchcase(testMethodName, pat):
667 if self.verbosity > 3:
668 sys.stdout.write('Skipped test: %s (matched exclude_tests pattern: %s)\n' % (testMethodName, pat))
669
670 elif self.verbosity > 2:
671 sys.stdout.write('Skipped test: %s\n' % (testMethodName,))
672
673 add = False
674 break
675
676 if add:
677 if self.__match_tests(self.tests, test_obj, testMethodName):
678 include = True
679 if self.configuration.include_tests:
680 include = False
681 for pat in self.configuration.include_tests:
682 if fnmatch.fnmatchcase(testMethodName, pat):
683 include = True
684 break
685 if include:
686 test_suite.append(test_obj)
687 else:
688 if self.verbosity > 3:
689 sys.stdout.write('Skipped test: %s (did not match any include_tests pattern %s)\n' % (self.configuration.include_tests,))
690 return test_suite
691
692
693 def iter_tests(self, test_objs):
694 #Note: not using yield because of Jython 2.1.
695 tests = []
696 for test_obj in test_objs:
697 if isinstance(test_obj, unittest.TestSuite):
698 tests.extend(self.iter_tests(test_obj._tests))
699
700 elif isinstance(test_obj, unittest.TestCase):
701 tests.append(test_obj)
702 return tests
703
704
705 def list_test_names(self, test_objs):
706 names = []
707 for tc in self.iter_tests(test_objs):
708 try:
709 testMethodName = tc._TestCase__testMethodName
710 except AttributeError:
711 #changed in python 2.5
712 testMethodName = tc._testMethodName
713 names.append(testMethodName)
714 return names
715
716
717 def __match_tests(self, tests, test_case, test_method_name):
718 if not tests:
719 return 1
720
721 for t in tests:
722 class_and_method = t.split('.')
723 if len(class_and_method) == 1:
724 #only class name
725 if class_and_method[0] == test_case.__class__.__name__:
726 return 1
727
728 elif len(class_and_method) == 2:
729 if class_and_method[0] == test_case.__class__.__name__ and class_and_method[1] == test_method_name:
730 return 1
731
732 return 0
733
734
735 def __match(self, filter_list, name):
736 """ returns whether a test name matches the test filter """
737 if filter_list is None:
738 return 1
739 for f in filter_list:
740 if re.match(f, name):
741 return 1
742 return 0
743
744
745 def run_tests(self, handle_coverage=True):
746 """ runs all tests """
747 sys.stdout.write("Finding files... ")
748 files = self.find_import_files()
749 if self.verbosity > 3:
750 sys.stdout.write('%s ... done.\n' % (self.files_or_dirs))
751 else:
752 sys.stdout.write('done.\n')
753 sys.stdout.write("Importing test modules ... ")
754
755
756 if handle_coverage:
757 coverage_files, coverage = StartCoverageSupport(self.configuration)
758
759 file_and_modules_and_module_name = self.find_modules_from_files(files)
760 sys.stdout.write("done.\n")
761
762 all_tests = self.find_tests_from_modules(file_and_modules_and_module_name)
763 all_tests = self.filter_tests(all_tests)
764
765 test_suite = pydev_runfiles_unittest.PydevTestSuite(all_tests)
766 import pydev_runfiles_xml_rpc
767 pydev_runfiles_xml_rpc.notifyTestsCollected(test_suite.countTestCases())
768
769 start_time = time.time()
770
771 def run_tests():
772 executed_in_parallel = False
773 if self.jobs > 1:
774 import pydev_runfiles_parallel
775
776 #What may happen is that the number of jobs needed is lower than the number of jobs requested
777 #(e.g.: 2 jobs were requested for running 1 test) -- in which case ExecuteTestsInParallel will
778 #return False and won't run any tests.
779 executed_in_parallel = pydev_runfiles_parallel.ExecuteTestsInParallel(
780 all_tests, self.jobs, self.split_jobs, self.verbosity, coverage_files, self.configuration.coverage_include)
781
782 if not executed_in_parallel:
783 #If in coverage, we don't need to pass anything here (coverage is already enabled for this execution).
784 runner = pydev_runfiles_unittest.PydevTextTestRunner(stream=sys.stdout, descriptions=1, verbosity=self.verbosity)
785 sys.stdout.write('\n')
786 runner.run(test_suite)
787
788 if self.configuration.django:
789 MyDjangoTestSuiteRunner(run_tests).run_tests([])
790 else:
791 run_tests()
792
793 if handle_coverage:
794 coverage.stop()
795 coverage.save()
796
797 total_time = 'Finished in: %.2f secs.' % (time.time() - start_time,)
798 pydev_runfiles_xml_rpc.notifyTestRunFinished(total_time)
799
800
801try:
802 from django.test.simple import DjangoTestSuiteRunner
803except:
804 class DjangoTestSuiteRunner:
805 def __init__(self):
806 pass
807
808 def run_tests(self, *args, **kwargs):
809 raise AssertionError("Unable to run suite with DjangoTestSuiteRunner because it couldn't be imported.")
810
811class MyDjangoTestSuiteRunner(DjangoTestSuiteRunner):
812
813 def __init__(self, on_run_suite):
814 DjangoTestSuiteRunner.__init__(self)
815 self.on_run_suite = on_run_suite
816
817 def build_suite(self, *args, **kwargs):
818 pass
819
820 def suite_result(self, *args, **kwargs):
821 pass
822
823 def run_suite(self, *args, **kwargs):
824 self.on_run_suite()
825
826
827#=======================================================================================================================
828# main
829#=======================================================================================================================
830def main(configuration):
831 PydevTestRunner(configuration).run_tests()