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

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: More cleanup 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 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 = parser.get('expect_tests', 'skip').splitlines()
dnj 2014/09/19 23:56:05 This should be 'black_list.update(...)'. Function
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
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
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 14 matching lines...) Expand all
208 assert os.path.isfile(os.path.join(package, '__init__.py')), \ 224 assert os.path.isfile(os.path.join(package, '__init__.py')), \
209 "'package' is not pointing to a package. It must be a " + \ 225 "'package' is not pointing to a package. It must be a " + \
210 "path to a directory containing a __init__.py file." 226 "path to a directory containing a __init__.py file."
211 227
212 test_gens = [] 228 test_gens = []
213 path = os.path.abspath(os.path.dirname(package)) 229 path = os.path.abspath(os.path.dirname(package))
214 if update_syspath: 230 if update_syspath:
215 sys.path.insert(0, path) 231 sys.path.insert(0, path)
216 package_name = os.path.split(package.rstrip(os.path.sep))[-1] 232 package_name = os.path.split(package.rstrip(os.path.sep))[-1]
217 233
218 for modname in walk_package(package_name, path): 234 test_modules = walk_package(package_name, path)
235
236 for modname in test_modules:
dnj 2014/09/19 23:56:05 nit: Why split this? I think it was fine before.
pgervais 2014/09/20 00:26:55 merge problem I think. will fix.
219 mod = load_module(modname) 237 mod = load_module(modname)
220 for obj in mod.__dict__.values(): 238 for obj in mod.__dict__.values():
221 if util.is_test_generator(obj): 239 if util.is_test_generator(obj):
222 test_gens.append(obj) 240 test_gens.append(obj)
223 elif _is_unittest(obj): 241 elif _is_unittest(obj):
224 test_gens.append(UnittestTestCase(obj)) 242 test_gens.append(UnittestTestCase(obj))
225 return test_gens 243 return test_gens
226 244
227 245
228 def gen_loop_process(gens, test_queue, result_queue, opts, kill_switch, 246 def gen_loop_process(gens, test_queue, result_queue, opts, kill_switch,
(...skipping 292 matching lines...) Expand 10 before | Expand all | Expand 10 after
521 539
522 if procs: 540 if procs:
523 error = opts.handler.result_stage_loop(opts, generate_objects(procs)) 541 error = opts.handler.result_stage_loop(opts, generate_objects(procs))
524 except ResultStageAbort: 542 except ResultStageAbort:
525 pass 543 pass
526 544
527 if not kill_switch.is_set() and not result_queue.empty(): 545 if not kill_switch.is_set() and not result_queue.empty():
528 error = True 546 error = True
529 547
530 return error, kill_switch.is_set() 548 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