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."""
12 from datetime import datetime
21 from xtesting.utils import decorators
22 from xtesting.utils import env
24 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
27 class TestCase(object): # pylint: disable=too-many-instance-attributes
28 """Base model for single test case."""
31 """everything is OK"""
33 EX_RUN_ERROR = os.EX_SOFTWARE
36 EX_PUSH_TO_DB_ERROR = os.EX_SOFTWARE - 1
37 """push_to_db() failed"""
39 EX_TESTCASE_FAILED = os.EX_SOFTWARE - 2
40 """results are false"""
42 EX_TESTCASE_SKIPPED = os.EX_SOFTWARE - 3
43 """requirements are unmet"""
45 _job_name_rule = "(dai|week)ly-(.+?)-[0-9]*"
46 _headers = {'Content-Type': 'application/json'}
47 __logger = logging.getLogger(__name__)
49 def __init__(self, **kwargs):
51 self.project_name = kwargs.get('project_name', 'xtesting')
52 self.case_name = kwargs.get('case_name', '')
53 self.criteria = kwargs.get('criteria', 100)
57 self.is_skipped = False
61 assert self.project_name
66 result = 'PASS' if(self.is_successful(
67 ) == TestCase.EX_OK) else 'FAIL'
68 msg = prettytable.PrettyTable(
69 header_style='upper', padding_width=5,
70 field_names=['test case', 'project', 'duration',
72 msg.add_row([self.case_name, self.project_name,
73 self.get_duration(), result])
74 return msg.get_string()
75 except AssertionError:
76 self.__logger.error("We cannot print invalid objects")
77 return super(TestCase, self).__str__()
79 def get_duration(self):
80 """Return the duration of the test case.
83 duration if start_time and stop_time are set
89 assert self.start_time
91 if self.stop_time < self.start_time:
93 return "{0[0]:02.0f}:{0[1]:02.0f}".format(divmod(
94 self.stop_time - self.start_time, 60))
95 except Exception: # pylint: disable=broad-except
96 self.__logger.error("Please run test before getting the duration")
99 def is_successful(self):
100 """Interpret the result of the test case.
102 It allows getting the result of TestCase. It completes run()
103 which only returns the execution status.
105 It can be overriden if checking result is not suitable.
108 TestCase.EX_OK if result is 'PASS'.
109 TestCase.EX_TESTCASE_SKIPPED if test case is skipped.
110 TestCase.EX_TESTCASE_FAILED otherwise.
114 return TestCase.EX_TESTCASE_SKIPPED
116 assert self.result is not None
117 if (not isinstance(self.result, str) and
118 not isinstance(self.criteria, str)):
119 if self.result >= self.criteria:
120 return TestCase.EX_OK
122 # Backward compatibility
123 # It must be removed as soon as TestCase subclasses
124 # stop setting result = 'PASS' or 'FAIL'.
125 # In this case criteria is unread.
126 self.__logger.warning(
127 "Please update result which must be an int!")
128 if self.result == 'PASS':
129 return TestCase.EX_OK
130 except AssertionError:
131 self.__logger.error("Please run test before checking the results")
132 return TestCase.EX_TESTCASE_FAILED
134 def check_requirements(self): # pylint: disable=no-self-use
135 """Check the requirements of the test case.
137 It can be overriden on purpose.
139 self.is_skipped = False
141 def run(self, **kwargs):
142 """Run the test case.
144 It allows running TestCase and getting its execution
147 The subclasses must override the default implementation which
150 The new implementation must set the following attributes to
151 push the results to DB:
158 kwargs: Arbitrary keyword arguments.
161 TestCase.EX_RUN_ERROR.
163 # pylint: disable=unused-argument
164 self.__logger.error("Run must be implemented")
165 return TestCase.EX_RUN_ERROR
167 @decorators.can_dump_request_to_file
168 def push_to_db(self):
169 """Push the results of the test case to the DB.
171 It allows publishing the results and checking the status.
173 It could be overriden if the common implementation is not
176 The following attributes must be set before pushing the results to DB:
184 The next vars must be set in env:
193 TestCase.EX_OK if results were pushed to DB.
194 TestCase.EX_PUSH_TO_DB_ERROR otherwise.
198 return TestCase.EX_PUSH_TO_DB_ERROR
199 assert self.project_name
200 assert self.case_name
201 assert self.start_time
202 assert self.stop_time
203 url = env.get('TEST_DB_URL')
204 data = {"project_name": self.project_name,
205 "case_name": self.case_name,
206 "details": self.details}
207 data["installer"] = env.get('INSTALLER_TYPE')
208 data["scenario"] = env.get('DEPLOY_SCENARIO')
209 data["pod_name"] = env.get('NODE_NAME')
210 data["build_tag"] = env.get('BUILD_TAG')
211 data["criteria"] = 'PASS' if self.is_successful(
212 ) == TestCase.EX_OK else 'FAIL'
213 data["start_date"] = datetime.fromtimestamp(
214 self.start_time).strftime('%Y-%m-%d %H:%M:%S')
215 data["stop_date"] = datetime.fromtimestamp(
216 self.stop_time).strftime('%Y-%m-%d %H:%M:%S')
218 data["version"] = re.search(
219 TestCase._job_name_rule,
220 env.get('BUILD_TAG')).group(2)
221 except Exception: # pylint: disable=broad-except
222 data["version"] = "unknown"
224 url, data=json.dumps(data, sort_keys=True),
225 headers=self._headers)
226 req.raise_for_status()
228 "The results were successfully pushed to DB %s", url)
229 except AssertionError:
230 self.__logger.exception(
231 "Please run test before publishing the results")
232 return TestCase.EX_PUSH_TO_DB_ERROR
233 except requests.exceptions.HTTPError:
234 self.__logger.exception("The HTTP request raises issues")
235 return TestCase.EX_PUSH_TO_DB_ERROR
236 except Exception: # pylint: disable=broad-except
237 self.__logger.exception("The results cannot be pushed to DB")
238 return TestCase.EX_PUSH_TO_DB_ERROR
239 return TestCase.EX_OK
242 """Clean the resources.
244 It can be overriden if resources must be deleted after
245 running the test case.