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
24 from six.moves import urllib
26 from xtesting.utils import decorators
27 from xtesting.utils import env
29 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
32 @six.add_metaclass(abc.ABCMeta)
34 # pylint: disable=too-many-instance-attributes
35 """Base model for single test case."""
38 """everything is OK"""
40 EX_RUN_ERROR = os.EX_SOFTWARE
43 EX_PUSH_TO_DB_ERROR = os.EX_SOFTWARE - 1
44 """push_to_db() failed"""
46 EX_TESTCASE_FAILED = os.EX_SOFTWARE - 2
47 """results are false"""
49 EX_TESTCASE_SKIPPED = os.EX_SOFTWARE - 3
50 """requirements are unmet"""
52 EX_PUBLISH_ARTIFACTS_ERROR = os.EX_SOFTWARE - 4
53 """publish_artifacts() failed"""
55 dir_results = "/var/lib/xtesting/results"
56 _job_name_rule = "(dai|week)ly-(.+?)-[0-9]*"
57 _headers = {'Content-Type': 'application/json'}
58 __logger = logging.getLogger(__name__)
60 def __init__(self, **kwargs):
62 self.project_name = kwargs.get('project_name', 'xtesting')
63 self.case_name = kwargs.get('case_name', '')
64 self.criteria = kwargs.get('criteria', 100)
68 self.is_skipped = False
69 self.res_dir = "{}/{}".format(self.dir_results, self.case_name)
73 assert self.project_name
78 result = 'PASS' if(self.is_successful(
79 ) == TestCase.EX_OK) else 'FAIL'
80 msg = prettytable.PrettyTable(
81 header_style='upper', padding_width=5,
82 field_names=['test case', 'project', 'duration',
84 msg.add_row([self.case_name, self.project_name,
85 self.get_duration(), result])
86 return msg.get_string()
87 except AssertionError:
88 self.__logger.error("We cannot print invalid objects")
89 return super(TestCase, self).__str__()
91 def get_duration(self):
92 """Return the duration of the test case.
95 duration if start_time and stop_time are set
101 assert self.start_time
102 assert self.stop_time
103 if self.stop_time < self.start_time:
105 return "{0[0]:02.0f}:{0[1]:02.0f}".format(divmod(
106 self.stop_time - self.start_time, 60))
107 except Exception: # pylint: disable=broad-except
108 self.__logger.error("Please run test before getting the duration")
111 def is_successful(self):
112 """Interpret the result of the test case.
114 It allows getting the result of TestCase. It completes run()
115 which only returns the execution status.
117 It can be overriden if checking result is not suitable.
120 TestCase.EX_OK if result is 'PASS'.
121 TestCase.EX_TESTCASE_SKIPPED if test case is skipped.
122 TestCase.EX_TESTCASE_FAILED otherwise.
126 return TestCase.EX_TESTCASE_SKIPPED
128 assert self.result is not None
129 if (not isinstance(self.result, str) and
130 not isinstance(self.criteria, str)):
131 if self.result >= self.criteria:
132 return TestCase.EX_OK
134 # Backward compatibility
135 # It must be removed as soon as TestCase subclasses
136 # stop setting result = 'PASS' or 'FAIL'.
137 # In this case criteria is unread.
138 self.__logger.warning(
139 "Please update result which must be an int!")
140 if self.result == 'PASS':
141 return TestCase.EX_OK
142 except AssertionError:
143 self.__logger.error("Please run test before checking the results")
144 return TestCase.EX_TESTCASE_FAILED
146 def check_requirements(self): # pylint: disable=no-self-use
147 """Check the requirements of the test case.
149 It can be overriden on purpose.
151 self.is_skipped = False
154 def run(self, **kwargs):
155 """Run the test case.
157 It allows running TestCase and getting its execution
160 The subclasses must override the default implementation which
163 The new implementation must set the following attributes to
164 push the results to DB:
171 kwargs: Arbitrary keyword arguments.
174 @decorators.can_dump_request_to_file
175 def push_to_db(self):
176 """Push the results of the test case to the DB.
178 It allows publishing the results and checking the status.
180 It could be overriden if the common implementation is not
183 The following attributes must be set before pushing the results to DB:
191 The next vars must be set in env:
200 TestCase.EX_OK if results were pushed to DB.
201 TestCase.EX_PUSH_TO_DB_ERROR otherwise.
205 return TestCase.EX_PUSH_TO_DB_ERROR
206 assert self.project_name
207 assert self.case_name
208 assert self.start_time
209 assert self.stop_time
210 url = env.get('TEST_DB_URL')
211 data = {"project_name": self.project_name,
212 "case_name": self.case_name,
213 "details": self.details}
214 data["installer"] = env.get('INSTALLER_TYPE')
215 data["scenario"] = env.get('DEPLOY_SCENARIO')
216 data["pod_name"] = env.get('NODE_NAME')
217 data["build_tag"] = env.get('BUILD_TAG')
218 data["criteria"] = 'PASS' if self.is_successful(
219 ) == TestCase.EX_OK else 'FAIL'
220 data["start_date"] = datetime.fromtimestamp(
221 self.start_time).strftime('%Y-%m-%d %H:%M:%S')
222 data["stop_date"] = datetime.fromtimestamp(
223 self.stop_time).strftime('%Y-%m-%d %H:%M:%S')
225 data["version"] = re.search(
226 TestCase._job_name_rule,
227 env.get('BUILD_TAG')).group(2)
228 except Exception: # pylint: disable=broad-except
229 data["version"] = "unknown"
231 url, data=json.dumps(data, sort_keys=True),
232 headers=self._headers)
233 req.raise_for_status()
235 "The results were successfully pushed to DB")
236 except AssertionError:
237 self.__logger.exception(
238 "Please run test before publishing the results")
239 return TestCase.EX_PUSH_TO_DB_ERROR
240 except requests.exceptions.HTTPError:
241 self.__logger.exception("The HTTP request raises issues")
242 return TestCase.EX_PUSH_TO_DB_ERROR
243 except Exception: # pylint: disable=broad-except
244 self.__logger.exception("The results cannot be pushed to DB")
245 return TestCase.EX_PUSH_TO_DB_ERROR
246 return TestCase.EX_OK
248 def publish_artifacts(self):
249 """Push the artifacts to the S3 repository.
251 It allows publishing the artifacts.
253 It could be overriden if the common implementation is not
256 The credentials must be configured before publishing the artifacts:
258 * fill ~/.aws/credentials or ~/.boto,
259 * set AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in env.
261 The next vars must be set in env:
263 * S3_ENDPOINT_URL (http://127.0.0.1:9000),
264 * S3_DST_URL (s3://xtesting/prefix),
265 * HTTP_DST_URL (http://127.0.0.1/prefix).
268 TestCase.EX_OK if artifacts were published to repository.
269 TestCase.EX_PUBLISH_ARTIFACTS_ERROR otherwise.
272 b3resource = boto3.resource(
273 's3', endpoint_url=os.environ["S3_ENDPOINT_URL"])
274 dst_s3_url = os.environ["S3_DST_URL"]
275 bucket = urllib.parse.urlparse(dst_s3_url).netloc
276 path = urllib.parse.urlparse(dst_s3_url).path.strip("/")
278 for root, _, files in os.walk(self.dir_results):
279 for pub_file in files:
280 # pylint: disable=no-member
281 b3resource.Bucket(bucket).upload_file(
282 os.path.join(root, pub_file),
283 os.path.join(path, os.path.relpath(
284 os.path.join(root, pub_file),
285 start=self.dir_results)))
286 dst_http_url = os.environ["HTTP_DST_URL"]
287 output_str += "\n{}".format(
288 os.path.join(dst_http_url, os.path.relpath(
289 os.path.join(root, pub_file),
290 start=self.dir_results)))
292 "All artifacts were successfully published: %s\n", output_str)
293 return TestCase.EX_OK
294 except KeyError as ex:
295 self.__logger.error("Please check env var: %s", str(ex))
296 return TestCase.EX_PUBLISH_ARTIFACTS_ERROR
297 except botocore.exceptions.NoCredentialsError:
299 "Please fill ~/.aws/credentials, ~/.boto or set "
300 "AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY in env")
301 return TestCase.EX_PUBLISH_ARTIFACTS_ERROR
302 except Exception: # pylint: disable=broad-except
303 self.__logger.exception("Cannot publish the artifacts")
304 return TestCase.EX_PUBLISH_ARTIFACTS_ERROR
307 """Clean the resources.
309 It can be overriden if resources must be deleted after
310 running the test case.