3 # Copyright (c) 2016 Orange and others.
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
10 """Define the parent class of all Xtesting TestCases."""
13 from datetime import datetime
23 from xtesting.utils import decorators
24 from xtesting.utils import env
26 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
29 @six.add_metaclass(abc.ABCMeta)
30 class TestCase(object):
31 # pylint: disable=too-many-instance-attributes
32 """Base model for single test case."""
35 """everything is OK"""
37 EX_RUN_ERROR = os.EX_SOFTWARE
40 EX_PUSH_TO_DB_ERROR = os.EX_SOFTWARE - 1
41 """push_to_db() failed"""
43 EX_TESTCASE_FAILED = os.EX_SOFTWARE - 2
44 """results are false"""
46 EX_TESTCASE_SKIPPED = os.EX_SOFTWARE - 3
47 """requirements are unmet"""
49 _job_name_rule = "(dai|week)ly-(.+?)-[0-9]*"
50 _headers = {'Content-Type': 'application/json'}
51 __logger = logging.getLogger(__name__)
53 def __init__(self, **kwargs):
55 self.project_name = kwargs.get('project_name', 'xtesting')
56 self.case_name = kwargs.get('case_name', '')
57 self.criteria = kwargs.get('criteria', 100)
61 self.is_skipped = False
65 assert self.project_name
70 result = 'PASS' if(self.is_successful(
71 ) == TestCase.EX_OK) else 'FAIL'
72 msg = prettytable.PrettyTable(
73 header_style='upper', padding_width=5,
74 field_names=['test case', 'project', 'duration',
76 msg.add_row([self.case_name, self.project_name,
77 self.get_duration(), result])
78 return msg.get_string()
79 except AssertionError:
80 self.__logger.error("We cannot print invalid objects")
81 return super(TestCase, self).__str__()
83 def get_duration(self):
84 """Return the duration of the test case.
87 duration if start_time and stop_time are set
93 assert self.start_time
95 if self.stop_time < self.start_time:
97 return "{0[0]:02.0f}:{0[1]:02.0f}".format(divmod(
98 self.stop_time - self.start_time, 60))
99 except Exception: # pylint: disable=broad-except
100 self.__logger.error("Please run test before getting the duration")
103 def is_successful(self):
104 """Interpret the result of the test case.
106 It allows getting the result of TestCase. It completes run()
107 which only returns the execution status.
109 It can be overriden if checking result is not suitable.
112 TestCase.EX_OK if result is 'PASS'.
113 TestCase.EX_TESTCASE_SKIPPED if test case is skipped.
114 TestCase.EX_TESTCASE_FAILED otherwise.
118 return TestCase.EX_TESTCASE_SKIPPED
120 assert self.result is not None
121 if (not isinstance(self.result, str) and
122 not isinstance(self.criteria, str)):
123 if self.result >= self.criteria:
124 return TestCase.EX_OK
126 # Backward compatibility
127 # It must be removed as soon as TestCase subclasses
128 # stop setting result = 'PASS' or 'FAIL'.
129 # In this case criteria is unread.
130 self.__logger.warning(
131 "Please update result which must be an int!")
132 if self.result == 'PASS':
133 return TestCase.EX_OK
134 except AssertionError:
135 self.__logger.error("Please run test before checking the results")
136 return TestCase.EX_TESTCASE_FAILED
138 def check_requirements(self): # pylint: disable=no-self-use
139 """Check the requirements of the test case.
141 It can be overriden on purpose.
143 self.is_skipped = False
146 def run(self, **kwargs):
147 """Run the test case.
149 It allows running TestCase and getting its execution
152 The subclasses must override the default implementation which
155 The new implementation must set the following attributes to
156 push the results to DB:
163 kwargs: Arbitrary keyword arguments.
166 @decorators.can_dump_request_to_file
167 def push_to_db(self):
168 """Push the results of the test case to the DB.
170 It allows publishing the results and checking the status.
172 It could be overriden if the common implementation is not
175 The following attributes must be set before pushing the results to DB:
183 The next vars must be set in env:
192 TestCase.EX_OK if results were pushed to DB.
193 TestCase.EX_PUSH_TO_DB_ERROR otherwise.
197 return TestCase.EX_PUSH_TO_DB_ERROR
198 assert self.project_name
199 assert self.case_name
200 assert self.start_time
201 assert self.stop_time
202 url = env.get('TEST_DB_URL')
203 data = {"project_name": self.project_name,
204 "case_name": self.case_name,
205 "details": self.details}
206 data["installer"] = env.get('INSTALLER_TYPE')
207 data["scenario"] = env.get('DEPLOY_SCENARIO')
208 data["pod_name"] = env.get('NODE_NAME')
209 data["build_tag"] = env.get('BUILD_TAG')
210 data["criteria"] = 'PASS' if self.is_successful(
211 ) == TestCase.EX_OK else 'FAIL'
212 data["start_date"] = datetime.fromtimestamp(
213 self.start_time).strftime('%Y-%m-%d %H:%M:%S')
214 data["stop_date"] = datetime.fromtimestamp(
215 self.stop_time).strftime('%Y-%m-%d %H:%M:%S')
217 data["version"] = re.search(
218 TestCase._job_name_rule,
219 env.get('BUILD_TAG')).group(2)
220 except Exception: # pylint: disable=broad-except
221 data["version"] = "unknown"
223 url, data=json.dumps(data, sort_keys=True),
224 headers=self._headers)
225 req.raise_for_status()
227 "The results were successfully pushed to DB")
228 except AssertionError:
229 self.__logger.exception(
230 "Please run test before publishing the results")
231 return TestCase.EX_PUSH_TO_DB_ERROR
232 except requests.exceptions.HTTPError:
233 self.__logger.exception("The HTTP request raises issues")
234 return TestCase.EX_PUSH_TO_DB_ERROR
235 except Exception: # pylint: disable=broad-except
236 self.__logger.exception("The results cannot be pushed to DB")
237 return TestCase.EX_PUSH_TO_DB_ERROR
238 return TestCase.EX_OK
241 """Clean the resources.
243 It can be overriden if resources must be deleted after
244 running the test case.