Merge "Add Energy recording support"
[functest.git] / functest / core / testcase.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2016 Orange and others.
4 #
5 # All rights reserved. This program and the accompanying materials
6 # are made available under the terms of the Apache License, Version 2.0
7 # which accompanies this distribution, and is available at
8 # http://www.apache.org/licenses/LICENSE-2.0
9
10 """Define the parent class of all Functest TestCases."""
11
12 import logging
13 import os
14
15 import functest.utils.functest_utils as ft_utils
16
17 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
18
19
20 class TestCase(object):
21     """Base model for single test case."""
22
23     EX_OK = os.EX_OK
24     """everything is OK"""
25
26     EX_RUN_ERROR = os.EX_SOFTWARE
27     """run() failed"""
28
29     EX_PUSH_TO_DB_ERROR = os.EX_SOFTWARE - 1
30     """push_to_db() failed"""
31
32     EX_TESTCASE_FAILED = os.EX_SOFTWARE - 2
33     """results are false"""
34
35     __logger = logging.getLogger(__name__)
36
37     def __init__(self, **kwargs):
38         self.details = {}
39         self.project_name = kwargs.get('project_name', 'functest')
40         self.case_name = kwargs.get('case_name', '')
41         self.criteria = kwargs.get('criteria', 100)
42         self.result = 0
43         self.start_time = 0
44         self.stop_time = 0
45
46     def __str__(self):
47         try:
48             assert self.project_name
49             assert self.case_name
50             result = 'PASS' if(self.is_successful(
51                 ) == TestCase.EX_OK) else 'FAIL'
52             return ('| {0:<23} | {1:<13} | {2:<10} | {3:<13} |'
53                     '\n{4:-<26}{4:-<16}{4:-<13}{4:-<16}{4}'.format(
54                         self.case_name, self.project_name,
55                         self.get_duration(), result, '+'))
56         except AssertionError:
57             self.__logger.error("We cannot print invalid objects")
58             return '| {0:^68} |\n{1:-<26}{1:-<16}{1:-<13}{1:-<16}{1}'.format(
59                 'INVALID OBJECT', '+')
60
61     def get_duration(self):
62         """Return the duration of the test case.
63
64         Returns:
65             duration if start_time and stop_time are set
66             "XX:XX" otherwise.
67         """
68         try:
69             assert self.start_time
70             assert self.stop_time
71             if self.stop_time < self.start_time:
72                 return "XX:XX"
73             return "{0[0]:02.0f}:{0[1]:02.0f}".format(divmod(
74                 self.stop_time - self.start_time, 60))
75         except Exception:  # pylint: disable=broad-except
76             self.__logger.error("Please run test before getting the duration")
77             return "XX:XX"
78
79     def is_successful(self):
80         """Interpret the result of the test case.
81
82         It allows getting the result of TestCase. It completes run()
83         which only returns the execution status.
84
85         It can be overriden if checking result is not suitable.
86
87         Returns:
88             TestCase.EX_OK if result is 'PASS'.
89             TestCase.EX_TESTCASE_FAILED otherwise.
90         """
91         try:
92             assert self.criteria
93             if (not isinstance(self.result, str) and
94                     not isinstance(self.criteria, str)):
95                 if self.result >= self.criteria:
96                     return TestCase.EX_OK
97             else:
98                 # Backward compatibility
99                 # It must be removed as soon as TestCase subclasses
100                 # stop setting result = 'PASS' or 'FAIL'.
101                 # In this case criteria is unread.
102                 self.__logger.warning(
103                     "Please update result which must be an int!")
104                 if self.result == 'PASS':
105                     return TestCase.EX_OK
106         except AssertionError:
107             self.__logger.error("Please run test before checking the results")
108         return TestCase.EX_TESTCASE_FAILED
109
110     def run(self, **kwargs):
111         """Run the test case.
112
113         It allows running TestCase and getting its execution
114         status.
115
116         The subclasses must override the default implementation which
117         is false on purpose.
118
119         The new implementation must set the following attributes to
120         push the results to DB:
121
122             * result,
123             * start_time,
124             * stop_time.
125
126         Args:
127             kwargs: Arbitrary keyword arguments.
128
129         Returns:
130             TestCase.EX_RUN_ERROR.
131         """
132         # pylint: disable=unused-argument
133         self.__logger.error("Run must be implemented")
134         return TestCase.EX_RUN_ERROR
135
136     def push_to_db(self):
137         """Push the results of the test case to the DB.
138
139         It allows publishing the results and to check the status.
140
141         It could be overriden if the common implementation is not
142         suitable. The following attributes must be set before pushing
143         the results to DB:
144
145             * project_name,
146             * case_name,
147             * result,
148             * start_time,
149             * stop_time.
150
151         Returns:
152             TestCase.EX_OK if results were pushed to DB.
153             TestCase.EX_PUSH_TO_DB_ERROR otherwise.
154         """
155         try:
156             assert self.project_name
157             assert self.case_name
158             assert self.start_time
159             assert self.stop_time
160             pub_result = 'PASS' if self.is_successful(
161                 ) == TestCase.EX_OK else 'FAIL'
162             if ft_utils.push_results_to_db(
163                     self.project_name, self.case_name, self.start_time,
164                     self.stop_time, pub_result, self.details):
165                 self.__logger.info(
166                     "The results were successfully pushed to DB")
167                 return TestCase.EX_OK
168             else:
169                 self.__logger.error("The results cannot be pushed to DB")
170                 return TestCase.EX_PUSH_TO_DB_ERROR
171         except Exception:  # pylint: disable=broad-except
172             self.__logger.exception("The results cannot be pushed to DB")
173             return TestCase.EX_PUSH_TO_DB_ERROR