| 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
|
|
|