Move push_results_to_db to TestCase
[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 from datetime import datetime
13 import json
14 import logging
15 import os
16 import re
17 import requests
18
19 from functest.utils import decorators
20
21
22 import prettytable
23
24
25 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
26
27
28 class TestCase(object):
29     """Base model for single test case."""
30
31     EX_OK = os.EX_OK
32     """everything is OK"""
33
34     EX_RUN_ERROR = os.EX_SOFTWARE
35     """run() failed"""
36
37     EX_PUSH_TO_DB_ERROR = os.EX_SOFTWARE - 1
38     """push_to_db() failed"""
39
40     EX_TESTCASE_FAILED = os.EX_SOFTWARE - 2
41     """results are false"""
42
43     _job_name_rule = "(dai|week)ly-(.+?)-[0-9]*"
44     _headers = {'Content-Type': 'application/json'}
45     __logger = logging.getLogger(__name__)
46
47     def __init__(self, **kwargs):
48         self.details = {}
49         self.project_name = kwargs.get('project_name', 'functest')
50         self.case_name = kwargs.get('case_name', '')
51         self.criteria = kwargs.get('criteria', 100)
52         self.result = 0
53         self.start_time = 0
54         self.stop_time = 0
55
56     def __str__(self):
57         try:
58             assert self.project_name
59             assert self.case_name
60             result = 'PASS' if(self.is_successful(
61                 ) == TestCase.EX_OK) else 'FAIL'
62             msg = prettytable.PrettyTable(
63                 header_style='upper', padding_width=5,
64                 field_names=['test case', 'project', 'duration',
65                              'result'])
66             msg.add_row([self.case_name, self.project_name,
67                          self.get_duration(), result])
68             return msg.get_string()
69         except AssertionError:
70             self.__logger.error("We cannot print invalid objects")
71             return super(TestCase, self).__str__()
72
73     def get_duration(self):
74         """Return the duration of the test case.
75
76         Returns:
77             duration if start_time and stop_time are set
78             "XX:XX" otherwise.
79         """
80         try:
81             assert self.start_time
82             assert self.stop_time
83             if self.stop_time < self.start_time:
84                 return "XX:XX"
85             return "{0[0]:02.0f}:{0[1]:02.0f}".format(divmod(
86                 self.stop_time - self.start_time, 60))
87         except Exception:  # pylint: disable=broad-except
88             self.__logger.error("Please run test before getting the duration")
89             return "XX:XX"
90
91     def is_successful(self):
92         """Interpret the result of the test case.
93
94         It allows getting the result of TestCase. It completes run()
95         which only returns the execution status.
96
97         It can be overriden if checking result is not suitable.
98
99         Returns:
100             TestCase.EX_OK if result is 'PASS'.
101             TestCase.EX_TESTCASE_FAILED otherwise.
102         """
103         try:
104             assert self.criteria
105             assert self.result is not None
106             if (not isinstance(self.result, str) and
107                     not isinstance(self.criteria, str)):
108                 if self.result >= self.criteria:
109                     return TestCase.EX_OK
110             else:
111                 # Backward compatibility
112                 # It must be removed as soon as TestCase subclasses
113                 # stop setting result = 'PASS' or 'FAIL'.
114                 # In this case criteria is unread.
115                 self.__logger.warning(
116                     "Please update result which must be an int!")
117                 if self.result == 'PASS':
118                     return TestCase.EX_OK
119         except AssertionError:
120             self.__logger.error("Please run test before checking the results")
121         return TestCase.EX_TESTCASE_FAILED
122
123     def run(self, **kwargs):
124         """Run the test case.
125
126         It allows running TestCase and getting its execution
127         status.
128
129         The subclasses must override the default implementation which
130         is false on purpose.
131
132         The new implementation must set the following attributes to
133         push the results to DB:
134
135             * result,
136             * start_time,
137             * stop_time.
138
139         Args:
140             kwargs: Arbitrary keyword arguments.
141
142         Returns:
143             TestCase.EX_RUN_ERROR.
144         """
145         # pylint: disable=unused-argument
146         self.__logger.error("Run must be implemented")
147         return TestCase.EX_RUN_ERROR
148
149     @decorators.can_dump_request_to_file
150     def push_to_db(self):
151         """Push the results of the test case to the DB.
152
153         It allows publishing the results and checking the status.
154
155         It could be overriden if the common implementation is not
156         suitable.
157
158         The following attributes must be set before pushing the results to DB:
159
160             * project_name,
161             * case_name,
162             * result,
163             * start_time,
164             * stop_time.
165
166         The next vars must be set in env:
167
168             * TEST_DB_URL,
169             * INSTALLER_TYPE,
170             * DEPLOY_SCENARIO,
171             * NODE_NAME,
172             * BUILD_TAG.
173
174         Returns:
175             TestCase.EX_OK if results were pushed to DB.
176             TestCase.EX_PUSH_TO_DB_ERROR otherwise.
177         """
178         try:
179             assert self.project_name
180             assert self.case_name
181             assert self.start_time
182             assert self.stop_time
183             url = os.environ['TEST_DB_URL']
184             data = {"project_name": self.project_name,
185                     "case_name": self.case_name,
186                     "details": self.details}
187             data["installer"] = os.environ['INSTALLER_TYPE']
188             data["scenario"] = os.environ['DEPLOY_SCENARIO']
189             data["pod_name"] = os.environ['NODE_NAME']
190             data["build_tag"] = os.environ['BUILD_TAG']
191             data["criteria"] = 'PASS' if self.is_successful(
192                 ) == TestCase.EX_OK else 'FAIL'
193             data["start_date"] = datetime.fromtimestamp(
194                 self.start_time).strftime('%Y-%m-%d %H:%M:%S')
195             data["stop_date"] = datetime.fromtimestamp(
196                 self.stop_time).strftime('%Y-%m-%d %H:%M:%S')
197             try:
198                 data["version"] = re.search(
199                     TestCase._job_name_rule,
200                     os.environ['BUILD_TAG']).group(2)
201             except Exception:  # pylint: disable=broad-except
202                 data["version"] = "unknown"
203             req = requests.post(
204                 url, data=json.dumps(data, sort_keys=True),
205                 headers=self._headers)
206             req.raise_for_status()
207             self.__logger.info(
208                 "The results %s were successfully pushed to DB %s", data, url)
209         except AssertionError:
210             self.__logger.exception(
211                 "Please run test before publishing the results")
212             return TestCase.EX_PUSH_TO_DB_ERROR
213         except KeyError as exc:
214             self.__logger.error("Please set env var: " + str(exc))
215             return TestCase.EX_PUSH_TO_DB_ERROR
216         except requests.exceptions.HTTPError:
217             self.__logger.exception("The HTTP request raises issues")
218             return TestCase.EX_PUSH_TO_DB_ERROR
219         except Exception:  # pylint: disable=broad-except
220             self.__logger.exception("The results cannot be pushed to DB")
221             return TestCase.EX_PUSH_TO_DB_ERROR
222         return TestCase.EX_OK
223
224     def clean(self):
225         """Clean the resources.
226
227         It can be overriden if resources must be deleted after
228         running the test case.
229         """