Feedback client interface.
This includes the interface definition (classes and constants) as
described in the design doc.
BUG=b:26162591
TEST=None
Change-Id: Iff67506e4e5d02d04186f53c72df00c45c6e43df
Reviewed-on: https://chromium-review.googlesource.com/319197
Commit-Ready: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Simran Basi <sbasi@chromium.org>
diff --git a/client/common_lib/feedback/client.py b/client/common_lib/feedback/client.py
new file mode 100644
index 0000000..19e8240
--- /dev/null
+++ b/client/common_lib/feedback/client.py
@@ -0,0 +1,255 @@
+# Copyright 2016 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""Interactive feedback layer abstraction."""
+
+from autotest_lib.client.common_lib import error
+
+
+# All known queries.
+#
+# Audio playback and recording testing.
+QUERY_AUDIO_PLAYBACK_SILENT = 0
+QUERY_AUDIO_PLAYBACK_AUDIBLE = 1
+QUERY_AUDIO_RECORDING = 2
+# Motion sensor testing.
+QUERY_MOTION_RESTING = 10
+QUERY_MOTION_MOVING = 11
+# USB keyboard plugging and typing.
+QUERY_KEYBOARD_PLUG = 20
+QUERY_KEYBOARD_TYPE = 21
+# GPIO write/read testing.
+QUERY_GPIO_WRITE = 30
+QUERY_GPIO_READ = 31
+# On-board light testing.
+QUERY_LIGHT_ON = 40
+# TODO(garnold) Camera controls testing.
+#QUERY_CAMERA_???
+# Power management testing.
+QUERY_POWER_WAKEUP = 60
+
+
+# Feedback client definition.
+#
+class Client(object):
+ """Interface for an interactive feedback layer."""
+
+ def __init__(self):
+ self._initialized = False
+ self._finalized = False
+
+
+ def _check_active(self):
+ """Ensure that the client was initialized and not finalized."""
+ if not self._initialized:
+ raise error.TestError('Client was not initialized')
+ if self._finalized:
+ raise error.TestError('Client was already finalized')
+
+
+ def __enter__(self):
+ self._check_active()
+ return self
+
+
+ def __exit__(self, ex_type, ex_val, ex_tb):
+ self.finalize()
+
+
+ def initialize(self, test, host):
+ """Initializes the feedback object.
+
+ This method should be called once prior to any other call.
+
+ @param test: An object representing the test case.
+ @param host: An object representing the DUT.
+
+ @raise TestError: There was an error during initialization.
+ """
+ if self._initialized:
+ raise error.TestError('Client was already initialized')
+ if self._finalized:
+ raise error.TestError('Client was already finalized')
+ self._initialize_impl(test, host)
+ self._initialized = True
+ return self
+
+
+ def _initialize_impl(self, test, host):
+ """Implementation of feedback client initialization.
+
+ This should be implemented in concrete subclasses.
+ """
+ raise NotImplementedError
+
+
+ def new_query(self, query_id):
+ """Instantiates a new query.
+
+ @param query_id: A query identifier (see QUERY_ constants above).
+
+ @return A query object.
+
+ @raise TestError: Query is invalid or not supported.
+ """
+ self._check_active()
+ return self._new_query_impl(query_id)
+
+
+ def _new_query_impl(self, query_id):
+ """Implementation of new query instantiation.
+
+ This should be implemented in concrete subclasses.
+ """
+ raise NotImplementedError
+
+
+ def finalize(self):
+ """Finalizes the feedback object.
+
+ This method should be called once when done using the client.
+
+ @raise TestError: There was an error while finalizing the client.
+ """
+ self._check_active()
+ self._finalize_impl()
+ self._finalized = True
+
+
+ def _finalize_impl(self):
+ """Implementation of feedback client finalization.
+
+ This should be implemented in concrete subclasses.
+ """
+ raise NotImplementedError
+
+
+# Feedback query definitions.
+#
+class _Query(object):
+ """Interactive feedback query base class.
+
+ This class is further derived and should not be inherited directly.
+ """
+
+ def __init__(self):
+ self._prepare_called = False
+ self._validate_called = False
+
+
+ def prepare(self, **kwargs):
+ """Prepares the tester for providing or capturing feedback.
+
+ @raise TestError: Query preparation failed.
+ """
+ if self._prepare_called:
+ raise error.TestError('Prepare was already called')
+ self._prepare_impl(**kwargs)
+ self._prepare_called = True
+
+
+ def _prepare_impl(self, **kwargs):
+ """Implementation of query preparation logic.
+
+ This should be implemented in concrete subclasses.
+ """
+ raise NotImplementedError
+
+
+ def validate(self, **kwargs):
+ """Validates the interactive input/output result.
+
+ This enforces that the method is called at most once, then delegates
+ to an underlying implementation method.
+
+ @raise TestError: An error occurred during validation.
+ @raise TestFail: Query validation failed.
+ """
+ if self._validate_called:
+ raise error.TestError('Validate was already called')
+ self._validate_impl(**kwargs)
+ self._validate_called = True
+
+
+ def _validate_impl(self, **kwargs):
+ """Implementation of query validation logic.
+
+ This should be implemented in concrete subclasses.
+ """
+ raise NotImplementedError
+
+
+class OutputQuery(_Query):
+ """Interface for an output interactive feedback query.
+
+ This class mandates that prepare() is called prior to validate().
+ Subclasses should override implementations of _prepare_impl() and
+ _validate_impl().
+ """
+
+ def __init__(self):
+ super(OutputQuery, self).__init__()
+
+
+ def validate(self, **kwargs):
+ """Validates the interactive input/output result.
+
+ This enforces the precondition and delegates to the base method.
+
+ @raise TestError: An error occurred during validation.
+ @raise TestFail: Query validation failed.
+ """
+ if not self._prepare_called:
+ raise error.TestError('Prepare was not called')
+ super(OutputQuery, self).validate(**kwargs)
+
+
+class InputQuery(_Query):
+ """Interface for an input interactive feedback query.
+
+ This class mandates that prepare() is called first, then emit(), and
+ finally validate(). Subclasses should override implementations of
+ _prepare_impl(), _emit_impl() and _validate_impl().
+ """
+
+ def __init__(self):
+ super(InputQuery, self).__init__()
+ self._emit_called = False
+
+
+ def validate(self, **kwargs):
+ """Validates the interactive input/output result.
+
+ This enforces the precondition and delegates to the base method.
+
+ @raise TestError: An error occurred during validation.
+ @raise TestFail: Query validation failed.
+ """
+ if not self._emit_called:
+ raise error.TestError('Emit was not called')
+ super(InputQuery, self).validate(**kwargs)
+
+
+ def emit(self):
+ """Instructs the tester to emit a feedback to be captured by the test.
+
+ This enforces the precondition and ensures the method is called at most
+ once, then delegates to an underlying implementation method.
+
+ @raise TestError: An error occurred during emission.
+ """
+ if not self._prepare_called:
+ raise error.TestError('Prepare was not called')
+ if self._emit_called:
+ raise error.TestError('Emit was already called')
+ self._emit_impl()
+ self._emit_called = True
+
+
+ def _emit_impl(self):
+ """Implementation of query emission logic.
+
+ This should be implemented in concrete subclasses.
+ """
+ raise NotImplementedError