Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2014 The Chromium Authors. All rights reserved. | 1 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
| 3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
| 4 | 4 |
| 5 import Queue | 5 import ConfigParser |
| 6 import glob | 6 import glob |
| 7 import imp | 7 import imp |
| 8 import inspect | 8 import inspect |
| 9 import logging | 9 import logging |
| 10 import multiprocessing | 10 import multiprocessing |
| 11 import os | 11 import os |
| 12 import Queue | |
| 12 import re | 13 import re |
| 13 import signal | 14 import signal |
| 14 import sys | 15 import sys |
| 15 import tempfile | 16 import tempfile |
| 16 import traceback | 17 import traceback |
| 17 | 18 |
| 18 from cStringIO import StringIO | 19 from cStringIO import StringIO |
| 19 | 20 |
| 20 from expect_tests.tempdir import TempDir | 21 from expect_tests.tempdir import TempDir |
| 21 from expect_tests.type_definitions import ( | 22 from expect_tests.type_definitions import ( |
| 22 Test, UnknownError, TestError, NoMatchingTestsError, MultiTest, | 23 Test, UnknownError, TestError, NoMatchingTestsError, MultiTest, |
| 23 Result, ResultStageAbort) | 24 Result, ResultStageAbort) |
| 24 | 25 |
| 25 from expect_tests.unittest_helper import _is_unittest, UnittestTestCase | 26 from expect_tests.unittest_helper import _is_unittest, UnittestTestCase |
| 26 from expect_tests import util | 27 from expect_tests import util |
| 27 | 28 |
| 28 | 29 |
| 30 CONFIG_FILE_NAME = '.expect_tests.cfg' | |
| 31 | |
| 32 | |
| 29 class ResetableStringIO(object): | 33 class ResetableStringIO(object): |
| 30 def __init__(self): | 34 def __init__(self): |
| 31 self._stream = StringIO() | 35 self._stream = StringIO() |
| 32 | 36 |
| 33 def reset(self): | 37 def reset(self): |
| 34 self._stream = StringIO() | 38 self._stream = StringIO() |
| 35 | 39 |
| 36 def __getattr__(self, key): | 40 def __getattr__(self, key): |
| 37 return getattr(self._stream, key) | 41 return getattr(self._stream, key) |
| 38 | 42 |
| 39 | 43 |
| 40 def get_package_path(package_name, path): | 44 def get_package_path(package_name, path): |
| 41 """Return path toward 'package_name'. | 45 """Return path toward 'package_name'. |
| 42 | 46 |
| 43 If path is None, search for a package in sys.path. | 47 If path is None, search for a package in sys.path. |
| 44 Otherwise, look for a direct subdirectory of path. | 48 Otherwise, look for a direct subdirectory of path. |
| 45 | 49 |
| 46 If no package is found, returns None. | 50 If no package is found, returns None. |
| 47 """ | 51 """ |
| 48 if path is None: | 52 if path is None: |
| 49 _, package_path, _ = imp.find_module(package_name) | 53 _, package_path, _ = imp.find_module(package_name) |
| 50 else: | 54 else: |
| 51 package_path = os.path.join(path, package_name) | 55 package_path = os.path.join(path, package_name) |
| 52 if not os.path.exists(package_path): | 56 if not os.path.exists(package_path): |
| 53 raise ValueError('File not found: %s' % package_path) | 57 raise ValueError('File not found: %s' % package_path) |
| 54 ispkg = os.path.isfile(os.path.join(package_path, '__init__.py')) | 58 ispkg = os.path.isfile(os.path.join(package_path, '__init__.py')) |
| 55 return os.path.normpath(package_path) if ispkg else None | 59 return os.path.normpath(package_path) if ispkg else None |
| 56 | 60 |
| 57 | 61 |
| 58 def get_blacklist(path): | 62 def get_config(path): |
| 59 """Get blacklisted subdirectories | 63 """Get configuration values |
| 60 | 64 |
| 61 Reads the file called __expect_tests_skip in provided path, remove | 65 Reads the config file in provided path, and returns content. |
| 62 comments, and returns content. | 66 See Python ConfigParser for general formatting syntax. |
| 63 | 67 |
| 64 A comment starts with #, on its own line (no trailing comment). | 68 Example: |
| 69 [expect_tests] | |
| 70 skip=directory1 | |
| 71 directory2 | |
| 72 directory3 | |
| 65 | 73 |
| 66 Args: | 74 Args: |
| 67 path (str): path to a directory. | 75 path (str): path to a directory. |
| 68 | 76 |
| 69 Returns: | 77 Returns: |
| 70 blacklist (set of str): name of blacklisted subdirectories. | 78 black_list (set): blacklisted subdirectories. |
| 71 """ | 79 """ |
| 72 black_list = set() | 80 black_list = set() |
| 73 black_list_filename = os.path.join(path, '__expect_tests_skip') | |
| 74 if os.path.isfile(black_list_filename): | |
| 75 with open(black_list_filename, 'r') as f: | |
| 76 for line in f: | |
| 77 stripped = line.strip() | |
| 78 if stripped and not stripped.startswith('#'): | |
| 79 black_list.add(stripped) | |
| 80 | 81 |
| 82 config_file_name = os.path.join(path, CONFIG_FILE_NAME) | |
| 83 parser = ConfigParser.ConfigParser() | |
| 84 parser.read([config_file_name]) | |
| 85 | |
| 86 if not parser.has_section('expect_tests'): | |
| 87 return black_list | |
| 88 | |
| 89 if parser.has_option('expect_tests', 'skip'): | |
| 90 black_list.update(parser.get('expect_tests', 'skip').splitlines()) | |
| 81 return black_list | 91 return black_list |
| 82 | 92 |
| 83 | 93 |
| 84 def is_test_file(filename): | 94 def is_test_file(filename): |
| 85 """Returns True if filename is supposed to contain tests. | 95 """Returns True if filename is supposed to contain tests. |
| 86 | 96 |
| 87 Args: | 97 Args: |
| 88 filename (str): path to a python file. | 98 filename (str): path to a python file. |
| 89 | 99 |
| 90 Returns: | 100 Returns: |
| 91 is_test (boolean): True if filename points to a test file. | 101 is_test (boolean): True if filename points to a test file. |
| 92 """ | 102 """ |
| 93 return filename.endswith('_test.py') | 103 return filename.endswith('_test.py') |
| 94 | 104 |
| 95 | 105 |
| 96 def walk_package(package_name, path): | 106 def walk_package(package_name, path): |
| 97 """Yield all test files inside a single package. | 107 """Return all test files inside a single package. |
| 98 | 108 |
| 99 In all cases, this function returns the full package name of files ending | 109 In all cases, this function returns the full package name of files ending |
| 100 in '_test.py' found either under the package called <package_name>. | 110 in '_test.py' found either under the package called <package_name>. |
| 101 Example: 'test_package.foo_test' for 'test_package/foo_test.py'. | 111 Example: 'test_package.foo_test' for 'test_package/foo_test.py'. |
| 102 | 112 |
| 103 Provided that <path> is in sys.path, calling __import__ with one of the | 113 Provided that <path> is in sys.path, calling __import__ with one of the |
| 104 strings returned by this function works. | 114 strings returned by this function works. |
| 105 | 115 |
| 106 If a __expect_tests_skip file is present somewhere in the search hierarchy, | 116 If a config file is present somewhere in the search hierarchy, |
| 107 it is interpreted as a list of subdirectories to ignore. This is the way to | 117 it is interpreted as a list of subdirectories to ignore. This is the way to |
| 108 make this function ignore some subpackages. | 118 make this function ignore some subpackages. |
| 109 | 119 |
| 110 This function has several behaviors depending on the arguments: | 120 This function has several behaviors depending on the arguments: |
| 111 - if path is None: search for package_name under any path in sys.path. | 121 - if path is None: search for package_name under any path in sys.path. |
| 112 - if path is not None: search for a package called package_name directly | 122 - if path is not None: search for a package called package_name directly |
| 113 under <path> (sys.path is not used). | 123 under <path> (sys.path is not used). |
| 114 | 124 |
| 115 It is not necessary to change sys.path for the present function to work (it | 125 It is not necessary to change sys.path for the present function to work (it |
| 116 does not actually import anything). | 126 does not actually import anything). |
| 117 | 127 |
| 128 Args: | |
| 129 package_name (str): name of the package, as expected by import. | |
| 130 path (str): path containing the above module (optional) | |
| 131 | |
| 132 Returns: | |
| 133 test_modules (list of str): name of modules containing tests. Each element is | |
| 134 a period-separated string ending with '_test', | |
| 135 e.g. shiny_package.subpackage.feature_test | |
| 136 | |
| 118 Example: | 137 Example: |
| 119 modules = walk_package('shiny_package', 'some/directory') | 138 modules = walk_package('shiny_package', 'some/directory') |
| 120 sys.path.insert(0, 'some/directory') | 139 sys.path[:] = sys.path.insert(0, 'some/directory') |
| 121 __import__(modules[0]) | 140 __import__(modules[0]) |
| 122 | 141 |
| 123 the first line assumes that the directory 'some/directory' is in the | 142 the first line assumes that the directory 'some/directory' is in the |
| 124 current working directory. | 143 current working directory. |
| 125 | |
| 126 modules[0] is a period-separated string ending with '_test', | |
| 127 e.g. shiny_package.subpackage.feature_test | |
| 128 """ | 144 """ |
| 129 assert package_name, 'walk_package needs a package_name.' | 145 assert package_name, 'walk_package needs a package_name.' |
| 130 | 146 |
| 131 ret = [] | 147 test_modules = [] |
| 132 package_path = get_package_path(package_name, path) | 148 package_path = get_package_path(package_name, path) |
| 133 assert package_path, 'no package found.' | 149 assert package_path, 'no package found.' |
| 134 | 150 |
| 135 base_path = os.path.split(package_path.rstrip(os.path.sep))[0] | 151 base_path = os.path.split(package_path.rstrip(os.path.sep))[0] |
| 136 assert package_path.startswith(base_path) | 152 assert package_path.startswith(base_path) |
| 137 | 153 |
| 138 for dirpath, dirnames, filenames in os.walk(package_path): | 154 for dirpath, dirnames, filenames in os.walk(package_path): |
| 139 # Keep only submodules not blacklisted | 155 # Keep only submodules not blacklisted |
| 140 blacklist = get_blacklist(dirpath) | 156 blacklist = get_config(dirpath) |
| 141 | |
| 142 dirnames[:] = [d for d in dirnames | 157 dirnames[:] = [d for d in dirnames |
| 143 if d not in blacklist and | 158 if d not in blacklist and |
| 144 os.path.isfile(os.path.join(dirpath, d, '__init__.py'))] | 159 os.path.isfile(os.path.join(dirpath, d, '__init__.py'))] |
| 145 | 160 |
| 146 assert dirpath.startswith(package_path) | 161 assert dirpath.startswith(package_path) |
| 147 base_module_name = os.path.relpath(dirpath, base_path).split(os.path.sep) | 162 base_module_name = os.path.relpath(dirpath, base_path).split(os.path.sep) |
| 148 ret += ['.'.join(base_module_name + [inspect.getmodulename(filename)]) | 163 test_modules += ['.'.join(base_module_name |
|
pgervais
2014/09/20 00:30:06
This should be .extend(), sigh.
| |
| 149 for filename in filenames | 164 + [inspect.getmodulename(filename)]) |
| 150 if is_test_file(filename)] | 165 for filename in filenames |
| 166 if is_test_file(filename)] | |
| 151 | 167 |
| 152 return ret | 168 return test_modules |
| 153 | 169 |
| 154 | 170 |
| 155 def load_module(modname): | 171 def load_module(modname): |
| 156 """Import and return the specified module. | 172 """Import and return the specified module. |
| 157 | 173 |
| 158 Uses __import__ instead of pkgutil's PEP302-style find_module/load_module | 174 Uses __import__ instead of pkgutil's PEP302-style find_module/load_module |
| 159 because those just don't work. The tradeoff is that we have to walk down the | 175 because those just don't work. The tradeoff is that we have to walk down the |
| 160 package hierarchy to find the leaf module (since __import__ returns the | 176 package hierarchy to find the leaf module (since __import__ returns the |
| 161 topmost parent package), but at least this behaves deterministically. | 177 topmost parent package), but at least this behaves deterministically. |
| 162 """ | 178 """ |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 173 See UnittestTestCase for possible return values. | 189 See UnittestTestCase for possible return values. |
| 174 | 190 |
| 175 This function loads modules, thus no two conflicting packages (like appengine) | 191 This function loads modules, thus no two conflicting packages (like appengine) |
| 176 can be loaded at the same time: use separate processes for that. | 192 can be loaded at the same time: use separate processes for that. |
| 177 """ | 193 """ |
| 178 assert isinstance(path, basestring), 'path must be a string' | 194 assert isinstance(path, basestring), 'path must be a string' |
| 179 assert os.path.isdir(path), 'path is not a directory: %s' % path | 195 assert os.path.isdir(path), 'path is not a directory: %s' % path |
| 180 sys.path.insert(0, os.path.abspath(path)) | 196 sys.path.insert(0, os.path.abspath(path)) |
| 181 | 197 |
| 182 test_gens = [] | 198 test_gens = [] |
| 183 black_list = get_blacklist(path) | 199 black_list = get_config(path) |
| 184 | 200 |
| 185 for filename in filter(lambda x: x not in black_list, os.listdir(path)): | 201 for filename in filter(lambda x: x not in black_list, os.listdir(path)): |
| 186 abs_filename = os.path.join(path, filename) | 202 abs_filename = os.path.join(path, filename) |
| 187 if (os.path.isdir(abs_filename) | 203 if (os.path.isdir(abs_filename) |
| 188 and os.path.isfile(os.path.join(abs_filename, '__init__.py'))): | 204 and os.path.isfile(os.path.join(abs_filename, '__init__.py'))): |
| 189 test_gens += get_test_gens_package(abs_filename, | 205 test_gens += get_test_gens_package(abs_filename, |
| 190 update_syspath=False) | 206 update_syspath=False) |
| 191 return test_gens | 207 return test_gens |
| 192 | 208 |
| 193 | 209 |
| (...skipping 327 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 521 | 537 |
| 522 if procs: | 538 if procs: |
| 523 error = opts.handler.result_stage_loop(opts, generate_objects(procs)) | 539 error = opts.handler.result_stage_loop(opts, generate_objects(procs)) |
| 524 except ResultStageAbort: | 540 except ResultStageAbort: |
| 525 pass | 541 pass |
| 526 | 542 |
| 527 if not kill_switch.is_set() and not result_queue.empty(): | 543 if not kill_switch.is_set() and not result_queue.empty(): |
| 528 error = True | 544 error = True |
| 529 | 545 |
| 530 return error, kill_switch.is_set() | 546 return error, kill_switch.is_set() |
| OLD | NEW |