Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(200)

Side by Side Diff: expect_tests/pipeline.py

Issue 587493002: Config files (Closed) Base URL: https://chromium.googlesource.com/infra/testing/expect_tests@serial_exec
Patch Set: Changed config file name Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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()
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698