1
0
mirror of https://github.com/ARM-software/workload-automation.git synced 2025-02-20 20:09:11 +00:00

Adding intialize and finalize methods to workloads that will only be invoked once per run

- added initialze and finalize methods to workloads, which were the only
  major extension types that did not have them
- Semanatics for initialize/finalize for *all* Extensions are changed so
  that now they will always run at most once per run. They will not be
  executed twice even if invoke via istances of different subclasses (if
  those subclasses defined their own verions, then their versions will
  be invoked once each, but the base version will only get invoked
  once).
This commit is contained in:
Sergei Trofimov 2015-06-11 17:39:17 +01:00
parent 557b792c77
commit b3a0933221
7 changed files with 153 additions and 21 deletions

View File

@ -527,6 +527,10 @@ class Runner(object):
self.logger.info('Initializing device')
self.device.initialize(self.context)
self.logger.info('Initializing workloads')
for workload_spec in self.context.config.workload_specs:
workload_spec.workload.initialize(self.context)
props = self.device.get_properties(self.context)
self.context.run_info.device_properties = props
self.result_manager.initialize(self.context)

View File

@ -392,7 +392,8 @@ class ExtensionMeta(type):
('core_modules', str, ListCollection),
]
virtual_methods = ['validate']
virtual_methods = ['validate', 'initialize', 'finalize']
global_virtuals = ['initialize', 'finalize']
def __new__(mcs, clsname, bases, attrs):
mcs._propagate_attributes(bases, attrs)
@ -441,13 +442,13 @@ class ExtensionMeta(type):
super(cls, self).vmname()
.. note:: current implementation imposes a restriction in that
parameters into the function *must* be passed as keyword
arguments. There *must not* be positional arguments on
virutal method invocation.
This also ensures that the methods that have beend identified as
"globally virtual" are executed exactly once per WA execution, even if
invoked through instances of different subclasses
"""
methods = {}
called_globals = set()
for vmname in mcs.virtual_methods:
clsmethod = getattr(cls, vmname, None)
if clsmethod:
@ -455,11 +456,24 @@ class ExtensionMeta(type):
methods[vmname] = [bm for bm in basemethods if bm != clsmethod]
methods[vmname].append(clsmethod)
def wrapper(self, __name=vmname, **kwargs):
for dm in methods[__name]:
dm(self, **kwargs)
def generate_method_wrapper(vname): # pylint: disable=unused-argument
# this creates a closure with the method name so that it
# does not need to be passed to the wrapper as an argument,
# leaving the wrapper to accept exactly the same set of
# arguments as the method it is wrapping.
name__ = vmname # pylint: disable=cell-var-from-loop
setattr(cls, vmname, wrapper)
def wrapper(self, *args, **kwargs):
for dm in methods[name__]:
if name__ in mcs.global_virtuals:
if dm not in called_globals:
dm(self, *args, **kwargs)
called_globals.add(dm)
else:
dm(self, *args, **kwargs)
return wrapper
setattr(cls, vmname, generate_method_wrapper(vmname))
class Extension(object):
@ -539,6 +553,12 @@ class Extension(object):
for param in self.parameters:
param.validate(self)
def initialize(self, *args, **kwargs):
pass
def finalize(self, *args, **kwargs):
pass
def check_artifacts(self, context, level):
"""
Make sure that all mandatory artifacts have been generated.

View File

@ -288,9 +288,15 @@ def install(instrument):
attr = getattr(instrument, attr_name)
if not callable(attr):
raise ValueError('Attribute {} not callable in {}.'.format(attr_name, instrument))
arg_num = len(inspect.getargspec(attr).args)
if not arg_num == 2:
raise ValueError('{} must take exactly 2 arguments; {} given.'.format(attr_name, arg_num))
argspec = inspect.getargspec(attr)
arg_num = len(argspec.args)
# Instrument callbacks will be passed exactly two arguments: self
# (the instrument instance to which the callback is bound) and
# context. However, we also allow callbacks to capture the context
# in variable arguments (declared as "*args" in the definition).
if arg_num > 2 or (arg_num < 2 and argspec.varargs is None):
message = '{} must take exactly positional arguments; {} given.'
raise ValueError(message.format(attr_name, arg_num))
logger.debug('\tConnecting %s to %s', attr.__name__, SIGNAL_MAP[stripped_attr_name])
mc = ManagedCallback(instrument, attr)
@ -379,6 +385,12 @@ class Instrument(Extension):
self.is_enabled = True
self.is_broken = False
def initialize(self, context): # pylint: disable=arguments-differ
pass
def finalize(self, context): # pylint: disable=arguments-differ
pass
def __str__(self):
return self.name

View File

@ -140,7 +140,7 @@ class ResultProcessor(Extension):
"""
def initialize(self, context):
def initialize(self, context): # pylint: disable=arguments-differ
pass
def process_iteration_result(self, result, context):
@ -155,7 +155,7 @@ class ResultProcessor(Extension):
def export_run_result(self, result, context):
pass
def finalize(self, context):
def finalize(self, context): # pylint: disable=arguments-differ
pass

View File

@ -53,18 +53,25 @@ class Workload(Extension):
def init_resources(self, context):
"""
May be optionally overridden by concrete instances in order to discover and initialise
necessary resources. This method will be invoked at most once during the execution:
before running any workloads, and before invocation of ``validate()``, but after it is
clear that this workload will run (i.e. this method will not be invoked for workloads
that have been discovered but have not been scheduled run in the agenda).
This method may be used to perform early resource discovery and initialization. This is invoked
during the initial loading stage and before the device is ready, so cannot be used for any
device-dependent initialization. This method is invoked before the workload instance is
validated.
"""
pass
def initialize(self, context): # pylint: disable=arguments-differ
"""
This method should be used to perform once-per-run initialization of a workload instance, i.e.,
unlike ``setup()`` it will not be invoked on each iteration.
"""
pass
def setup(self, context):
"""
Perform the setup necessary to run the workload, such as copying the necessry files
Perform the setup necessary to run the workload, such as copying the necessary files
to the device, configuring the environments, etc.
This is also the place to perform any on-device checks prior to attempting to execute
@ -89,6 +96,9 @@ class Workload(Extension):
""" Perform any final clean up for the Workload. """
pass
def finalize(self, context): # pylint: disable=arguments-differ
pass
def __str__(self):
return '<Workload {}>'.format(self.name)

View File

@ -27,7 +27,7 @@ from wlauto.core.execution import BySpecRunner, ByIterationRunner
from wlauto.exceptions import DeviceError
from wlauto.core.configuration import WorkloadRunSpec, RebootPolicy
from wlauto.core.instrumentation import Instrument
from wlauto.core.device import Device
from wlauto.core.device import Device, DeviceMeta
from wlauto.core import instrumentation, signal
from wlauto.core.workload import Workload
from wlauto.core.result import IterationResult
@ -61,8 +61,37 @@ class Mock(object):
pass
class BadDeviceMeta(DeviceMeta):
@classmethod
def _implement_virtual(mcs, cls, bases):
"""
This version of _implement_virtual does not inforce "call global virutals only once"
policy, so that intialize() and finalize() my be invoked multiple times to test that
the errors they generated are handled correctly.
"""
# pylint: disable=cell-var-from-loop,unused-argument
methods = {}
for vmname in mcs.virtual_methods:
clsmethod = getattr(cls, vmname, None)
if clsmethod:
basemethods = [getattr(b, vmname) for b in bases if hasattr(b, vmname)]
methods[vmname] = [bm for bm in basemethods if bm != clsmethod]
methods[vmname].append(clsmethod)
def generate_method_wrapper(vname):
name__ = vmname
def wrapper(self, *args, **kwargs):
for dm in methods[name__]:
dm(self, *args, **kwargs)
return wrapper
setattr(cls, vmname, generate_method_wrapper(vmname))
class BadDevice(Device):
__metaclass__ = BadDeviceMeta
def __init__(self, when_to_fail, exception=DeviceError):
#pylint: disable=super-init-not-called
self.when_to_fail = when_to_fail

View File

@ -220,6 +220,63 @@ class ExtensionMetaTest(TestCase):
assert_equal(acid.v2, 2)
assert_equal(acid.vv2, 2)
def test_initialization(self):
class MyExt(Extension):
name = 'myext'
values = {'a': 0}
def __init__(self, *args, **kwargs):
super(MyExt, self).__init__(*args, **kwargs)
self.instance_init = 0
def initialize(self):
self.values['a'] += 1
class MyChildExt(MyExt):
name = 'mychildext'
def initialize(self):
self.instance_init += 1
ext = _instantiate(MyChildExt)
ext.initialize()
assert_equal(MyExt.values['a'], 1)
assert_equal(ext.instance_init, 1)
def test_initialization_happens_once(self):
class MyExt(Extension):
name = 'myext'
values = {'a': 0}
def __init__(self, *args, **kwargs):
super(MyExt, self).__init__(*args, **kwargs)
self.instance_init = 0
self.instance_validate = 0
def initialize(self):
self.values['a'] += 1
def validate(self):
self.instance_validate += 1
class MyChildExt(MyExt):
name = 'mychildext'
def initialize(self):
self.instance_init += 1
def validate(self):
self.instance_validate += 1
ext1 = _instantiate(MyExt)
ext2 = _instantiate(MyExt)
ext3 = _instantiate(MyChildExt)
ext1.initialize()
ext2.initialize()
ext3.initialize()
ext1.validate()
ext2.validate()
ext3.validate()
assert_equal(MyExt.values['a'], 1)
assert_equal(ext1.instance_init, 0)
assert_equal(ext3.instance_init, 1)
assert_equal(ext1.instance_validate, 1)
assert_equal(ext3.instance_validate, 2)
class ParametersTest(TestCase):