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

Unified Diff: recipe_engine/recipe_test_api.py

Issue 1773273003: Make output placeholders like json.output index-able by name. (Closed) Base URL: https://chromium.googlesource.com/external/github.com/luci/recipes-py@master
Patch Set: Address comments. Created 4 years, 9 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
« no previous file with comments | « no previous file | recipe_engine/step_runner.py » ('j') | recipe_engine/step_runner.py » ('J')
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: recipe_engine/recipe_test_api.py
diff --git a/recipe_engine/recipe_test_api.py b/recipe_engine/recipe_test_api.py
index a5e6a0b5e531b002c88007745b3ef4d54e8cb917..35c334aeca6d534a2e5fd251021c6552004ebbac 100644
--- a/recipe_engine/recipe_test_api.py
+++ b/recipe_engine/recipe_test_api.py
@@ -8,7 +8,7 @@ import contextlib
from .util import ModuleInjectionSite, static_call, static_wraps
from .types import freeze
-def combineify(name, dest, a, b):
+def combineify(name, dest, a, b, overwrite=False):
"""
Combines dictionary members in two objects into a third one using addition.
@@ -17,12 +17,17 @@ def combineify(name, dest, a, b):
dest - the destination object
a - the first source object
b - the second source object
+ overwrite - if True, for the same key, overwrite value from a with the one
+ from b; otherwise, use addition to merge them.
"""
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
+ if not overwrite:
+ dest_dict[k] += v
+ else:
+ dest_dict[k] = v
iannucci 2016/03/22 22:54:24 I don't think this needs distinction any more: all
stgao 2016/03/22 23:40:25 We still need this distinction, as we discussed of
else:
dest_dict[k] = v
@@ -37,13 +42,26 @@ class BaseTestData(object):
return self._enabled
-class PlaceholderTestData(BaseTestData):
+class InputPlaceholderTestData(BaseTestData):
def __init__(self, data=None):
- super(PlaceholderTestData, self).__init__()
+ super(InputPlaceholderTestData, self).__init__()
self.data = data
def __repr__(self):
- return "PlaceholderTestData(%r)" % (self.data,)
+ return "InputPlaceholderTestData(%r)" % (self.data,)
iannucci 2016/03/22 22:54:24 we don't need this class at all
stgao 2016/03/22 23:40:25 We need this one for the stdin below at line #139.
+
+
+class OutputPlaceholderTestData(BaseTestData):
+ def __init__(self, data=None, name=None):
+ super(OutputPlaceholderTestData, self).__init__()
+ self.data = data
+ self.name = name
+
+ def __repr__(self):
+ if self.name is None:
+ return "OutputPlaceholderTestData(%r)" % (self.data,)
iannucci 2016/03/22 22:54:24 maybe `OutputPlaceholderTestData(DEFAULT, %r)` ?
stgao 2016/03/22 23:40:25 Done.
+ else:
+ return "OutputPlaceholderTestData(%r, %r)" % (self.name, self.data,)
class StepTestData(BaseTestData):
@@ -55,8 +73,8 @@ class StepTestData(BaseTestData):
"""
def __init__(self):
super(StepTestData, self).__init__()
- # { (module, placeholder) -> [data] }. These are for output placeholders.
- self.placeholder_data = collections.defaultdict(list)
+ # { (module, placeholder, name) -> data }. Data are for output placeholders.
+ self.placeholder_data = collections.defaultdict(dict)
self.override = False
self._stdout = None
self._stderr = None
@@ -70,7 +88,7 @@ class StepTestData(BaseTestData):
ret = StepTestData()
- combineify('placeholder_data', ret, self, other)
+ combineify('placeholder_data', ret, self, other, overwrite=True)
# pylint: disable=W0212
ret._stdout = other._stdout or self._stdout
@@ -83,18 +101,13 @@ class StepTestData(BaseTestData):
return ret
def unwrap_placeholder(self):
- # {(module, placeholder): [data]} => data.
+ # {(module, placeholder, name): 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()
+ return self.placeholder_data.values()[0]
+
+ def pop_placeholder(self, module_name, placeholder_name, name):
+ return self.placeholder_data.pop(
+ (module_name, placeholder_name, name), OutputPlaceholderTestData())
@property
def retcode(self): # pylint: disable=E0202
@@ -106,25 +119,25 @@ class StepTestData(BaseTestData):
@property
def stdout(self):
- return self._stdout or PlaceholderTestData(None)
+ return self._stdout or OutputPlaceholderTestData(None)
@stdout.setter
def stdout(self, value):
- assert isinstance(value, PlaceholderTestData)
+ assert isinstance(value, OutputPlaceholderTestData)
self._stdout = value
@property
def stderr(self):
- return self._stderr or PlaceholderTestData(None)
+ return self._stderr or OutputPlaceholderTestData(None)
@stderr.setter
def stderr(self, value):
- assert isinstance(value, PlaceholderTestData)
+ assert isinstance(value, OutputPlaceholderTestData)
self._stderr = value
@property
def stdin(self): # pylint: disable=R0201
- return PlaceholderTestData(None)
+ return InputPlaceholderTestData(None)
def __repr__(self):
return "StepTestData(%r)" % ({
@@ -257,7 +270,7 @@ class DisabledTestData(BaseTestData):
def __getattr__(self, name):
return self
- def pop_placeholder(self, _name_pieces):
+ def pop_placeholder(self, _module_name, _placeholder_name, _name):
return self
def pop_step_test_data(self, _step_name, _step_test_data_fn):
@@ -288,42 +301,40 @@ def placeholder_step_data(func):
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
+ * <placeholder data>, <retcode or None>, <name or None>
+ * StepTestData containing exactly one OutputPlaceholderTestData 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>)
+ (<Test module name>, <wrapped function name>, <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
+ def cool_method(data, retcode=None, name=None):
+ return ("Test data (%s)" % data), retcode, name
@placeholder_step_data
- def other_method(self, retcode=None):
- return self.cool_method('hammer time', retcode)
+ def other_method(self, retcode=None, name=None):
+ return self.cool_method('hammer time', retcode=retcode, name=name)
- Code calling cool_method('hello') would get a StepTestData:
+ Code calling cool_method('hello', name='cool1') would get a StepTestData:
StepTestData(
placeholder_data = {
- ('foo_module', 'cool_method'): [
- PlaceholderTestData('Test data (hello)')
- ]
+ ('foo_module', 'cool_method', 'cool1') :
+ OutputPlaceholderTestData('Test data (hello)')
},
retcode = None
)
- Code calling other_method(50) would get a StepTestData:
+ Code calling other_method(retcode=50, name='other1') would get a StepTestData:
StepTestData(
placeholder_data = {
- ('foo_module', 'other_method'): [
- PlaceholderTestData('Test data (hammer time)')
- ]
+ ('foo_module', 'other_method', 'other1'):
+ OutputPlaceholderTestData('Test data (hammer time)')
},
retcode = 50
)
@@ -334,20 +345,20 @@ def placeholder_step_data(func):
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]
+ all_data = data.placeholder_data.values()
assert len(all_data) == 1, (
'placeholder_step_data is only expecting a single output placeholder '
'datum. Got: %r' % data
)
placeholder_data, retcode = all_data[0], data.retcode
else:
- placeholder_data, retcode = data
- placeholder_data = PlaceholderTestData(placeholder_data)
+ placeholder_data, retcode, name = data
+ placeholder_data = OutputPlaceholderTestData(
+ data=placeholder_data, name=name)
ret = StepTestData()
- ret.placeholder_data[(mod_name, inner.__name__)].append(placeholder_data)
+ key = (mod_name, inner.__name__, placeholder_data.name)
+ ret.placeholder_data[key] = placeholder_data
ret.retcode = retcode
return ret
return inner
@@ -374,10 +385,10 @@ class RecipeTestApi(object):
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.
+ placeholder_data - A mapping from placeholder name to the
+ OutputPlaceholderTestData object in the step.
+ stdout, stderr - OutputPlaceholderTestData 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:
@@ -389,17 +400,18 @@ class RecipeTestApi(object):
api.platform('win', 64) +
api.step_data(
'some_step',
- api.json.output("bobface"),
- api.json.output({'key': 'value'})
+ api.json.output("bobface", name="a"),
+ api.json.output({'key': 'value'}, name="b")
)
)
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"}'.
+ installation, and the step named 'some_step' would have the json output of
+ the placeholder with name "a" be mocked to return '"bobface"', and the json
+ output of the placeholder with name "b" be mocked to return
+ '{"key": "value"}'.
The properties.tryserver() call is documented in the 'properties' module's
test_api.
@@ -439,9 +451,9 @@ class RecipeTestApi(object):
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.
+ data - Zero or more StepTestData objects. These may fill in output
+ 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 a single output placeholder datum for a
@@ -489,7 +501,8 @@ class RecipeTestApi(object):
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())
+ setattr(ret.step_data[name], key,
+ stdio_test_data.unwrap_placeholder())
return ret
def step_data(self, name, *data, **kwargs):
« no previous file with comments | « no previous file | recipe_engine/step_runner.py » ('j') | recipe_engine/step_runner.py » ('J')

Powered by Google App Engine
This is Rietveld 408576698