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

Side by Side Diff: expect_tests/listing.py

Issue 709853003: New expect_tests UI (Closed) Base URL: https://chromium.googlesource.com/infra/testing/expect_tests@shebang
Patch Set: Fixed nits Created 6 years, 1 month 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 | expect_tests/main.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
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
3 # found in the LICENSE file.
4
5
6 import ConfigParser
7 import glob
8 import os
9 import re
10
11
12 CONFIG_FILE_NAME = '.expect_tests.cfg'
13
14
15 def get_python_root(path):
16 """Get the lowest directory with no __init__.py file.
17
18 When ``path`` is pointing inside a Python package, this function returns the
19 directory directly containing this package. If ``path`` points outside of
20 a Python package, the it returns ``path``.
21
22 Args:
23 path (str): arbitrary path
24 Returns:
25 root (str): ancestor directory, with no __init__.py file in it.
26 """
27 if not os.path.exists(path):
28 raise ValueError('path must exist: %s')
29
30 while path != os.path.dirname(path):
31 if not os.path.exists(os.path.join(path, '__init__.py')):
32 return path
33 path = os.path.dirname(path)
34
35 # This is not supposed to happen, but in case somebody adds a __init__.py
36 # at the filesystem root ...
37 raise IOError("Unable to find a python root for %s" % path)
38
39
40 def parse_test_glob(test_glob):
41 """A test glob is composed of a path and a glob expression like:
42 '<path>:<glob>'. The path should point to a directory or a file inside
43 a Python package (it can be the root directory of that package).
44 The glob is a Python name used to filter tests.
45
46 A test name is the name of the method prepended with the class name.
47
48 Example:
49 'my/nice/package/test1/:TestA*', the package root being 'my/nice/package':
50 this matches all tests whose class name starts with 'TestA' inside all
51 files matching test1/*_test.py, like
52 ``TestABunchOfStuff.testCatFoodDeployment``.
53
54 Args:
55 test_glob (str): a test glob
56 Returns:
57 (path, test_filter): absolute path and test filter glob.
58 """
59 parts = test_glob.split(':')
60 if len(parts) > 2:
61 raise ValueError('A test_glob should contain at most one colon (got %s)'
62 % test_glob)
63 if len(parts) == 2:
64 path, test_filter = parts
65 if '/' in test_filter:
66 raise ValueError('A test filter cannot contain a slash (got %s)',
67 test_filter)
68
69 if not test_filter: # empty string case
70 test_filter = ('*',)
71 else:
72 path, test_filter = parts[0], ('*',)
73
74 path = os.path.abspath(path)
75 return path, test_filter
76
77
78 class PackageTestingContext(object):
79 def __init__(self, cwd, package_name, filters):
80 """Information to run a set of tests in a single package.
81
82 See also parse_test_glob.
83 """
84 # TODO(iannucci): let's scan packages too so that <path> can also be a
85 # glob. Then expect_tests can use a default of '*:*' when no tests are
86 # specified.
87
88 self.cwd = cwd
89 self.package_name = package_name
90 # list of (path, filter) pairs.
91 # The path is a relative path to a subdirectory of
92 # os.path.join(self.cwd, self.package_name) in which to look for tests.
93 # Only tests whose name matches the glob are kept.
94 self.filters = filters
95
96 def itermatchers(self):
97 """Iterate over all filters, and yield matchers for each of them.
98
99 Yields:
100 path (str): restrict test listing to this subpackage.
101 matcher (SRE_Pattern): whitelist matcher
102 """
103 for filt in self.filters:
104 # Implicitely append * to globs
105 one_glob = '%s%s' % (filt[1], '*' if '*' not in filt[1] else '')
106 matcher = re.compile('^(?:%s)$' % glob.fnmatch.translate(one_glob))
107
108 if matcher.pattern == '^$':
109 matcher = re.compile('^.*$')
110
111 yield filt[0], matcher
112
113 @classmethod
114 def from_path(cls, path, filters=('*',)):
115 path = os.path.abspath(path)
116 if not os.path.exists(path):
117 raise ValueError('Path does not exist: %s' % path)
118 cwd = get_python_root(path)
119 package_name = os.path.relpath(path, cwd).split(os.path.sep)[0]
120 # The path in which to look for tests. Only tests whose name matches the
121 # glob are kept.
122 relpath = os.path.relpath(path, os.path.join(cwd, package_name))
123
124 if not isinstance(filters, (list, tuple)):
125 raise ValueError('the "filter" parameter must be a tuple or a list, '
126 'got %s' % type(filters).__name__)
127 if len(filters) == 0:
128 filters = [(relpath, '*')]
129 else:
130 filters = [(relpath, filt) for filt in filters]
131
132 return cls(cwd, package_name, filters)
133
134 @classmethod
135 def from_context_list(cls, contexts):
136 """Merge several PackageTestingContext pointing to the same package."""
137 cwd = set(context.cwd for context in contexts)
138 if len(cwd) > 1:
139 raise ValueError(
140 'from_context_list() was given'
141 'process contexts containing the following cwds, '
142 'but can only process contexts which all share a single cwd: '
143 '%s' % str(cwd))
144
145 package_name = set(context.package_name for context in contexts)
146 if len(package_name) > 1:
147 raise ValueError(
148 'from_context_list() was given'
149 'process contexts containing the following package_name, '
150 'but can only process contexts which all share a single package_name: '
151 '%s' % str(package_name))
152
153 filters = []
154 for context in contexts:
155 filters.extend(context.filters)
156
157 return cls(cwd.pop(), package_name.pop(), filters)
158
159
160 class ProcessingContext(object):
161 def __init__(self, testing_contexts):
162 """Information to run a set of tasks in a given working directory.
163
164 Args:
165 testing_contexts (list): list of PackageTestingContext instances.
166 """
167 self.cwd = testing_contexts[0].cwd
168
169 # Merge testing_contexts by package
170 groups = {}
171 for context in testing_contexts:
172 if context.cwd != self.cwd:
173 raise ValueError('All package must have the same value for "cwd"')
174 groups.setdefault(context.package_name, []).append(context)
175
176 self.testing_contexts = [PackageTestingContext.from_context_list(contexts)
177 for contexts in groups.itervalues()]
178
179
180 def get_config(path):
181 """Get configuration values
182
183 Reads the config file in provided path, and returns content.
184 See Python ConfigParser for general formatting syntax.
185
186 Example:
187 [expect_tests]
188 skip=directory1
189 directory2
190 directory3
191
192 Args:
193 path (str): path to a directory.
194
195 Returns:
196 black_list (set): blacklisted subdirectories.
197 """
198 black_list = set()
199
200 config_file_name = os.path.join(path, CONFIG_FILE_NAME)
201 parser = ConfigParser.ConfigParser()
202 parser.read([config_file_name])
203
204 if not parser.has_section('expect_tests'):
205 return black_list
206
207 if parser.has_option('expect_tests', 'skip'):
208 black_list.update(parser.get('expect_tests', 'skip').splitlines())
209 return black_list
210
211
212 def get_runtime_contexts(test_globs):
213 """Compute the list of packages/filters to get tests from."""
214 # Step 1: compute list of packages + subtree
215 testing_contexts = []
216 for test_glob in test_globs:
217 path, test_filter = parse_test_glob(test_glob)
218 if os.path.exists(os.path.join(path, '__init__.py')):
219 testing_contexts.append(
220 PackageTestingContext.from_path(path, test_filter))
221 else:
222 # Look for all packages in path.
223 subpaths = []
224 black_list = get_config(path)
225
226 for filename in filter(lambda x: x not in black_list, os.listdir(path)):
227 abs_filename = os.path.join(path, filename)
228 if (os.path.isdir(abs_filename)
229 and os.path.isfile(os.path.join(abs_filename, '__init__.py'))):
230 subpaths.append(abs_filename)
231
232 testing_contexts.extend(
233 [PackageTestingContext.from_path(subpath, test_filter)
234 for subpath in subpaths])
235
236 # Step 2: group by working directory - one process per wd.
237 groups = {}
238 for context in testing_contexts:
239 groups.setdefault(context.cwd, []).append(context)
240 return [ProcessingContext(contexts) for contexts in groups.itervalues()]
OLDNEW
« no previous file with comments | « no previous file | expect_tests/main.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698