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

Unified Diff: third_party/recipe_engine/expect_tests/type_definitions.py

Issue 1241323004: Cross-repo recipe package system. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/tools/build
Patch Set: Roll to latest recipes-py Created 5 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 side-by-side diff with in-line comments
Download patch
Index: third_party/recipe_engine/expect_tests/type_definitions.py
diff --git a/third_party/recipe_engine/expect_tests/type_definitions.py b/third_party/recipe_engine/expect_tests/type_definitions.py
deleted file mode 100644
index e357156f0f1b9a5a54f407dcfea2b32badd63253..0000000000000000000000000000000000000000
--- a/third_party/recipe_engine/expect_tests/type_definitions.py
+++ /dev/null
@@ -1,433 +0,0 @@
-# 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 inspect
-import os
-import re
-
-from collections import namedtuple
-
-# These have to do with deriving classes from namedtuple return values.
-# Pylint can't tell that namedtuple returns a new-style type() object.
-#
-# "no __init__ method" pylint: disable=W0232
-# "use of super on an old style class" pylint: disable=E1002
-
-UnknownError = namedtuple('UnknownError', 'message')
-NoMatchingTestsError = namedtuple('NoMatchingTestsError', '')
-Result = namedtuple('Result', 'data')
-MultiResult = namedtuple('MultiResult', 'results')
-DirSeen = namedtuple('DirSeen', 'dir')
-
-class ResultStageAbort(Exception):
- pass
-
-
-class Failure(object):
- pass
-
-
-class TestError(namedtuple('TestError', 'test message log_lines')):
- def __new__(cls, test, message, log_lines=()):
- return super(TestError, cls).__new__(cls, test, message, log_lines)
-
-
-class Bind(namedtuple('_Bind', 'loc name')):
- """A placeholder argument for a FuncCall.
-
- A Bind instance either indicates a 0-based index into the args argument,
- or a name in kwargs when calling .bind().
- """
-
- def __new__(cls, loc=None, name=None):
- """Either loc or name must be defined."""
- assert ((loc is None and isinstance(name, str)) or
- (name is None and 0 <= loc))
- return super(Bind, cls).__new__(cls, loc, name)
-
- def bind(self, args=(), kwargs=None):
- """Return the appropriate value for this Bind when binding against args and
- kwargs.
-
- >>> b = Bind(2)
- >>> # A bind will return itself if a matching arg value isn't present
- >>> b.bind(['cat'], {'arg': 100}) is b
- True
- >>> # Otherwise the matching value is returned
- >>> v = 'money'
- >>> b.bind(['happy', 'cool', v]) is v
- True
- >>> b2 = Bind(name='cat')
- >>> b2.bind((), {'cat': 'cool'})
- 'cool'
- """
- kwargs = kwargs or {}
- if self.loc is not None:
- v = args[self.loc:self.loc+1]
- return self if not v else v[0]
- else:
- return kwargs.get(self.name, self)
-
- @staticmethod
- def maybe_bind(value, args, kwargs):
- """Helper which binds value with (args, kwargs) if value is a Bind."""
- return value.bind(args, kwargs) if isinstance(value, Bind) else value
-
-
-class FuncCall(object):
- def __init__(self, func, *args, **kwargs):
- """FuncCall is a trivial single-function closure which is pickleable.
-
- This assumes that func, args and kwargs are all pickleable.
-
- When constructing the FuncCall, you may also set any positional or named
- argument to a Bind instance. A FuncCall can then be bound with the
- .bind(*args, **kwargs) method, and finally called by invoking func_call().
-
- A FuncCall may also be directly invoked with func_call(*args, **kwargs),
- which is equivalent to func_call.bind(*args, **kwargs)().
-
- Invoking a FuncCall with an unbound Bind instance is an error.
-
- >>> def func(alpha, beta=None, gamma=None):
- ... return '%s-%s-%s' % (alpha, beta, gamma)
- >>> f = FuncCall(func, Bind(2), beta=Bind(name='context'), gamma=Bind(2))
- >>> # the first arg and the named arg 'gamma' are bound to index 2 of args.
- >>> # the named arg 'beta' is bound to the named kwarg 'context'.
- >>> #
- >>> # The FuncCall is equivalent to (py3 pattern syntax):
- >>> # UNSET = object()
- >>> # def f(_, _, arg1, *_, context=UNSET, **_):
- >>> # assert pickle is not UNSET
- >>> # return func(arg1, beta=context, gamma=arg1)
- >>> bound = f.bind('foo', 'bar', 'baz', context=100, extra=None)
- >>> # At this point, bound is a FuncCall with no Bind arguments, and can be
- >>> # invoked. This would be equivalent to:
- >>> # func('baz', beta=100, gamma='baz')
- >>> bound()
- baz-100-baz
-
- Unused arguments in the .bind() call are ignored, which allows you to build
- value-agnostic invocations to FuncCall.bind().
- """
- self._func = func
- self._args = args
- self._kwargs = kwargs
- self._fully_bound = None
-
- # "access to a protected member" pylint: disable=W0212
- func = property(lambda self: self._func)
- args = property(lambda self: self._args)
- kwargs = property(lambda self: self._kwargs)
-
- @property
- def fully_bound(self):
- if self._fully_bound is None:
- self._fully_bound = not (
- any(isinstance(v, Bind) for v in self._args) or
- any(isinstance(v, Bind) for v in self._kwargs.itervalues())
- )
- return self._fully_bound
-
- def bind(self, *args, **kwargs):
- if self.fully_bound or not (args or kwargs):
- return self
-
- new = FuncCall(self._func)
- new._args = [Bind.maybe_bind(a, args, kwargs) for a in self.args]
- new._kwargs = {k: Bind.maybe_bind(v, args, kwargs)
- for k, v in self.kwargs.iteritems()}
- return new
-
- def __call__(self, *args, **kwargs):
- f = self.bind(args, kwargs)
- assert f.fully_bound
- return f.func(*f.args, **f.kwargs)
-
- def __repr__(self):
- return 'FuncCall(%r, *%r, **%r)' % (self.func, self.args, self.kwargs)
-
-
-_Test = namedtuple(
- 'Test', 'name func_call expect_dir expect_base ext covers breakpoints')
-
-class Test(_Test):
- TEST_COVERS_MATCH = re.compile(r'.*/test/([^/]*)_test\.py$')
-
- def __new__(cls, name, func_call, expect_dir=None, expect_base=None,
- ext='json', covers=None, breakpoints=None, break_funcs=()):
- """Create a new test.
-
- @param name: The name of the test. Will be used as the default expect_base
-
- @param func_call: A FuncCall object
-
- @param expect_dir: The directory which holds the expectation file for this
- Test.
- @param expect_base: The basename (without extension) of the expectation
- file. Defaults to |name|.
- @param ext: The extension of the expectation file. Affects the serializer
- used to write the expectations to disk. Valid values are
- 'json' and 'yaml' (Keys in SERIALIZERS).
- @param covers: A list of coverage file patterns to include for this Test.
- By default, a Test covers the file in which its function
- was defined, as well as the source file matching the test
- according to TEST_COVERS_MATCH.
-
- @param breakpoints: A list of (path, lineno, func_name) tuples. These will
- turn into breakpoints when the tests are run in 'debug'
- mode. See |break_funcs| for an easier way to set this.
- @param break_funcs: A list of functions for which to set breakpoints.
- """
- breakpoints = breakpoints or []
- if not breakpoints or break_funcs:
- for f in break_funcs or (func_call.func,):
- if hasattr(f, 'im_func'):
- f = f.im_func
- breakpoints.append((f.func_code.co_filename,
- f.func_code.co_firstlineno,
- f.func_code.co_name))
-
- expect_dir = expect_dir.rstrip('/')
- return super(Test, cls).__new__(cls, name, func_call, expect_dir,
- expect_base, ext, covers, breakpoints)
-
- def coverage_includes(self):
- if self.covers is not None:
- return self.covers
-
- test_file = inspect.getabsfile(self.func_call.func)
- covers = [test_file]
- match = Test.TEST_COVERS_MATCH.match(test_file)
- if match:
- covers.append(os.path.join(
- os.path.dirname(os.path.dirname(test_file)),
- match.group(1) + '.py'
- ))
-
- return covers
-
- def expect_path(self, ext=None):
- expect_dir = self.expect_dir
- if expect_dir is None:
- test_file = inspect.getabsfile(self.func_call.func)
- expect_dir = os.path.splitext(test_file)[0] + '.expected'
- name = self.expect_base or self.name
- name = ''.join('_' if c in '<>:"\\/|?*\0' else c for c in name)
- return os.path.join(expect_dir, name + ('.%s' % (ext or self.ext)))
-
- def run(self, context=None):
- return self.func_call(context=context)
-
- def process(self, func=lambda test: test.run()):
- """Applies |func| to the test, and yields (self, func(self)).
-
- For duck-typing compatibility with MultiTest.
-
- Bind(name='context') if used by your test function, is bound to None.
-
- Used interally by expect_tests, you're not expected to call this yourself.
- """
- yield self, func(self.bind(context=None))
-
- def bind(self, *args, **kwargs):
- return self._replace(func_call=self.func_call.bind(*args, **kwargs))
-
- def restrict(self, tests):
- assert tests[0] is self
- return self
-
- @property
- def tests(self):
- """Returns a list of all one tests in this singleton test. For uniformity
- with MultiTest."""
- return [self]
-
-
-_MultiTest = namedtuple(
- 'MultiTest', 'name make_ctx_call destroy_ctx_call tests atomic')
-
-class MultiTest(_MultiTest):
- """A wrapper around one or more Test instances.
-
- Allows the entire group to have common pre- and post- actions and an optional
- shared context between the Test methods (represented by Bind(name='context')).
-
- Args:
- name - The name of the MultiTest. Each Test's name should be prefixed with
- this name, though this is not enforced.
- make_ctx_call - A FuncCall which will be called once before any test in this
- MultiTest runs. The return value of this FuncCall will become bound
- to the name 'context' for both the |destroy_ctx_call| as well as every
- test in |tests|.
- destroy_ctx_call - A FuncCall which will be called once after all tests in
- this MultiTest runs. The context object produced by |make_ctx_call| is
- bound to the name 'context'.
- tests - A list of Test instances. The context object produced by
- |make_ctx_call| is bound to the name 'context'.
- atomic - A boolean which indicates that this MultiTest must be executed
- either all at once, or not at all (i.e., subtests may not be filtered).
- """
-
- def restrict(self, tests):
- """A helper method to re-cast the MultiTest with fewer subtests.
-
- All fields will be identical except for tests. If this MultiTest is atomic,
- then this method returns |self|.
-
- Used interally by expect_tests, you're not expected to call this yourself.
- """
- if self.atomic:
- return self
- assert all(t in self.tests for t in tests)
- return self._replace(tests=tests)
-
- def process(self, func=lambda test: test.run()):
- """Applies |func| to each sub-test, with properly bound context.
-
- make_ctx_call will be called before any test, and its return value becomes
- bound to the name 'context'. All sub-tests will be bound with this value
- as well as destroy_ctx_call, which will be invoked after all tests have
- been yielded.
-
- Optionally, you may specify a different function to apply to each test
- (by default it is `lambda test: test.run()`). The context will be bound
- to the test before your function receives it.
-
- Used interally by expect_tests, you're not expected to call this yourself.
- """
- # TODO(iannucci): pass list of test names?
- ctx_object = self.make_ctx_call()
- try:
- for test in self.tests:
- yield test, func(test.bind(context=ctx_object))
- finally:
- self.destroy_ctx_call.bind(context=ctx_object)()
-
- @staticmethod
- def expect_path(_ext=None):
- return None
-
-
-class Handler(object):
- """Handler object.
-
- Defines 3 handler methods for each stage of the test pipeline. The pipeline
- looks like:
-
- -> ->
- -> jobs -> (main)
- GenStage -> test_queue -> * -> result_queue -> ResultStage
- -> RunStage ->
- -> ->
-
- Each process will have an instance of one of the nested handler classes, which
- will be called on each test / result.
-
- You can skip the RunStage phase by setting SKIP_RUNLOOP to True on your
- implementation class.
-
- Tips:
- * Only do printing in ResultStage, since it's running on the main process.
- """
- SKIP_RUNLOOP = False
-
- @classmethod
- def add_options(cls, parser):
- """
- @type parser: argparse.ArgumentParser()
- """
- pass
-
- @classmethod
- def gen_stage_loop(cls, _opts, tests, put_next_stage, _put_result_stage):
- """Called in the GenStage portion of the pipeline.
-
- @param opts: Parsed CLI options
- @param tests:
- Iteraterable of type_definitions.Test or type_definitions.MultiTest
- objects.
- @param put_next_stage:
- Function to push an object to the next stage of the pipeline (RunStage).
- Note that you should push the item you got from |tests|, not the
- subtests, in the case that the item is a MultiTest.
- @param put_result_stage:
- Function to push an object to the result stage of the pipeline.
- """
- for test in tests:
- put_next_stage(test)
-
- @classmethod
- def run_stage_loop(cls, _opts, tests_results, put_next_stage):
- """Called in the RunStage portion of the pipeline.
-
- @param opts: Parsed CLI options
- @param tests_results: Iteraterable of (type_definitions.Test,
- type_definitions.Result) objects
- @param put_next_stage: Function to push an object to the next stage of the
- pipeline (ResultStage).
- """
- for _, result in tests_results:
- put_next_stage(result)
-
- @classmethod
- def result_stage_loop(cls, opts, objects):
- """Called in the ResultStage portion of the pipeline.
-
- Consider subclassing ResultStageHandler instead as it provides a more
- flexible interface for dealing with |objects|.
-
- @param opts: Parsed CLI options
- @param objects: Iteraterable of objects from GenStage and RunStage.
- """
- error = False
- aborted = False
- handler = cls.ResultStageHandler(opts)
- try:
- for obj in objects:
- error |= isinstance(handler(obj), Failure)
- except ResultStageAbort:
- aborted = True
- handler.finalize(aborted)
- return error
-
- class ResultStageHandler(object):
- """SAX-like event handler dispatches to self.handle_{type(obj).__name__}
-
- So if |obj| is a Test, this would call self.handle_Test(obj).
-
- self.__unknown is called to handle objects which have no defined handler.
-
- self.finalize is called after all objects are processed.
- """
- def __init__(self, opts):
- self.opts = opts
-
- def __call__(self, obj):
- """Called to handle each object in the ResultStage
-
- @type obj: Anything passed to put_result in GenStage or RunStage.
-
- @return: If the handler method returns Failure(), then it will
- cause the entire test run to ultimately return an error code.
- """
- return getattr(self, 'handle_' + type(obj).__name__, self.__unknown)(obj)
-
- def handle_NoMatchingTestsError(self, _error):
- print 'No tests found that match the glob: %s' % (
- ' '.join(self.opts.test_glob),)
- return Failure()
-
- def __unknown(self, obj):
- if self.opts.verbose:
- print 'UNHANDLED:', obj
- return Failure()
-
- def finalize(self, aborted):
- """Called after __call__() has been called for all results.
-
- @param aborted: True if the user aborted the run.
- @type aborted: bool
- """
- pass
« no previous file with comments | « third_party/recipe_engine/expect_tests/serialize.py ('k') | third_party/recipe_engine/expect_tests/unittest_helper.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698