You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
5.1 KiB
174 lines
5.1 KiB
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2020 - The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from abc import ABC, abstractmethod
|
|
from datetime import datetime, timedelta
|
|
from mobly import signals
|
|
from threading import Condition
|
|
|
|
from cert.event_stream import static_remaining_time_delta
|
|
from cert.truth import assertThat
|
|
|
|
|
|
class IHasBehaviors(ABC):
|
|
|
|
@abstractmethod
|
|
def get_behaviors(self):
|
|
pass
|
|
|
|
|
|
def anything():
|
|
return lambda obj: True
|
|
|
|
|
|
def when(has_behaviors):
|
|
assertThat(isinstance(has_behaviors, IHasBehaviors)).isTrue()
|
|
return has_behaviors.get_behaviors()
|
|
|
|
|
|
def IGNORE_UNHANDLED(obj):
|
|
pass
|
|
|
|
|
|
class SingleArgumentBehavior(object):
|
|
|
|
def __init__(self, reply_stage_factory):
|
|
self._reply_stage_factory = reply_stage_factory
|
|
self._instances = []
|
|
self._invoked_obj = []
|
|
self._invoked_condition = Condition()
|
|
self.set_default_to_crash()
|
|
|
|
def begin(self, matcher):
|
|
return PersistenceStage(self, matcher, self._reply_stage_factory)
|
|
|
|
def append(self, behavior_instance):
|
|
self._instances.append(behavior_instance)
|
|
|
|
def set_default(self, fn):
|
|
assertThat(fn).isNotNone()
|
|
self._default_fn = fn
|
|
|
|
def set_default_to_crash(self):
|
|
self._default_fn = None
|
|
|
|
def set_default_to_ignore(self):
|
|
self._default_fn = IGNORE_UNHANDLED
|
|
|
|
def run(self, obj):
|
|
for instance in self._instances:
|
|
if instance.try_run(obj):
|
|
self.__obj_invoked(obj)
|
|
return
|
|
if self._default_fn is not None:
|
|
# IGNORE_UNHANDLED is also a default fn
|
|
self._default_fn(obj)
|
|
self.__obj_invoked(obj)
|
|
else:
|
|
raise signals.TestFailure(
|
|
"%s: behavior for %s went unhandled" % (self._reply_stage_factory().__class__.__name__, obj),
|
|
extras=None)
|
|
|
|
def __obj_invoked(self, obj):
|
|
self._invoked_condition.acquire()
|
|
self._invoked_obj.append(obj)
|
|
self._invoked_condition.notify()
|
|
self._invoked_condition.release()
|
|
|
|
def wait_until_invoked(self, matcher, times, timeout):
|
|
end_time = datetime.now() + timeout
|
|
invoked_times = 0
|
|
while datetime.now() < end_time and invoked_times < times:
|
|
remaining = static_remaining_time_delta(end_time)
|
|
invoked_times = sum((matcher(i) for i in self._invoked_obj))
|
|
self._invoked_condition.acquire()
|
|
self._invoked_condition.wait(remaining.total_seconds())
|
|
self._invoked_condition.release()
|
|
return invoked_times == times
|
|
|
|
|
|
class PersistenceStage(object):
|
|
|
|
def __init__(self, behavior, matcher, reply_stage_factory):
|
|
self._behavior = behavior
|
|
self._matcher = matcher
|
|
self._reply_stage_factory = reply_stage_factory
|
|
|
|
def then(self, times=1):
|
|
reply_stage = self._reply_stage_factory()
|
|
reply_stage.init(self._behavior, self._matcher, times)
|
|
return reply_stage
|
|
|
|
def always(self):
|
|
return self.then(times=-1)
|
|
|
|
|
|
class ReplyStage(object):
|
|
|
|
def init(self, behavior, matcher, persistence):
|
|
self._behavior = behavior
|
|
self._matcher = matcher
|
|
self._persistence = persistence
|
|
|
|
def _commit(self, fn):
|
|
self._behavior.append(BehaviorInstance(self._matcher, self._persistence, fn))
|
|
|
|
|
|
class BehaviorInstance(object):
|
|
|
|
def __init__(self, matcher, persistence, fn):
|
|
self._matcher = matcher
|
|
self._persistence = persistence
|
|
self._fn = fn
|
|
self._called_count = 0
|
|
|
|
def try_run(self, obj):
|
|
if not self._matcher(obj):
|
|
return False
|
|
if self._persistence >= 0:
|
|
if self._called_count >= self._persistence:
|
|
return False
|
|
self._called_count += 1
|
|
self._fn(obj)
|
|
return True
|
|
|
|
|
|
class BoundVerificationStage(object):
|
|
|
|
def __init__(self, behavior, matcher, timeout):
|
|
self._behavior = behavior
|
|
self._matcher = matcher
|
|
self._timeout = timeout
|
|
|
|
def times(self, times=1):
|
|
return self._behavior.wait_until_invoked(self._matcher, times, self._timeout)
|
|
|
|
|
|
class WaitForBehaviorSubject(object):
|
|
|
|
def __init__(self, behaviors, timeout):
|
|
self._behaviors = behaviors
|
|
self._timeout = timeout
|
|
|
|
def __getattr__(self, item):
|
|
behavior = getattr(self._behaviors, item + "_behavior")
|
|
t = self._timeout
|
|
return lambda matcher: BoundVerificationStage(behavior, matcher, t)
|
|
|
|
|
|
def wait_until(i_has_behaviors, timeout=timedelta(seconds=3)):
|
|
return WaitForBehaviorSubject(i_has_behaviors.get_behaviors(), timeout)
|