Migrating to Python v3

 - support for Python v3.8.x
 - support for Python v3.5.x
 - new tag, 2019.2.8
 - updates class generation and iterators
 - unittests updated with coverage >75%
 - new coverage routines
 - unittests profiling
 - full fake data for unittests
 - unittest testrun is ~1.5 seconds long

Bugfixes
 - 34834, proper use of 'sudo' option
 - multiple proper iterator use
 - 37919, show warning when installed and candidate versions
   are newer comparing to release version

Change-Id: Idd6b889f7ce94ae0c832e2f0a0346e4fdc3264a3
Related-PROD: PROD-34834 PROD-34664 PROD-34919
diff --git a/tests/test_base.py b/tests/test_base.py
index c4a627f..b30a6e5 100644
--- a/tests/test_base.py
+++ b/tests/test_base.py
@@ -1,29 +1,122 @@
 import contextlib
 import io
+import os
 import sys
 import unittest
+import logging
+
+from copy import deepcopy
+
+tests_dir = os.path.dirname(__file__)
+tests_dir = os.path.normpath(tests_dir)
+tests_dir = os.path.abspath(tests_dir)
 
 
 class CfgCheckerTestBase(unittest.TestCase):
     dummy_base_var = 0
+    last_stderr = ""
+    last_stdout = ""
 
-    def _safe_import_module(self, _str):
+    def _safe_import(self, _str):
+        if "." not in _str:
+            return self._safe_import_module(_str)
+        else:
+            return self._safe_import_class(_str)
+
+    def _safe_import_class(self, _str):
+        _import_msg = ""
+        attrs = _str.split('.')
+        _import_msg, _module = self._safe_import_module(attrs[0])
+        if _import_msg:
+            return _import_msg, _module
+        else:
+            for attr_name in attrs[1:]:
+                _module = getattr(_module, attr_name)
+            return "", _module
+
+    @staticmethod
+    def _safe_import_module(_str, *args, **kwargs):
         _import_msg = ""
         _module = None
 
         try:
-            _module = __import__(_str)
+            _module = __import__(_str, *args, **kwargs)
         except ImportError as e:
             _import_msg = e.message
 
         return _import_msg, _module
 
+    @staticmethod
+    def _safe_run(_obj, *args, **kwargs):
+        _m = ""
+        try:
+            _r = _obj(*args, **kwargs)
+        except Exception as ex:
+            if hasattr(ex, 'message'):
+                _m = "{}: {}".format(str(_obj), ex.message)
+            elif hasattr(ex, 'msg'):
+                _m = "{}: {}".format(str(_obj), ex.msg)
+            else:
+                _m = "{}: {}".format(str(_obj), "<no message>")
+        return _r, _m
+
+    def run_main(self, args_list):
+        _module_name = 'cfg_checker.cfg_check'
+        _m = self._try_import(_module_name)
+        with self.save_arguments():
+            with self.redirect_output():
+                with self.assertRaises(SystemExit) as ep:
+                    sys.argv = ["fake.py"] + args_list
+                    _m.cfg_check.config_check_entrypoint()
+        return ep.exception.code
+
+    def run_cli(self, command, args_list):
+        _module_name = 'cfg_checker.cli.command'
+        _m = self._try_import(_module_name)
+        with self.save_arguments():
+            with self.redirect_output():
+                with self.assertRaises(SystemExit) as ep:
+                    import sys
+                    sys.argv = ["fake.py"] + args_list
+                    _m.cli.command.cli_command(
+                        "Fake run for '{} {}'".format(
+                            command,
+                            " ".join(args_list)
+                        ),
+                        command
+                    )
+        return ep.exception.code
+
     @contextlib.contextmanager
     def redirect_output(self):
         save_stdout = sys.stdout
         save_stderr = sys.stderr
-        sys.stdout = io.BytesIO()
-        sys.stderr = io.BytesIO()
+        sys.stdout = io.StringIO()
+        sys.stderr = io.StringIO()
+        logging.disable(logging.CRITICAL)
         yield
+        self.last_stderr = sys.stderr.read()
+        self.last_stdout = sys.stdout.read()
         sys.stdout = save_stdout
         sys.stderr = save_stderr
+
+    @contextlib.contextmanager
+    def save_arguments(self):
+        _argv = deepcopy(sys.argv)
+        yield
+        sys.argv = _argv
+
+    def _try_import(self, module_name):
+        with self.redirect_output():
+            _msg, _m = self._safe_import_module(module_name)
+
+        self.assertEqual(
+            len(_msg),
+            0,
+            "Error importing '{}': {}".format(
+                module_name,
+                _msg
+            )
+        )
+
+        return _m