| Index: third_party/recipe_engine/recipe_test_api.py
|
| diff --git a/third_party/recipe_engine/recipe_test_api.py b/third_party/recipe_engine/recipe_test_api.py
|
| deleted file mode 100644
|
| index 2ea3791725a276ddffca3035baf47a652db7bc92..0000000000000000000000000000000000000000
|
| --- a/third_party/recipe_engine/recipe_test_api.py
|
| +++ /dev/null
|
| @@ -1,462 +0,0 @@
|
| -# Copyright 2013-2015 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 collections
|
| -
|
| -from .util import ModuleInjectionSite, static_call, static_wraps
|
| -
|
| -def combineify(name, dest, a, b):
|
| - """
|
| - Combines dictionary members in two objects into a third one using addition.
|
| -
|
| - Args:
|
| - name - the name of the member
|
| - dest - the destination object
|
| - a - the first source object
|
| - b - the second source object
|
| - """
|
| - dest_dict = getattr(dest, name)
|
| - dest_dict.update(getattr(a, name))
|
| - for k, v in getattr(b, name).iteritems():
|
| - if k in dest_dict:
|
| - dest_dict[k] += v
|
| - else:
|
| - dest_dict[k] = v
|
| -
|
| -
|
| -class BaseTestData(object):
|
| - def __init__(self, enabled=True):
|
| - super(BaseTestData, self).__init__()
|
| - self._enabled = enabled
|
| -
|
| - @property
|
| - def enabled(self):
|
| - return self._enabled
|
| -
|
| -
|
| -class PlaceholderTestData(BaseTestData):
|
| - def __init__(self, data=None):
|
| - super(PlaceholderTestData, self).__init__()
|
| - self.data = data
|
| -
|
| - def __repr__(self):
|
| - return "PlaceholderTestData(%r)" % (self.data,)
|
| -
|
| -
|
| -class StepTestData(BaseTestData):
|
| - """
|
| - Mutable container for per-step test data.
|
| -
|
| - This data is consumed while running the recipe (during
|
| - annotated_run.run_steps).
|
| - """
|
| - def __init__(self):
|
| - super(StepTestData, self).__init__()
|
| - # { (module, placeholder) -> [data] }
|
| - self.placeholder_data = collections.defaultdict(list)
|
| - self.override = False
|
| - self._stdout = None
|
| - self._stderr = None
|
| - self._retcode = None
|
| -
|
| - def __add__(self, other):
|
| - assert isinstance(other, StepTestData)
|
| -
|
| - if other.override:
|
| - return other
|
| -
|
| - ret = StepTestData()
|
| -
|
| - combineify('placeholder_data', ret, self, other)
|
| -
|
| - # pylint: disable=W0212
|
| - ret._stdout = other._stdout or self._stdout
|
| - ret._stderr = other._stderr or self._stderr
|
| - ret._retcode = self._retcode
|
| - if other._retcode is not None:
|
| - assert ret._retcode is None
|
| - ret._retcode = other._retcode
|
| -
|
| - return ret
|
| -
|
| - def unwrap_placeholder(self):
|
| - # {(module, placeholder): [data]} => data.
|
| - assert len(self.placeholder_data) == 1
|
| - data_list = self.placeholder_data.items()[0][1]
|
| - assert len(data_list) == 1
|
| - return data_list[0]
|
| -
|
| - def pop_placeholder(self, name_pieces):
|
| - l = self.placeholder_data[name_pieces]
|
| - if l:
|
| - return l.pop(0)
|
| - else:
|
| - return PlaceholderTestData()
|
| -
|
| - @property
|
| - def retcode(self): # pylint: disable=E0202
|
| - return self._retcode or 0
|
| -
|
| - @retcode.setter
|
| - def retcode(self, value): # pylint: disable=E0202
|
| - self._retcode = value
|
| -
|
| - @property
|
| - def stdout(self):
|
| - return self._stdout or PlaceholderTestData(None)
|
| -
|
| - @stdout.setter
|
| - def stdout(self, value):
|
| - assert isinstance(value, PlaceholderTestData)
|
| - self._stdout = value
|
| -
|
| - @property
|
| - def stderr(self):
|
| - return self._stderr or PlaceholderTestData(None)
|
| -
|
| - @stderr.setter
|
| - def stderr(self, value):
|
| - assert isinstance(value, PlaceholderTestData)
|
| - self._stderr = value
|
| -
|
| - @property
|
| - def stdin(self): # pylint: disable=R0201
|
| - return PlaceholderTestData(None)
|
| -
|
| - def __repr__(self):
|
| - return "StepTestData(%r)" % ({
|
| - 'placeholder_data': dict(self.placeholder_data.iteritems()),
|
| - 'stdout': self._stdout,
|
| - 'stderr': self._stderr,
|
| - 'retcode': self._retcode,
|
| - 'override': self.override,
|
| - },)
|
| -
|
| -
|
| -class ModuleTestData(BaseTestData, dict):
|
| - """
|
| - Mutable container for test data for a specific module.
|
| -
|
| - This test data is consumed at module load time (i.e. when create_recipe_api
|
| - runs).
|
| - """
|
| - def __add__(self, other):
|
| - assert isinstance(other, ModuleTestData)
|
| - ret = ModuleTestData()
|
| - ret.update(self)
|
| - ret.update(other)
|
| - return ret
|
| -
|
| - def __repr__(self):
|
| - return "ModuleTestData(%r)" % super(ModuleTestData, self).__repr__()
|
| -
|
| -
|
| -class TestData(BaseTestData):
|
| - def __init__(self, name=None):
|
| - super(TestData, self).__init__()
|
| - self.name = name
|
| - self.properties = {} # key -> val
|
| - self.mod_data = collections.defaultdict(ModuleTestData)
|
| - self.step_data = collections.defaultdict(StepTestData)
|
| - self.expected_exception = None
|
| -
|
| - def __add__(self, other):
|
| - assert isinstance(other, TestData)
|
| - ret = TestData(self.name or other.name)
|
| -
|
| - ret.properties.update(self.properties)
|
| - ret.properties.update(other.properties)
|
| -
|
| - combineify('mod_data', ret, self, other)
|
| - combineify('step_data', ret, self, other)
|
| - ret.expected_exception = self.expected_exception
|
| - if other.expected_exception:
|
| - ret.expected_exception = other.expected_exception
|
| -
|
| - return ret
|
| -
|
| - def empty(self):
|
| - return not self.step_data
|
| -
|
| - def pop_step_test_data(self, step_name, step_test_data_fn):
|
| - step_test_data = step_test_data_fn()
|
| - if step_name in self.step_data:
|
| - step_test_data += self.step_data.pop(step_name)
|
| - return step_test_data
|
| -
|
| - def get_module_test_data(self, module_name):
|
| - return self.mod_data.get(module_name, ModuleTestData())
|
| -
|
| - def expect_exception(self, exception):
|
| - assert not self.expected_exception
|
| - self.expected_exception = exception
|
| -
|
| - def is_unexpected_exception(self, exception):
|
| - name = exception.__class__.__name__
|
| - return not (self.enabled and name == self.expected_exception)
|
| -
|
| - def __repr__(self):
|
| - return "TestData(%r)" % ({
|
| - 'name': self.name,
|
| - 'properties': self.properties,
|
| - 'mod_data': dict(self.mod_data.iteritems()),
|
| - 'step_data': dict(self.step_data.iteritems()),
|
| - 'expected_exception': self.expected_exception,
|
| - },)
|
| -
|
| -
|
| -class DisabledTestData(BaseTestData):
|
| - def __init__(self):
|
| - super(DisabledTestData, self).__init__(False)
|
| -
|
| - def __getattr__(self, name):
|
| - return self
|
| -
|
| - def pop_placeholder(self, _name_pieces):
|
| - return self
|
| -
|
| - def pop_step_test_data(self, _step_name, _step_test_data_fn):
|
| - return self
|
| -
|
| - def get_module_test_data(self, _module_name):
|
| - return self
|
| -
|
| - def is_unexpected_exception(self, exception): #pylint: disable=R0201
|
| - return False
|
| -
|
| -def mod_test_data(func):
|
| - @static_wraps(func)
|
| - def inner(self, *args, **kwargs):
|
| - assert isinstance(self, RecipeTestApi)
|
| - mod_name = self._module.NAME # pylint: disable=W0212
|
| - ret = TestData(None)
|
| - data = static_call(self, func, *args, **kwargs)
|
| - ret.mod_data[mod_name][inner.__name__] = data
|
| - return ret
|
| - return inner
|
| -
|
| -
|
| -def placeholder_step_data(func):
|
| - """Decorates RecipeTestApi member functions to allow those functions to
|
| - return just the placeholder data, instead of the normally required
|
| - StepTestData() object.
|
| -
|
| - The wrapped function may return either:
|
| - * <placeholder data>, <retcode or None>
|
| - * StepTestData containing exactly one PlaceholderTestData and possible a
|
| - retcode. This is useful for returning the result of another method which
|
| - is wrapped with placeholder_step_data.
|
| -
|
| - In either case, the wrapper function will return a StepTestData object with
|
| - the retcode and placeholder datum inserted with a name of:
|
| - (<Test module name>, <wrapped function name>)
|
| -
|
| - Say you had a 'foo_module' with the following RecipeTestApi:
|
| - class FooTestApi(RecipeTestApi):
|
| - @placeholder_step_data
|
| - @staticmethod
|
| - def cool_method(data, retcode=None):
|
| - return ("Test data (%s)" % data), retcode
|
| -
|
| - @placeholder_step_data
|
| - def other_method(self, retcode=None):
|
| - return self.cool_method('hammer time', retcode)
|
| -
|
| - Code calling cool_method('hello') would get a StepTestData:
|
| - StepTestData(
|
| - placeholder_data = {
|
| - ('foo_module', 'cool_method'): [
|
| - PlaceholderTestData('Test data (hello)')
|
| - ]
|
| - },
|
| - retcode = None
|
| - )
|
| -
|
| - Code calling other_method(50) would get a StepTestData:
|
| - StepTestData(
|
| - placeholder_data = {
|
| - ('foo_module', 'other_method'): [
|
| - PlaceholderTestData('Test data (hammer time)')
|
| - ]
|
| - },
|
| - retcode = 50
|
| - )
|
| - """
|
| - @static_wraps(func)
|
| - def inner(self, *args, **kwargs):
|
| - assert isinstance(self, RecipeTestApi)
|
| - mod_name = self._module.NAME # pylint: disable=W0212
|
| - data = static_call(self, func, *args, **kwargs)
|
| - if isinstance(data, StepTestData):
|
| - all_data = [i
|
| - for l in data.placeholder_data.values()
|
| - for i in l]
|
| - assert len(all_data) == 1, (
|
| - 'placeholder_step_data is only expecting a single placeholder datum. '
|
| - 'Got: %r' % data
|
| - )
|
| - placeholder_data, retcode = all_data[0], data.retcode
|
| - else:
|
| - placeholder_data, retcode = data
|
| - placeholder_data = PlaceholderTestData(placeholder_data)
|
| -
|
| - ret = StepTestData()
|
| - ret.placeholder_data[(mod_name, inner.__name__)].append(placeholder_data)
|
| - ret.retcode = retcode
|
| - return ret
|
| - return inner
|
| -
|
| -
|
| -class RecipeTestApi(object):
|
| - """Provides testing interface for GenTest method.
|
| -
|
| - There are two primary components to the test api:
|
| - * Test data creation methods (test and step_data)
|
| - * test_api's from all the modules in DEPS.
|
| -
|
| - Every test in GenTests(api) takes the form:
|
| - yield <instance of TestData>
|
| -
|
| - There are 4 basic pieces to TestData:
|
| - name - The name of the test.
|
| - properties - Simple key-value dictionary which is used as the combined
|
| - build_properties and factory_properties for this test.
|
| - mod_data - Module-specific testing data (see the platform module for a
|
| - good example). This is testing data which is only used once at
|
| - the start of the execution of the recipe. Modules should
|
| - provide methods to get their specific test information. See
|
| - the platform module's test_api for a good example of this.
|
| - step_data - Step-specific data. There are two major components to this.
|
| - retcode - The return code of the step
|
| - placeholder_data - A mapping from placeholder name to the a list of
|
| - PlaceholderTestData objects, one for each instance
|
| - of that kind of Placeholder in the step.
|
| - stdout, stderr - PlaceholderTestData objects for stdout and stderr.
|
| -
|
| - TestData objects are concatenatable, so it's convenient to phrase test cases
|
| - as a series of added TestData objects. For example:
|
| - DEPS = ['properties', 'platform', 'json']
|
| - def GenTests(api):
|
| - yield (
|
| - api.test('try_win64') +
|
| - api.properties.tryserver(power_level=9001) +
|
| - api.platform('win', 64) +
|
| - api.step_data(
|
| - 'some_step',
|
| - api.json.output("bobface"),
|
| - api.json.output({'key': 'value'})
|
| - )
|
| - )
|
| -
|
| - This example would run a single test (named 'try_win64') with the standard
|
| - tryserver properties (plus an extra property 'power_level' whose value was
|
| - over 9000). The test would run as if it were being run on a 64-bit windows
|
| - installation, and the step named 'some_step' would have its first json output
|
| - placeholder be mocked to return '"bobface"', and its second json output
|
| - placeholder be mocked to return '{"key": "value"}'.
|
| -
|
| - The properties.tryserver() call is documented in the 'properties' module's
|
| - test_api.
|
| - The platform() call is documented in the 'platform' module's test_api.
|
| - The json.output() call is documented in the 'json' module's test_api.
|
| - """
|
| - def __init__(self, module=None):
|
| - """Note: Injected dependencies are NOT available in __init__()."""
|
| - # If we're the 'root' api, inject directly into 'self'.
|
| - # Otherwise inject into 'self.m'
|
| - self.m = self if module is None else ModuleInjectionSite()
|
| - self._module = module
|
| -
|
| - @staticmethod
|
| - def test(name):
|
| - """Returns a new empty TestData with the name filled in.
|
| -
|
| - Use in GenTests:
|
| - def GenTests(api):
|
| - yield api.test('basic')
|
| - """
|
| - return TestData(name)
|
| -
|
| - @staticmethod
|
| - def empty_test_data():
|
| - """Returns a TestData with no information.
|
| -
|
| - This is the identity of the + operator for combining TestData.
|
| - """
|
| - return TestData(None)
|
| -
|
| - @staticmethod
|
| - def _step_data(name, *data, **kwargs):
|
| - """Returns a new TestData with the mock data filled in for a single step.
|
| -
|
| - Used by step_data and override_step_data.
|
| -
|
| - Args:
|
| - name - The name of the step we're providing data for
|
| - data - Zero or more StepTestData objects. These may fill in placeholder
|
| - data for zero or more modules, as well as possibly setting the
|
| - retcode for this step.
|
| - retcode=(int or None) - Override the retcode for this step, even if it
|
| - was set by |data|. This must be set as a keyword arg.
|
| - stdout - StepTestData object with placeholder data for a step's stdout.
|
| - stderr - StepTestData object with placeholder data for a step's stderr.
|
| - override=(bool) - This step data completely replaces any previously
|
| - generated step data, instead of adding on to it.
|
| -
|
| - Use in GenTests:
|
| - # Hypothetically, suppose that your recipe has default test data for two
|
| - # steps 'init' and 'sync' (probably via recipe_api.inject_test_data()).
|
| - # For this example, lets say that the default test data looks like:
|
| - # api.step_data('init', api.json.output({'some': ["cool", "json"]}))
|
| - # AND
|
| - # api.step_data('sync', api.json.output({'src': {'rev': 100}}))
|
| - # Then, your GenTests code may augment or replace this data like:
|
| -
|
| - def GenTests(api):
|
| - yield (
|
| - api.test('more') +
|
| - api.step_data( # Adds step data for a step with no default test data
|
| - 'mystep',
|
| - api.json.output({'legend': ['...', 'DARY!']})
|
| - ) +
|
| - api.step_data( # Adds retcode to default step_data for this step
|
| - 'init',
|
| - retcode=1
|
| - ) +
|
| - api.override_step_data( # Removes json output and overrides retcode
|
| - 'sync',
|
| - retcode=100
|
| - )
|
| - )
|
| - """
|
| - assert all(isinstance(d, StepTestData) for d in data)
|
| - ret = TestData(None)
|
| - if data:
|
| - ret.step_data[name] = reduce(sum, data)
|
| - if 'retcode' in kwargs:
|
| - ret.step_data[name].retcode = kwargs['retcode']
|
| - if 'override' in kwargs:
|
| - ret.step_data[name].override = kwargs['override']
|
| - for key in ('stdout', 'stderr'):
|
| - if key in kwargs:
|
| - stdio_test_data = kwargs[key]
|
| - assert isinstance(stdio_test_data, StepTestData)
|
| - setattr(ret.step_data[name], key, stdio_test_data.unwrap_placeholder())
|
| - return ret
|
| -
|
| - def step_data(self, name, *data, **kwargs):
|
| - """See _step_data()"""
|
| - return self._step_data(name, *data, **kwargs)
|
| - step_data.__doc__ = _step_data.__doc__
|
| -
|
| - def override_step_data(self, name, *data, **kwargs):
|
| - """See _step_data()"""
|
| - kwargs['override'] = True
|
| - return self._step_data(name, *data, **kwargs)
|
| - override_step_data.__doc__ = _step_data.__doc__
|
| -
|
| - def expect_exception(self, exc_type): #pylint: disable=R0201
|
| - ret = TestData(None)
|
| - ret.expect_exception(exc_type)
|
| - return ret
|
|
|