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 package_path if ispkg else None | 59 return 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 blacklisted subdirectories |
| 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. | |
| 63 | 66 |
| 64 A comment starts with #, on its own line (no trailing comment). | 67 A comment starts with #, on its own line (no trailing comment). |
| 65 | 68 |
| 66 Args: | 69 Args: |
| 67 path (str): path to a directory. | 70 path (str): path to a directory. |
| 68 | 71 |
| 69 Returns: | 72 Returns: |
| 70 blacklist (set of str): name of blacklisted subdirectories. | 73 blacklist (set of str): name of blacklisted subdirectories. |
| 71 """ | 74 """ |
| 72 black_list = set() | 75 black_list = set() |
| 73 black_list_filename = os.path.join(path, '__expect_tests_skip') | 76 python_path = '' |
| 74 if os.path.isfile(black_list_filename): | 77 black_list_filename = os.path.join(path, CONFIG_FILE_NAME) |
|
dnj
2014/09/19 21:15:59
If you're generalizing this to config file, the va
pgervais
2014/09/19 22:06:26
Done.
| |
| 75 with open(black_list_filename, 'r') as f: | 78 parser = ConfigParser.ConfigParser() |
| 76 for line in f: | 79 parser.read([black_list_filename]) |
| 77 stripped = line.strip() | |
| 78 if stripped and not stripped.startswith('#'): | |
| 79 black_list.add(stripped) | |
| 80 | 80 |
| 81 return black_list | 81 if not parser.has_section('expect_tests'): |
| 82 return black_list, python_path | |
| 83 | |
| 84 if parser.has_option('expect_tests', 'skip'): | |
| 85 black_list = parser.get('expect_tests', 'skip').splitlines() | |
| 86 if parser.has_option('expect_tests', 'pythonpath'): | |
| 87 python_path = [os.path.normpath(os.path.join(path, s.strip())) | |
| 88 for s | |
| 89 in parser.get('expect_tests', 'pythonpath').splitlines()] | |
| 90 return black_list, python_path | |
| 82 | 91 |
| 83 | 92 |
| 84 def walk_package(package_name, path): | 93 def walk_package(package_name, path=None): |
| 85 """Yield all test files inside a single package. | 94 """Return all test files inside a single package. |
| 86 | 95 |
| 87 In all cases, this function returns the full package name of files ending | 96 In all cases, this function returns the full package name of files ending |
| 88 in '_test.py' found either under the package called <package_name>. | 97 in '_test.py' found either under the package called <package_name>. |
| 89 Example: 'test_package.foo_test' for 'test_package/foo_test.py'. | 98 Example: 'test_package.foo_test' for 'test_package/foo_test.py'. |
| 90 | 99 |
| 91 Provided that <path> is in sys.path, calling __import__ with one of the | 100 Provided that <path> is in sys.path, calling __import__ with one of the |
| 92 strings returned by this function works. | 101 strings returned by this function works. |
| 93 | 102 |
| 94 If a __expect_tests_skip file is present somewhere in the search hierarchy, | 103 If a config file is present somewhere in the search hierarchy, |
| 95 it is interpreted as a list of subdirectories to ignore. This is the way to | 104 it is interpreted as a list of subdirectories to ignore. This is the way to |
| 96 make this function ignore some subpackages. | 105 make this function ignore some subpackages. |
| 97 | 106 |
| 98 This function has several behaviors depending on the arguments: | 107 This function has several behaviors depending on the arguments: |
| 99 - if path is None: search for package_name under any path in sys.path. | 108 - if path is None: search for package_name under any path in sys.path. |
| 100 - if path is not None: search for a package called package_name directly | 109 - if path is not None: search for a package called package_name directly |
| 101 under <path> (sys.path is not used). | 110 under <path> (sys.path is not used). |
| 102 | 111 |
| 103 It is not necessary to change sys.path for the present function to work (it | 112 It is not necessary to change sys.path for the present function to work (it |
| 104 does not actually import anything). | 113 does not actually import anything). |
| 105 | 114 |
| 115 Args: | |
| 116 package_name (str): name of the package, as expected by import. | |
| 117 path (str): path containing the above module (optional) | |
| 118 | |
| 119 Returns: | |
| 120 test_modules (list of str): name of modules containing tests. Each element is | |
| 121 a period-separated string ending with '_test', | |
| 122 e.g. shiny_package.subpackage.feature_test | |
| 123 pythonpath (list of str): paths to add to sys.path, as requested in | |
| 124 the config files. | |
| 125 | |
| 106 Example: | 126 Example: |
| 107 modules = walk_package('shiny_package', 'some/directory') | 127 modules, pythonpath = walk_package('shiny_package', 'some/directory') |
| 108 sys.path.insert(0, 'some/directory') | 128 sys.path[:] = ['some/directory'] + pythonpath + sys.path |
| 109 __import__(modules[0]) | 129 __import__(modules[0]) |
| 110 | 130 |
| 111 the first line assumes that the directory 'some/directory' is in the | 131 the first line assumes that the directory 'some/directory' is in the |
| 112 current working directory. | 132 current working directory. |
| 113 | |
| 114 modules[0] is a period-separated string ending with '_test', | |
| 115 e.g. shiny_package.subpackage.feature_test | |
| 116 """ | 133 """ |
| 117 assert package_name, 'walk_package needs a package_name.' | 134 assert package_name, 'walk_package needs a package_name.' |
| 118 | 135 |
| 119 # This function can be called with submodule names, but it's not a public | 136 # This function can be called with submodule names, but it's not a public |
| 120 # interface. | 137 # interface. |
| 121 subpackage_name = package_name.split('.')[-1] | 138 subpackage_name = package_name.split('.')[-1] |
| 122 | 139 |
| 123 ret = [] | 140 pythonpath = [] |
| 141 test_modules = [] | |
| 124 package_path = get_package_path(subpackage_name, path) | 142 package_path = get_package_path(subpackage_name, path) |
| 125 assert package_path, 'no package found.' | 143 assert package_path, 'no package found.' |
| 126 | 144 |
| 127 # Look for direct subpackages and test files. | 145 # Look for direct subpackages and test files. |
| 128 black_list = get_blacklist(package_path) | 146 black_list, extra_paths = get_config(package_path) |
| 147 pythonpath.extend(extra_paths) | |
| 129 for filename in filter(lambda x: x not in black_list, | 148 for filename in filter(lambda x: x not in black_list, |
| 130 os.listdir(package_path)): | 149 os.listdir(package_path)): |
| 131 if os.path.isfile(os.path.join(package_path, filename, '__init__.py')): | 150 if os.path.isfile(os.path.join(package_path, filename, '__init__.py')): |
| 132 ret += walk_package( | 151 extra_test_modules, extra_paths = walk_package( |
| 133 (package_name + '.' if package_name else '') | 152 (package_name + '.' if package_name else '') |
| 134 + filename, package_path) | 153 + filename, package_path) |
| 154 test_modules.extend(extra_test_modules) | |
| 155 pythonpath.extend(extra_paths) | |
| 135 continue | 156 continue |
| 136 if filename.endswith('_test.py'): | 157 if filename.endswith('_test.py'): |
| 137 ret.append( | 158 test_modules.append( |
| 138 (package_name + '.' if package_name else '') | 159 (package_name + '.' if package_name else '') |
| 139 + inspect.getmodulename(filename)) | 160 + inspect.getmodulename(filename)) |
| 140 return ret | 161 return test_modules, pythonpath |
| 141 | 162 |
| 142 | 163 |
| 143 def load_module(modname): | 164 def load_module(modname): |
| 144 """Import and return the specified module. | 165 """Import and return the specified module. |
| 145 | 166 |
| 146 Uses __import__ instead of pkgutil's PEP302-style find_module/load_module | 167 Uses __import__ instead of pkgutil's PEP302-style find_module/load_module |
| 147 because those just don't work. The tradeoff is that we have to walk down the | 168 because those just don't work. The tradeoff is that we have to walk down the |
| 148 package hierarchy to find the leaf module (since __import__ returns the | 169 package hierarchy to find the leaf module (since __import__ returns the |
| 149 topmost parent package), but at least this behaves deterministically. | 170 topmost parent package), but at least this behaves deterministically. |
| 150 """ | 171 """ |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 161 See UnittestTestCase for possible return values. | 182 See UnittestTestCase for possible return values. |
| 162 | 183 |
| 163 This function loads modules, thus no two conflicting packages (like appengine) | 184 This function loads modules, thus no two conflicting packages (like appengine) |
| 164 can be loaded at the same time: use separate processes for that. | 185 can be loaded at the same time: use separate processes for that. |
| 165 """ | 186 """ |
| 166 assert isinstance(path, basestring), 'path must be a string' | 187 assert isinstance(path, basestring), 'path must be a string' |
| 167 assert os.path.isdir(path), 'path is not a directory: %s' % path | 188 assert os.path.isdir(path), 'path is not a directory: %s' % path |
| 168 sys.path.insert(0, os.path.abspath(path)) | 189 sys.path.insert(0, os.path.abspath(path)) |
| 169 | 190 |
| 170 test_gens = [] | 191 test_gens = [] |
| 171 black_list = get_blacklist(path) | 192 black_list, _ = get_config(path) |
| 172 | 193 |
| 173 for filename in filter(lambda x: x not in black_list, os.listdir(path)): | 194 for filename in filter(lambda x: x not in black_list, os.listdir(path)): |
| 174 abs_filename = os.path.join(path, filename) | 195 abs_filename = os.path.join(path, filename) |
| 175 if (os.path.isdir(abs_filename) | 196 if (os.path.isdir(abs_filename) |
| 176 and os.path.isfile(os.path.join(abs_filename, '__init__.py'))): | 197 and os.path.isfile(os.path.join(abs_filename, '__init__.py'))): |
| 177 test_gens += get_test_gens_package(abs_filename, | 198 test_gens += get_test_gens_package(abs_filename, |
| 178 update_syspath=False) | 199 update_syspath=False) |
| 179 return test_gens | 200 return test_gens |
| 180 | 201 |
| 181 | 202 |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 196 assert os.path.isfile(os.path.join(package, '__init__.py')), \ | 217 assert os.path.isfile(os.path.join(package, '__init__.py')), \ |
| 197 "'package' is not pointing to a package. It must be a " + \ | 218 "'package' is not pointing to a package. It must be a " + \ |
| 198 "path to a directory containing a __init__.py file." | 219 "path to a directory containing a __init__.py file." |
| 199 | 220 |
| 200 test_gens = [] | 221 test_gens = [] |
| 201 path = os.path.abspath(os.path.dirname(package)) | 222 path = os.path.abspath(os.path.dirname(package)) |
| 202 if update_syspath: | 223 if update_syspath: |
| 203 sys.path.insert(0, path) | 224 sys.path.insert(0, path) |
| 204 package_name = os.path.split(package.rstrip(os.path.sep))[-1] | 225 package_name = os.path.split(package.rstrip(os.path.sep))[-1] |
| 205 | 226 |
| 206 for modname in walk_package(package_name, path): | 227 test_modules, pythonpath = walk_package(package_name, path) |
| 228 | |
| 229 if pythonpath: | |
| 230 sys.path[:] = pythonpath + sys.path | |
| 231 | |
| 232 for modname in test_modules: | |
| 207 mod = load_module(modname) | 233 mod = load_module(modname) |
| 208 for obj in mod.__dict__.values(): | 234 for obj in mod.__dict__.values(): |
| 209 if util.is_test_generator(obj): | 235 if util.is_test_generator(obj): |
| 210 test_gens.append(obj) | 236 test_gens.append(obj) |
| 211 elif _is_unittest(obj): | 237 elif _is_unittest(obj): |
| 212 test_gens.append(UnittestTestCase(obj)) | 238 test_gens.append(UnittestTestCase(obj)) |
| 213 return test_gens | 239 return test_gens |
| 214 | 240 |
| 215 | 241 |
| 216 def gen_loop_process(gens, test_queue, result_queue, opts, kill_switch, | 242 def gen_loop_process(gens, test_queue, result_queue, opts, kill_switch, |
| (...skipping 293 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 510 | 536 |
| 511 if procs: | 537 if procs: |
| 512 error = opts.handler.result_stage_loop(opts, generate_objects(procs)) | 538 error = opts.handler.result_stage_loop(opts, generate_objects(procs)) |
| 513 except ResultStageAbort: | 539 except ResultStageAbort: |
| 514 pass | 540 pass |
| 515 | 541 |
| 516 if not kill_switch.is_set() and not result_queue.empty(): | 542 if not kill_switch.is_set() and not result_queue.empty(): |
| 517 error = True | 543 error = True |
| 518 | 544 |
| 519 return error, kill_switch.is_set() | 545 return error, kill_switch.is_set() |
| OLD | NEW |