1# Copyright 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""Interactive feedback layer abstraction."""
6
7from autotest_lib.client.common_lib import error
8
9
10# All known queries.
11#
12# Audio playback and recording testing.
13QUERY_AUDIO_PLAYBACK_SILENT = 0
14QUERY_AUDIO_PLAYBACK_AUDIBLE = 1
15QUERY_AUDIO_RECORDING = 2
16# Motion sensor testing.
17QUERY_MOTION_RESTING = 10
18QUERY_MOTION_MOVING = 11
19# USB keyboard plugging and typing.
20QUERY_KEYBOARD_PLUG = 20
21QUERY_KEYBOARD_TYPE = 21
22# GPIO write/read testing.
23QUERY_GPIO_WRITE = 30
24QUERY_GPIO_READ = 31
25# On-board light testing.
26QUERY_LIGHT_ON = 40
27# TODO(garnold) Camera controls testing.
28#QUERY_CAMERA_???
29# Power management testing.
30QUERY_POWER_WAKEUP = 60
31
32INPUT_QUERIES = set((
33        QUERY_AUDIO_RECORDING,
34        QUERY_MOTION_RESTING,
35        QUERY_MOTION_MOVING,
36        QUERY_KEYBOARD_PLUG,
37        QUERY_KEYBOARD_TYPE,
38        QUERY_GPIO_READ,
39        QUERY_POWER_WAKEUP,
40))
41
42OUTPUT_QUERIES = set((
43        QUERY_AUDIO_PLAYBACK_SILENT,
44        QUERY_AUDIO_PLAYBACK_AUDIBLE,
45        QUERY_GPIO_WRITE,
46        QUERY_LIGHT_ON,
47))
48
49ALL_QUERIES = INPUT_QUERIES.union(OUTPUT_QUERIES)
50
51
52# Feedback client definition.
53#
54class Client(object):
55    """Interface for an interactive feedback layer."""
56
57    def __init__(self):
58        self._initialized = False
59        self._finalized = False
60
61
62    def _check_active(self):
63        """Ensure that the client was initialized and not finalized."""
64        if not self._initialized:
65            raise error.TestError('Client was not initialized')
66        if self._finalized:
67            raise error.TestError('Client was already finalized')
68
69
70    def __enter__(self):
71        self._check_active()
72        return self
73
74
75    def __exit__(self, ex_type, ex_val, ex_tb):
76        self.finalize()
77
78
79    def initialize(self, test, host=None):
80        """Initializes the feedback object.
81
82        This method should be called once prior to any other call.
83
84        @param test: An object representing the test case.
85        @param host: An object representing the DUT; required for server-side
86                     tests.
87
88        @raise TestError: There was an error during initialization.
89        """
90        if self._initialized:
91            raise error.TestError('Client was already initialized')
92        if self._finalized:
93            raise error.TestError('Client was already finalized')
94        self._initialize_impl(test, host)
95        self._initialized = True
96        return self
97
98
99    def _initialize_impl(self, test, host):
100        """Implementation of feedback client initialization.
101
102        This should be implemented in concrete subclasses.
103        """
104        raise NotImplementedError
105
106
107    def new_query(self, query_id):
108        """Instantiates a new query.
109
110        @param query_id: A query identifier (see QUERY_ constants above).
111
112        @return A query object.
113
114        @raise TestError: Query is invalid or not supported.
115        """
116        self._check_active()
117        return self._new_query_impl(query_id)
118
119
120    def _new_query_impl(self, query_id):
121        """Implementation of new query instantiation.
122
123        This should be implemented in concrete subclasses.
124        """
125        raise NotImplementedError
126
127
128    def finalize(self):
129        """Finalizes the feedback object.
130
131        This method should be called once when done using the client.
132
133        @raise TestError: There was an error while finalizing the client.
134        """
135        self._check_active()
136        self._finalize_impl()
137        self._finalized = True
138
139
140    def _finalize_impl(self):
141        """Implementation of feedback client finalization.
142
143        This should be implemented in concrete subclasses.
144        """
145        raise NotImplementedError
146
147
148# Feedback query definitions.
149#
150class _Query(object):
151    """Interactive feedback query base class.
152
153    This class is further derived and should not be inherited directly.
154    """
155
156    def __init__(self):
157        self._prepare_called = False
158        self._validate_called = False
159
160
161    def prepare(self, **kwargs):
162        """Prepares the tester for providing or capturing feedback.
163
164        @raise TestError: Query preparation failed.
165        """
166        if self._prepare_called:
167            raise error.TestError('Prepare was already called')
168        self._prepare_impl(**kwargs)
169        self._prepare_called = True
170
171
172    def _prepare_impl(self, **kwargs):
173        """Implementation of query preparation logic.
174
175        This should be implemented in concrete subclasses.
176        """
177        raise NotImplementedError
178
179
180    def validate(self, **kwargs):
181        """Validates the interactive input/output result.
182
183        This enforces that the method is called at most once, then delegates
184        to an underlying implementation method.
185
186        @raise TestError: An error occurred during validation.
187        @raise TestFail: Query validation failed.
188        """
189        if self._validate_called:
190            raise error.TestError('Validate was already called')
191        self._validate_impl(**kwargs)
192        self._validate_called = True
193
194
195    def _validate_impl(self, **kwargs):
196        """Implementation of query validation logic.
197
198        This should be implemented in concrete subclasses.
199        """
200        raise NotImplementedError
201
202
203class OutputQuery(_Query):
204    """Interface for an output interactive feedback query.
205
206    This class mandates that prepare() is called prior to validate().
207    Subclasses should override implementations of _prepare_impl() and
208    _validate_impl().
209    """
210
211    def __init__(self):
212        super(OutputQuery, self).__init__()
213
214
215    def validate(self, **kwargs):
216        """Validates the interactive input/output result.
217
218        This enforces the precondition and delegates to the base method.
219
220        @raise TestError: An error occurred during validation.
221        @raise TestFail: Query validation failed.
222        """
223        if not self._prepare_called:
224            raise error.TestError('Prepare was not called')
225        super(OutputQuery, self).validate(**kwargs)
226
227
228class InputQuery(_Query):
229    """Interface for an input interactive feedback query.
230
231    This class mandates that prepare() is called first, then emit(), and
232    finally validate(). Subclasses should override implementations of
233    _prepare_impl(), _emit_impl() and _validate_impl().
234    """
235
236    def __init__(self):
237        super(InputQuery, self).__init__()
238        self._emit_called = False
239
240
241    def validate(self, **kwargs):
242        """Validates the interactive input/output result.
243
244        This enforces the precondition and delegates to the base method.
245
246        @raise TestError: An error occurred during validation.
247        @raise TestFail: Query validation failed.
248        """
249        if not self._emit_called:
250            raise error.TestError('Emit was not called')
251        super(InputQuery, self).validate(**kwargs)
252
253
254    def emit(self):
255        """Instructs the tester to emit a feedback to be captured by the test.
256
257        This enforces the precondition and ensures the method is called at most
258        once, then delegates to an underlying implementation method.
259
260        @raise TestError: An error occurred during emission.
261        """
262        if not self._prepare_called:
263            raise error.TestError('Prepare was not called')
264        if self._emit_called:
265            raise error.TestError('Emit was already called')
266        self._emit_impl()
267        self._emit_called = True
268
269
270    def _emit_impl(self):
271        """Implementation of query emission logic.
272
273        This should be implemented in concrete subclasses.
274        """
275        raise NotImplementedError
276