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

Unified 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | expect_tests/main.py » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: expect_tests/listing.py
diff --git a/expect_tests/listing.py b/expect_tests/listing.py
new file mode 100644
index 0000000000000000000000000000000000000000..d5ff4c30424ba35ddd127528af9f448a5120ef28
--- /dev/null
+++ b/expect_tests/listing.py
@@ -0,0 +1,240 @@
+# Copyright 2014 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+
+import ConfigParser
+import glob
+import os
+import re
+
+
+CONFIG_FILE_NAME = '.expect_tests.cfg'
+
+
+def get_python_root(path):
+ """Get the lowest directory with no __init__.py file.
+
+ When ``path`` is pointing inside a Python package, this function returns the
+ directory directly containing this package. If ``path`` points outside of
+ a Python package, the it returns ``path``.
+
+ Args:
+ path (str): arbitrary path
+ Returns:
+ root (str): ancestor directory, with no __init__.py file in it.
+ """
+ if not os.path.exists(path):
+ raise ValueError('path must exist: %s')
+
+ while path != os.path.dirname(path):
+ if not os.path.exists(os.path.join(path, '__init__.py')):
+ return path
+ path = os.path.dirname(path)
+
+ # This is not supposed to happen, but in case somebody adds a __init__.py
+ # at the filesystem root ...
+ raise IOError("Unable to find a python root for %s" % path)
+
+
+def parse_test_glob(test_glob):
+ """A test glob is composed of a path and a glob expression like:
+ '<path>:<glob>'. The path should point to a directory or a file inside
+ a Python package (it can be the root directory of that package).
+ The glob is a Python name used to filter tests.
+
+ A test name is the name of the method prepended with the class name.
+
+ Example:
+ 'my/nice/package/test1/:TestA*', the package root being 'my/nice/package':
+ this matches all tests whose class name starts with 'TestA' inside all
+ files matching test1/*_test.py, like
+ ``TestABunchOfStuff.testCatFoodDeployment``.
+
+ Args:
+ test_glob (str): a test glob
+ Returns:
+ (path, test_filter): absolute path and test filter glob.
+ """
+ parts = test_glob.split(':')
+ if len(parts) > 2:
+ raise ValueError('A test_glob should contain at most one colon (got %s)'
+ % test_glob)
+ if len(parts) == 2:
+ path, test_filter = parts
+ if '/' in test_filter:
+ raise ValueError('A test filter cannot contain a slash (got %s)',
+ test_filter)
+
+ if not test_filter: # empty string case
+ test_filter = ('*',)
+ else:
+ path, test_filter = parts[0], ('*',)
+
+ path = os.path.abspath(path)
+ return path, test_filter
+
+
+class PackageTestingContext(object):
+ def __init__(self, cwd, package_name, filters):
+ """Information to run a set of tests in a single package.
+
+ See also parse_test_glob.
+ """
+ # TODO(iannucci): let's scan packages too so that <path> can also be a
+ # glob. Then expect_tests can use a default of '*:*' when no tests are
+ # specified.
+
+ self.cwd = cwd
+ self.package_name = package_name
+ # list of (path, filter) pairs.
+ # The path is a relative path to a subdirectory of
+ # os.path.join(self.cwd, self.package_name) in which to look for tests.
+ # Only tests whose name matches the glob are kept.
+ self.filters = filters
+
+ def itermatchers(self):
+ """Iterate over all filters, and yield matchers for each of them.
+
+ Yields:
+ path (str): restrict test listing to this subpackage.
+ matcher (SRE_Pattern): whitelist matcher
+ """
+ for filt in self.filters:
+ # Implicitely append * to globs
+ one_glob = '%s%s' % (filt[1], '*' if '*' not in filt[1] else '')
+ matcher = re.compile('^(?:%s)$' % glob.fnmatch.translate(one_glob))
+
+ if matcher.pattern == '^$':
+ matcher = re.compile('^.*$')
+
+ yield filt[0], matcher
+
+ @classmethod
+ def from_path(cls, path, filters=('*',)):
+ path = os.path.abspath(path)
+ if not os.path.exists(path):
+ raise ValueError('Path does not exist: %s' % path)
+ cwd = get_python_root(path)
+ package_name = os.path.relpath(path, cwd).split(os.path.sep)[0]
+ # The path in which to look for tests. Only tests whose name matches the
+ # glob are kept.
+ relpath = os.path.relpath(path, os.path.join(cwd, package_name))
+
+ if not isinstance(filters, (list, tuple)):
+ raise ValueError('the "filter" parameter must be a tuple or a list, '
+ 'got %s' % type(filters).__name__)
+ if len(filters) == 0:
+ filters = [(relpath, '*')]
+ else:
+ filters = [(relpath, filt) for filt in filters]
+
+ return cls(cwd, package_name, filters)
+
+ @classmethod
+ def from_context_list(cls, contexts):
+ """Merge several PackageTestingContext pointing to the same package."""
+ cwd = set(context.cwd for context in contexts)
+ if len(cwd) > 1:
+ raise ValueError(
+ 'from_context_list() was given'
+ 'process contexts containing the following cwds, '
+ 'but can only process contexts which all share a single cwd: '
+ '%s' % str(cwd))
+
+ package_name = set(context.package_name for context in contexts)
+ if len(package_name) > 1:
+ raise ValueError(
+ 'from_context_list() was given'
+ 'process contexts containing the following package_name, '
+ 'but can only process contexts which all share a single package_name: '
+ '%s' % str(package_name))
+
+ filters = []
+ for context in contexts:
+ filters.extend(context.filters)
+
+ return cls(cwd.pop(), package_name.pop(), filters)
+
+
+class ProcessingContext(object):
+ def __init__(self, testing_contexts):
+ """Information to run a set of tasks in a given working directory.
+
+ Args:
+ testing_contexts (list): list of PackageTestingContext instances.
+ """
+ self.cwd = testing_contexts[0].cwd
+
+ # Merge testing_contexts by package
+ groups = {}
+ for context in testing_contexts:
+ if context.cwd != self.cwd:
+ raise ValueError('All package must have the same value for "cwd"')
+ groups.setdefault(context.package_name, []).append(context)
+
+ self.testing_contexts = [PackageTestingContext.from_context_list(contexts)
+ for contexts in groups.itervalues()]
+
+
+def get_config(path):
+ """Get configuration values
+
+ Reads the config file in provided path, and returns content.
+ See Python ConfigParser for general formatting syntax.
+
+ Example:
+ [expect_tests]
+ skip=directory1
+ directory2
+ directory3
+
+ Args:
+ path (str): path to a directory.
+
+ Returns:
+ black_list (set): blacklisted subdirectories.
+ """
+ black_list = set()
+
+ config_file_name = os.path.join(path, CONFIG_FILE_NAME)
+ parser = ConfigParser.ConfigParser()
+ parser.read([config_file_name])
+
+ if not parser.has_section('expect_tests'):
+ return black_list
+
+ if parser.has_option('expect_tests', 'skip'):
+ black_list.update(parser.get('expect_tests', 'skip').splitlines())
+ return black_list
+
+
+def get_runtime_contexts(test_globs):
+ """Compute the list of packages/filters to get tests from."""
+ # Step 1: compute list of packages + subtree
+ testing_contexts = []
+ for test_glob in test_globs:
+ path, test_filter = parse_test_glob(test_glob)
+ if os.path.exists(os.path.join(path, '__init__.py')):
+ testing_contexts.append(
+ PackageTestingContext.from_path(path, test_filter))
+ else:
+ # Look for all packages in path.
+ subpaths = []
+ black_list = get_config(path)
+
+ for filename in filter(lambda x: x not in black_list, os.listdir(path)):
+ abs_filename = os.path.join(path, filename)
+ if (os.path.isdir(abs_filename)
+ and os.path.isfile(os.path.join(abs_filename, '__init__.py'))):
+ subpaths.append(abs_filename)
+
+ testing_contexts.extend(
+ [PackageTestingContext.from_path(subpath, test_filter)
+ for subpath in subpaths])
+
+ # Step 2: group by working directory - one process per wd.
+ groups = {}
+ for context in testing_contexts:
+ groups.setdefault(context.cwd, []).append(context)
+ return [ProcessingContext(contexts) for contexts in groups.itervalues()]
« 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