| Tor Norbye | 1aa2e09 | 2014-08-20 17:01:23 -0700 | [diff] [blame] | 1 | from __future__ import nested_scopes |
| 2 | |
| 3 | import fnmatch |
| 4 | import os.path |
| 5 | from pydev_runfiles_coverage import StartCoverageSupport |
| 6 | import pydev_runfiles_unittest |
| 7 | from pydevd_constants import * #@UnusedWildImport |
| 8 | import re |
| 9 | import time |
| 10 | import unittest |
| 11 | |
| 12 | |
| 13 | #======================================================================================================================= |
| 14 | # Configuration |
| 15 | #======================================================================================================================= |
| 16 | class 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 | #======================================================================================================================= |
| 110 | def 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 | #======================================================================================================================= |
| 279 | class 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 | |
| 801 | try: |
| 802 | from django.test.simple import DjangoTestSuiteRunner |
| 803 | except: |
| 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 | |
| 811 | class 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 | #======================================================================================================================= |
| 830 | def main(configuration): |
| 831 | PydevTestRunner(configuration).run_tests() |