blob: 8ca0fec01039f573729b11d9d2b4a496181e8fd8 [file] [log] [blame]
Ang Li93420002016-05-10 19:11:44 -07001#!/usr/bin/env python3.4
2#
3# Copyright 2016 - The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
Ang Li64920272016-05-26 18:37:43 -070017import logging
Ang Li93420002016-05-10 19:11:44 -070018import os
19
20from vts.runners.host import asserts
21from vts.runners.host import errors
22from vts.runners.host import keys
23from vts.runners.host import logger
24from vts.runners.host import records
25from vts.runners.host import signals
Ang Li93420002016-05-10 19:11:44 -070026from vts.runners.host import utils
27
Ang Li93420002016-05-10 19:11:44 -070028# Macro strings for test result reporting
29TEST_CASE_TOKEN = "[Test Case]"
30RESULT_LINE_TEMPLATE = TEST_CASE_TOKEN + " %s %s"
31
32
33class BaseTestClass(object):
34 """Base class for all test classes to inherit from.
35
36 This class gets all the controller objects from test_runner and executes
37 the test cases requested within itself.
38
39 Most attributes of this class are set at runtime based on the configuration
40 provided.
41
42 Attributes:
43 tests: A list of strings, each representing a test case name.
44 TAG: A string used to refer to a test class. Default is the test class
45 name.
Ang Li93420002016-05-10 19:11:44 -070046 results: A records.TestResult object for aggregating test results from
47 the execution of test cases.
48 currentTestName: A string that's the name of the test case currently
49 being executed. If no test is executing, this should
50 be None.
51 """
52
53 TAG = None
54
55 def __init__(self, configs):
56 self.tests = []
57 if not self.TAG:
58 self.TAG = self.__class__.__name__
59 # Set all the controller objects and params.
60 for name, value in configs.items():
61 setattr(self, name, value)
62 self.results = records.TestResult()
63 self.currentTestName = None
64
65 def __enter__(self):
66 return self
67
68 def __exit__(self, *args):
69 self._exec_func(self.cleanUp)
70
Ang Lie2139f12016-05-12 17:39:06 -070071 def getUserParams(self, req_param_names=[], opt_param_names=[], **kwargs):
Ang Li93420002016-05-10 19:11:44 -070072 """Unpacks user defined parameters in test config into individual
73 variables.
74
75 Instead of accessing the user param with self.user_params["xxx"], the
76 variable can be directly accessed with self.xxx.
77
78 A missing required param will raise an exception. If an optional param
79 is missing, an INFO line will be logged.
80
81 Args:
82 req_param_names: A list of names of the required user params.
83 opt_param_names: A list of names of the optional user params.
84 **kwargs: Arguments that provide default values.
85 e.g. getUserParams(required_list, opt_list, arg_a="hello")
86 self.arg_a will be "hello" unless it is specified again in
87 required_list or opt_list.
88
89 Raises:
90 BaseTestError is raised if a required user params is missing from
91 test config.
92 """
93 for k, v in kwargs.items():
94 setattr(self, k, v)
95 for name in req_param_names:
96 if name not in self.user_params:
97 raise BaseTestError(("Missing required user param '%s' in test"
Ang Lie2139f12016-05-12 17:39:06 -070098 " configuration.") % name)
Ang Li93420002016-05-10 19:11:44 -070099 setattr(self, name, self.user_params[name])
100 for name in opt_param_names:
101 if name not in self.user_params:
Ang Li64920272016-05-26 18:37:43 -0700102 logging.info(
Ang Lie2139f12016-05-12 17:39:06 -0700103 ("Missing optional user param '%s' in "
104 "configuration, continue."), name)
Ang Li93420002016-05-10 19:11:44 -0700105 else:
106 setattr(self, name, self.user_params[name])
107
108 def _setUpClass(self):
109 """Proxy function to guarantee the base implementation of setUpClass
110 is called.
111 """
112 return self.setUpClass()
113
114 def setUpClass(self):
115 """Setup function that will be called before executing any test case in
116 the test class.
117
118 To signal setup failure, return False or raise an exception. If
119 exceptions were raised, the stack trace would appear in log, but the
120 exceptions would not propagate to upper levels.
121
122 Implementation is optional.
123 """
Keun Soo Yimbae361d2016-07-23 10:39:54 -0700124 pass
125
126 def _tearDownClass(self):
127 """Proxy function to guarantee the base implementation of tearDownClass
128 is called.
129 """
130 return self.tearDownClass()
Ang Li93420002016-05-10 19:11:44 -0700131
132 def tearDownClass(self):
133 """Teardown function that will be called after all the selected test
134 cases in the test class have been executed.
135
136 Implementation is optional.
137 """
Keun Soo Yimbae361d2016-07-23 10:39:54 -0700138 pass
Ang Li93420002016-05-10 19:11:44 -0700139
140 def _setUpTest(self, test_name):
141 """Proxy function to guarantee the base implementation of setUpTest is
142 called.
143 """
144 self.currentTestName = test_name
145 return self.setUpTest()
146
147 def setUpTest(self):
148 """Setup function that will be called every time before executing each
149 test case in the test class.
150
151 To signal setup failure, return False or raise an exception. If
152 exceptions were raised, the stack trace would appear in log, but the
153 exceptions would not propagate to upper levels.
154
155 Implementation is optional.
156 """
157
158 def _tearDownTest(self, test_name):
159 """Proxy function to guarantee the base implementation of tearDownTest
160 is called.
161 """
162 try:
163 self.tearDownTest()
164 finally:
165 self.currentTestName = None
166
167 def tearDownTest(self):
168 """Teardown function that will be called every time a test case has
169 been executed.
170
171 Implementation is optional.
172 """
173
174 def _onFail(self, record):
175 """Proxy function to guarantee the base implementation of onFail is
176 called.
177
178 Args:
179 record: The records.TestResultRecord object for the failed test
180 case.
181 """
182 test_name = record.test_name
Ang Li64920272016-05-26 18:37:43 -0700183 logging.error(record.details)
Ang Li93420002016-05-10 19:11:44 -0700184 begin_time = logger.epochToLogLineTimestamp(record.begin_time)
Ang Li64920272016-05-26 18:37:43 -0700185 logging.info(RESULT_LINE_TEMPLATE, test_name, record.result)
Ang Li93420002016-05-10 19:11:44 -0700186 self.onFail(test_name, begin_time)
187
188 def onFail(self, test_name, begin_time):
189 """A function that is executed upon a test case failure.
190
191 User implementation is optional.
192
193 Args:
194 test_name: Name of the test that triggered this function.
195 begin_time: Logline format timestamp taken when the test started.
196 """
197
198 def _onPass(self, record):
199 """Proxy function to guarantee the base implementation of onPass is
200 called.
201
202 Args:
203 record: The records.TestResultRecord object for the passed test
204 case.
205 """
206 test_name = record.test_name
207 begin_time = logger.epochToLogLineTimestamp(record.begin_time)
208 msg = record.details
209 if msg:
Ang Li64920272016-05-26 18:37:43 -0700210 logging.info(msg)
211 logging.info(RESULT_LINE_TEMPLATE, test_name, record.result)
Ang Li93420002016-05-10 19:11:44 -0700212 self.onPass(test_name, begin_time)
213
214 def onPass(self, test_name, begin_time):
215 """A function that is executed upon a test case passing.
216
217 Implementation is optional.
218
219 Args:
220 test_name: Name of the test that triggered this function.
221 begin_time: Logline format timestamp taken when the test started.
222 """
223
224 def _onSkip(self, record):
225 """Proxy function to guarantee the base implementation of onSkip is
226 called.
227
228 Args:
229 record: The records.TestResultRecord object for the skipped test
230 case.
231 """
232 test_name = record.test_name
233 begin_time = logger.epochToLogLineTimestamp(record.begin_time)
Ang Li64920272016-05-26 18:37:43 -0700234 logging.info(RESULT_LINE_TEMPLATE, test_name, record.result)
235 logging.info("Reason to skip: %s", record.details)
Ang Li93420002016-05-10 19:11:44 -0700236 self.onSkip(test_name, begin_time)
237
238 def onSkip(self, test_name, begin_time):
239 """A function that is executed upon a test case being skipped.
240
241 Implementation is optional.
242
243 Args:
244 test_name: Name of the test that triggered this function.
245 begin_time: Logline format timestamp taken when the test started.
246 """
247
248 def _onException(self, record):
249 """Proxy function to guarantee the base implementation of onException
250 is called.
251
252 Args:
253 record: The records.TestResultRecord object for the failed test
254 case.
255 """
256 test_name = record.test_name
Ang Li64920272016-05-26 18:37:43 -0700257 logging.exception(record.details)
Ang Li93420002016-05-10 19:11:44 -0700258 begin_time = logger.epochToLogLineTimestamp(record.begin_time)
Ang Li93420002016-05-10 19:11:44 -0700259 self.onException(test_name, begin_time)
260
261 def onException(self, test_name, begin_time):
262 """A function that is executed upon an unhandled exception from a test
263 case.
264
265 Implementation is optional.
266
267 Args:
268 test_name: Name of the test that triggered this function.
269 begin_time: Logline format timestamp taken when the test started.
270 """
271
272 def _exec_procedure_func(self, func, tr_record):
273 """Executes a procedure function like onPass, onFail etc.
274
275 This function will alternate the 'Result' of the test's record if
276 exceptions happened when executing the procedure function.
277
278 This will let signals.TestAbortAll through so abortAll works in all
279 procedure functions.
280
281 Args:
282 func: The procedure function to be executed.
283 tr_record: The TestResultRecord object associated with the test
284 case executed.
285 """
286 try:
287 func(tr_record)
288 except signals.TestAbortAll:
289 raise
290 except Exception as e:
Ang Li64920272016-05-26 18:37:43 -0700291 logging.exception("Exception happened when executing %s for %s.",
292 func.__name__, self.currentTestName)
Ang Li93420002016-05-10 19:11:44 -0700293 tr_record.addError(func.__name__, e)
294
295 def execOneTest(self, test_name, test_func, args, **kwargs):
296 """Executes one test case and update test results.
297
298 Executes one test case, create a records.TestResultRecord object with
299 the execution information, and add the record to the test class's test
300 results.
301
302 Args:
303 test_name: Name of the test.
304 test_func: The test function.
305 args: A tuple of params.
306 kwargs: Extra kwargs.
307 """
308 is_generate_trigger = False
309 tr_record = records.TestResultRecord(test_name, self.TAG)
310 tr_record.testBegin()
Ang Li64920272016-05-26 18:37:43 -0700311 logging.info("%s %s", TEST_CASE_TOKEN, test_name)
Ang Li93420002016-05-10 19:11:44 -0700312 verdict = None
313 try:
314 ret = self._setUpTest(test_name)
315 asserts.assertTrue(ret is not False,
Ang Lie2139f12016-05-12 17:39:06 -0700316 "Setup for %s failed." % test_name)
Ang Li93420002016-05-10 19:11:44 -0700317 try:
318 if args or kwargs:
319 verdict = test_func(*args, **kwargs)
320 else:
321 verdict = test_func()
322 finally:
323 self._tearDownTest(test_name)
324 except (signals.TestFailure, AssertionError) as e:
325 tr_record.testFail(e)
326 self._exec_procedure_func(self._onFail, tr_record)
327 except signals.TestSkip as e:
328 # Test skipped.
329 tr_record.testSkip(e)
330 self._exec_procedure_func(self._onSkip, tr_record)
331 except (signals.TestAbortClass, signals.TestAbortAll) as e:
332 # Abort signals, pass along.
333 tr_record.testFail(e)
334 raise e
335 except signals.TestPass as e:
336 # Explicit test pass.
337 tr_record.testPass(e)
338 self._exec_procedure_func(self._onPass, tr_record)
339 except signals.TestSilent as e:
340 # This is a trigger test for generated tests, suppress reporting.
341 is_generate_trigger = True
342 self.results.requested.remove(test_name)
343 except Exception as e:
344 # Exception happened during test.
345 tr_record.testError(e)
346 self._exec_procedure_func(self._onException, tr_record)
347 self._exec_procedure_func(self._onFail, tr_record)
348 else:
349 # Keep supporting return False for now.
350 # TODO(angli): Deprecate return False support.
351 if verdict or (verdict is None):
352 # Test passed.
353 tr_record.testPass()
354 self._exec_procedure_func(self._onPass, tr_record)
355 return
356 # Test failed because it didn't return True.
357 # This should be removed eventually.
358 tr_record.testFail()
359 self._exec_procedure_func(self._onFail, tr_record)
360 finally:
361 if not is_generate_trigger:
362 self.results.addRecord(tr_record)
363
Ang Lie2139f12016-05-12 17:39:06 -0700364 def runGeneratedTests(self,
365 test_func,
366 settings,
367 args=None,
368 kwargs=None,
369 tag="",
370 name_func=None):
Ang Li93420002016-05-10 19:11:44 -0700371 """Runs generated test cases.
372
373 Generated test cases are not written down as functions, but as a list
374 of parameter sets. This way we reduce code repetition and improve
375 test case scalability.
376
377 Args:
378 test_func: The common logic shared by all these generated test
379 cases. This function should take at least one argument,
380 which is a parameter set.
381 settings: A list of strings representing parameter sets. These are
382 usually json strings that get loaded in the test_func.
383 args: Iterable of additional position args to be passed to
384 test_func.
385 kwargs: Dict of additional keyword args to be passed to test_func
386 tag: Name of this group of generated test cases. Ignored if
387 name_func is provided and operates properly.
388 name_func: A function that takes a test setting and generates a
389 proper test name. The test name should be shorter than
390 utils.MAX_FILENAME_LEN. Names over the limit will be
391 truncated.
392
393 Returns:
394 A list of settings that did not pass.
395 """
396 args = args or ()
397 kwargs = kwargs or {}
398 failed_settings = []
399 for s in settings:
400 test_name = "{} {}".format(tag, s)
401 if name_func:
402 try:
403 test_name = name_func(s, *args, **kwargs)
404 except:
Ang Li64920272016-05-26 18:37:43 -0700405 logging.exception(
Ang Lie2139f12016-05-12 17:39:06 -0700406 ("Failed to get test name from "
407 "test_func. Fall back to default %s"), test_name)
Ang Li93420002016-05-10 19:11:44 -0700408 self.results.requested.append(test_name)
409 if len(test_name) > utils.MAX_FILENAME_LEN:
410 test_name = test_name[:utils.MAX_FILENAME_LEN]
411 previous_success_cnt = len(self.results.passed)
Ang Lie2139f12016-05-12 17:39:06 -0700412 self.execOneTest(test_name, test_func, (s, ) + args, **kwargs)
Ang Li93420002016-05-10 19:11:44 -0700413 if len(self.results.passed) - previous_success_cnt != 1:
414 failed_settings.append(s)
415 return failed_settings
416
417 def _exec_func(self, func, *args):
418 """Executes a function with exception safeguard.
419
420 This will let signals.TestAbortAll through so abortAll works in all
421 procedure functions.
422
423 Args:
424 func: Function to be executed.
425 args: Arguments to be passed to the function.
426
427 Returns:
428 Whatever the function returns, or False if unhandled exception
429 occured.
430 """
431 try:
432 return func(*args)
433 except signals.TestAbortAll:
434 raise
435 except:
Ang Li64920272016-05-26 18:37:43 -0700436 logging.exception("Exception happened when executing %s in %s.",
437 func.__name__, self.TAG)
Ang Li93420002016-05-10 19:11:44 -0700438 return False
439
440 def _get_all_test_names(self):
441 """Finds all the function names that match the test case naming
442 convention in this class.
443
444 Returns:
445 A list of strings, each is a test case name.
446 """
447 test_names = []
448 for name in dir(self):
Ang Li64920272016-05-26 18:37:43 -0700449 if name.startswith("test"):
450 attr_func = getattr(self, name)
451 if hasattr(attr_func, "__call__"):
452 test_names.append(name)
Ang Li93420002016-05-10 19:11:44 -0700453 return test_names
454
455 def _get_test_funcs(self, test_names):
456 """Obtain the actual functions of test cases based on test names.
457
458 Args:
459 test_names: A list of strings, each string is a test case name.
460
461 Returns:
462 A list of tuples of (string, function). String is the test case
463 name, function is the actual test case function.
464
465 Raises:
466 errors.USERError is raised if the test name does not follow
467 naming convention "test_*". This can only be caused by user input
468 here.
469 """
470 test_funcs = []
471 for test_name in test_names:
Ang Li64920272016-05-26 18:37:43 -0700472 if not test_name.startswith("test"):
Ang Li93420002016-05-10 19:11:44 -0700473 msg = ("Test case name %s does not follow naming convention "
Ang Li64920272016-05-26 18:37:43 -0700474 "test*, abort.") % test_name
Ang Li93420002016-05-10 19:11:44 -0700475 raise errors.USERError(msg)
476 try:
477 test_funcs.append((test_name, getattr(self, test_name)))
478 except AttributeError:
Ang Li64920272016-05-26 18:37:43 -0700479 logging.warning("%s does not have test case %s.", self.TAG,
480 test_name)
Ang Li93420002016-05-10 19:11:44 -0700481 except BaseTestError as e:
Ang Li64920272016-05-26 18:37:43 -0700482 logging.warning(str(e))
Ang Li93420002016-05-10 19:11:44 -0700483 return test_funcs
484
485 def run(self, test_names=None):
486 """Runs test cases within a test class by the order they appear in the
487 execution list.
488
489 One of these test cases lists will be executed, shown here in priority
490 order:
491 1. The test_names list, which is passed from cmd line. Invalid names
492 are guarded by cmd line arg parsing.
493 2. The self.tests list defined in test class. Invalid names are
494 ignored.
495 3. All function that matches test case naming convention in the test
496 class.
497
498 Args:
499 test_names: A list of string that are test case names requested in
500 cmd line.
501
502 Returns:
503 The test results object of this class.
504 """
Ang Li64920272016-05-26 18:37:43 -0700505 logging.info("==========> %s <==========", self.TAG)
Ang Li93420002016-05-10 19:11:44 -0700506 # Devise the actual test cases to run in the test class.
507 if not test_names:
508 if self.tests:
509 # Specified by run list in class.
510 test_names = list(self.tests)
511 else:
512 # No test case specified by user, execute all in the test class
513 test_names = self._get_all_test_names()
514 self.results.requested = test_names
515 tests = self._get_test_funcs(test_names)
516 # Setup for the class.
517 try:
518 if self._setUpClass() is False:
519 raise signals.TestFailure("Failed to setup %s." % self.TAG)
520 except Exception as e:
Ang Li64920272016-05-26 18:37:43 -0700521 logging.exception("Failed to setup %s.", self.TAG)
Ang Li93420002016-05-10 19:11:44 -0700522 self.results.failClass(self.TAG, e)
Keun Soo Yimbae361d2016-07-23 10:39:54 -0700523 self._exec_func(self._tearDownClass)
Ang Li93420002016-05-10 19:11:44 -0700524 return self.results
525 # Run tests in order.
526 try:
527 for test_name, test_func in tests:
Ang Li64920272016-05-26 18:37:43 -0700528 self.execOneTest(test_name, test_func, None)
Ang Li93420002016-05-10 19:11:44 -0700529 return self.results
530 except signals.TestAbortClass:
531 return self.results
532 except signals.TestAbortAll as e:
533 # Piggy-back test results on this exception object so we don't lose
534 # results from this test class.
535 setattr(e, "results", self.results)
536 raise e
537 finally:
Keun Soo Yimbae361d2016-07-23 10:39:54 -0700538 self._exec_func(self._tearDownClass)
Ang Li64920272016-05-26 18:37:43 -0700539 logging.info("Summary for test class %s: %s", self.TAG,
540 self.results.summary())
Ang Li93420002016-05-10 19:11:44 -0700541
542 def cleanUp(self):
543 """A function that is executed upon completion of all tests cases
544 selected in the test class.
545
546 This function should clean up objects initialized in the constructor by
547 user.
548 """