Move push_results_to_db to TestCase
[functest.git] / functest / core / testcase.py
index b9dcbb2..5894f5c 100644 (file)
@@ -9,10 +9,18 @@
 
 """Define the parent class of all Functest TestCases."""
 
+from datetime import datetime
+import json
+import logging
 import os
+import re
+import requests
+
+from functest.utils import decorators
+
+
+import prettytable
 
-import functest.utils.functest_logger as ft_logger
-import functest.utils.functest_utils as ft_utils
 
 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
 
@@ -32,17 +40,55 @@ class TestCase(object):
     EX_TESTCASE_FAILED = os.EX_SOFTWARE - 2
     """results are false"""
 
-    logger = ft_logger.Logger(__name__).getLogger()
+    _job_name_rule = "(dai|week)ly-(.+?)-[0-9]*"
+    _headers = {'Content-Type': 'application/json'}
+    __logger = logging.getLogger(__name__)
 
     def __init__(self, **kwargs):
         self.details = {}
         self.project_name = kwargs.get('project_name', 'functest')
         self.case_name = kwargs.get('case_name', '')
-        self.result = ""
-        self.start_time = ""
-        self.stop_time = ""
+        self.criteria = kwargs.get('criteria', 100)
+        self.result = 0
+        self.start_time = 0
+        self.stop_time = 0
+
+    def __str__(self):
+        try:
+            assert self.project_name
+            assert self.case_name
+            result = 'PASS' if(self.is_successful(
+                ) == TestCase.EX_OK) else 'FAIL'
+            msg = prettytable.PrettyTable(
+                header_style='upper', padding_width=5,
+                field_names=['test case', 'project', 'duration',
+                             'result'])
+            msg.add_row([self.case_name, self.project_name,
+                         self.get_duration(), result])
+            return msg.get_string()
+        except AssertionError:
+            self.__logger.error("We cannot print invalid objects")
+            return super(TestCase, self).__str__()
+
+    def get_duration(self):
+        """Return the duration of the test case.
+
+        Returns:
+            duration if start_time and stop_time are set
+            "XX:XX" otherwise.
+        """
+        try:
+            assert self.start_time
+            assert self.stop_time
+            if self.stop_time < self.start_time:
+                return "XX:XX"
+            return "{0[0]:02.0f}:{0[1]:02.0f}".format(divmod(
+                self.stop_time - self.start_time, 60))
+        except Exception:  # pylint: disable=broad-except
+            self.__logger.error("Please run test before getting the duration")
+            return "XX:XX"
 
-    def check_result(self):
+    def is_successful(self):
         """Interpret the result of the test case.
 
         It allows getting the result of TestCase. It completes run()
@@ -55,11 +101,23 @@ class TestCase(object):
             TestCase.EX_TESTCASE_FAILED otherwise.
         """
         try:
-            assert self.result
-            if self.result == 'PASS':
-                return TestCase.EX_OK
+            assert self.criteria
+            assert self.result is not None
+            if (not isinstance(self.result, str) and
+                    not isinstance(self.criteria, str)):
+                if self.result >= self.criteria:
+                    return TestCase.EX_OK
+            else:
+                # Backward compatibility
+                # It must be removed as soon as TestCase subclasses
+                # stop setting result = 'PASS' or 'FAIL'.
+                # In this case criteria is unread.
+                self.__logger.warning(
+                    "Please update result which must be an int!")
+                if self.result == 'PASS':
+                    return TestCase.EX_OK
         except AssertionError:
-            self.logger.error("Please run test before checking the results")
+            self.__logger.error("Please run test before checking the results")
         return TestCase.EX_TESTCASE_FAILED
 
     def run(self, **kwargs):
@@ -85,17 +143,19 @@ class TestCase(object):
             TestCase.EX_RUN_ERROR.
         """
         # pylint: disable=unused-argument
-        self.logger.error("Run must be implemented")
+        self.__logger.error("Run must be implemented")
         return TestCase.EX_RUN_ERROR
 
+    @decorators.can_dump_request_to_file
     def push_to_db(self):
         """Push the results of the test case to the DB.
 
-        It allows publishing the results and to check the status.
+        It allows publishing the results and checking the status.
 
         It could be overriden if the common implementation is not
-        suitable. The following attributes must be set before pushing
-        the results to DB:
+        suitable.
+
+        The following attributes must be set before pushing the results to DB:
 
             * project_name,
             * case_name,
@@ -103,6 +163,14 @@ class TestCase(object):
             * start_time,
             * stop_time.
 
+        The next vars must be set in env:
+
+            * TEST_DB_URL,
+            * INSTALLER_TYPE,
+            * DEPLOY_SCENARIO,
+            * NODE_NAME,
+            * BUILD_TAG.
+
         Returns:
             TestCase.EX_OK if results were pushed to DB.
             TestCase.EX_PUSH_TO_DB_ERROR otherwise.
@@ -110,17 +178,52 @@ class TestCase(object):
         try:
             assert self.project_name
             assert self.case_name
-            assert self.result
             assert self.start_time
             assert self.stop_time
-            if ft_utils.push_results_to_db(
-                    self.project_name, self.case_name, self.start_time,
-                    self.stop_time, self.result, self.details):
-                self.logger.info("The results were successfully pushed to DB")
-                return TestCase.EX_OK
-            else:
-                self.logger.error("The results cannot be pushed to DB")
-                return TestCase.EX_PUSH_TO_DB_ERROR
+            url = os.environ['TEST_DB_URL']
+            data = {"project_name": self.project_name,
+                    "case_name": self.case_name,
+                    "details": self.details}
+            data["installer"] = os.environ['INSTALLER_TYPE']
+            data["scenario"] = os.environ['DEPLOY_SCENARIO']
+            data["pod_name"] = os.environ['NODE_NAME']
+            data["build_tag"] = os.environ['BUILD_TAG']
+            data["criteria"] = 'PASS' if self.is_successful(
+                ) == TestCase.EX_OK else 'FAIL'
+            data["start_date"] = datetime.fromtimestamp(
+                self.start_time).strftime('%Y-%m-%d %H:%M:%S')
+            data["stop_date"] = datetime.fromtimestamp(
+                self.stop_time).strftime('%Y-%m-%d %H:%M:%S')
+            try:
+                data["version"] = re.search(
+                    TestCase._job_name_rule,
+                    os.environ['BUILD_TAG']).group(2)
+            except Exception:  # pylint: disable=broad-except
+                data["version"] = "unknown"
+            req = requests.post(
+                url, data=json.dumps(data, sort_keys=True),
+                headers=self._headers)
+            req.raise_for_status()
+            self.__logger.info(
+                "The results %s were successfully pushed to DB %s", data, url)
+        except AssertionError:
+            self.__logger.exception(
+                "Please run test before publishing the results")
+            return TestCase.EX_PUSH_TO_DB_ERROR
+        except KeyError as exc:
+            self.__logger.error("Please set env var: " + str(exc))
+            return TestCase.EX_PUSH_TO_DB_ERROR
+        except requests.exceptions.HTTPError:
+            self.__logger.exception("The HTTP request raises issues")
+            return TestCase.EX_PUSH_TO_DB_ERROR
         except Exception:  # pylint: disable=broad-except
-            self.logger.exception("The results cannot be pushed to DB")
+            self.__logger.exception("The results cannot be pushed to DB")
             return TestCase.EX_PUSH_TO_DB_ERROR
+        return TestCase.EX_OK
+
+    def clean(self):
+        """Clean the resources.
+
+        It can be overriden if resources must be deleted after
+        running the test case.
+        """