Leverage on abc and stevedore 81/60881/2
authorCédric Ollivier <cedric.ollivier@orange.com>
Fri, 10 Aug 2018 10:55:10 +0000 (12:55 +0200)
committerCédric Ollivier <cedric.ollivier@orange.com>
Sun, 12 Aug 2018 16:32:59 +0000 (18:32 +0200)
Change-Id: I7b3c4c0c5dd0c9e6fb3e52c3fff5221d4b831b02
Signed-off-by: Cédric Ollivier <cedric.ollivier@orange.com>
requirements.txt
setup.cfg
tox.ini
xtesting/ci/run_tests.py
xtesting/ci/testcases.yaml
xtesting/core/feature.py
xtesting/core/testcase.py
xtesting/tests/unit/ci/test_run_tests.py
xtesting/tests/unit/core/test_feature.py
xtesting/tests/unit/core/test_testcase.py
xtesting/tests/unit/utils/test_decorators.py

index dc05d71..02b3655 100644 (file)
@@ -2,6 +2,7 @@
 # of appearance. Changing the order has an impact on the overall integration
 # process, which may cause wedges in the gate later.
 pbr!=2.1.0,>=2.0.0 # Apache-2.0
+stevedore>=1.20.0  # Apache-2.0
 PyYAML>=3.12 # MIT
 enum34>=1.0.4;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
 requests>=2.14.2 # Apache-2.0
index 4c1a1ed..279e46a 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -23,6 +23,12 @@ packages = xtesting
 [entry_points]
 console_scripts =
     run_tests = xtesting.ci.run_tests:main
+xtesting.testcase =
+    bashfeature = xtesting.core.feature:BashFeature
+    robotframework = xtesting.core.robotframework:RobotFramework
+    unit = xtesting.core.unit:Suite
+    first = xtesting.samples.first:Test
+    second = xtesting.samples.second:Test
 
 [build_sphinx]
 all_files = 1
diff --git a/tox.ini b/tox.ini
index 6c6de70..98df801 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,6 @@
 [tox]
 envlist = docs,pep8,pylint,yamllint,bashate,py35,py27,perm,cover
+skipsdist = True
 
 [testenv]
 usedevelop = True
index 59ed561..3a3e685 100644 (file)
@@ -15,7 +15,6 @@
 
 import argparse
 import errno
-import importlib
 import logging
 import logging.config
 import os
@@ -27,6 +26,7 @@ import enum
 import pkg_resources
 import prettytable
 import six
+from stevedore import driver
 import yaml
 
 from xtesting.ci import tier_builder
@@ -151,10 +151,12 @@ class Runner(object):
         if run_dict:
             try:
                 LOGGER.info("Loading test case '%s'...", test.get_name())
-                module = importlib.import_module(run_dict['module'])
-                cls = getattr(module, run_dict['class'])
                 test_dict = Runner.get_dict_by_test(test.get_name())
-                test_case = cls(**test_dict)
+                test_case = driver.DriverManager(
+                    namespace='xtesting.testcase',
+                    name=run_dict['name'],
+                    invoke_on_load=True,
+                    invoke_kwds=test_dict).driver
                 self.executed_test_cases[test.get_name()] = test_case
                 test_case.check_requirements()
                 if test_case.is_skipped:
index 6ab5927..7c621fd 100644 (file)
@@ -13,8 +13,7 @@ tiers:
                 clean_flag: false
                 description: ''
                 run:
-                    module: 'xtesting.samples.first'
-                    class: 'Test'
+                    name: 'first'
 
             -
                 case_name: second
@@ -24,8 +23,7 @@ tiers:
                 clean_flag: false
                 description: ''
                 run:
-                    module: 'xtesting.samples.second'
-                    class: 'Test'
+                    name: 'second'
 
             -
                 case_name: third
@@ -35,8 +33,7 @@ tiers:
                 clean_flag: false
                 description: ''
                 run:
-                    module: 'xtesting.core.feature'
-                    class: 'BashFeature'
+                    name: 'bashfeature'
                     args:
                         cmd: 'echo -n Hello World; exit 0'
 
@@ -48,8 +45,7 @@ tiers:
                 clean_flag: false
                 description: ''
                 run:
-                    module: 'xtesting.core.unit'
-                    class: 'Suite'
+                    name: 'unit'
                     args:
                         name: 'xtesting.samples.fourth'
 
index 932e023..8621551 100644 (file)
@@ -13,21 +13,25 @@ Feature is considered as TestCase offered by Third-party. It offers
 helpers to run any python method or any bash command.
 """
 
+import abc
 import logging
 import subprocess
 import time
 
+import six
 from xtesting.core import testcase
 
 __author__ = ("Serena Feng <feng.xiaowei@zte.com.cn>, "
               "Cedric Ollivier <cedric.ollivier@orange.com>")
 
 
+@six.add_metaclass(abc.ABCMeta)
 class Feature(testcase.TestCase):
     """Base model for single feature."""
 
     __logger = logging.getLogger(__name__)
 
+    @abc.abstractmethod
     def execute(self, **kwargs):
         """Execute the Python method.
 
@@ -39,12 +43,7 @@ class Feature(testcase.TestCase):
 
         Args:
             kwargs: Arbitrary keyword arguments.
-
-        Returns:
-            -1.
         """
-        # pylint: disable=unused-argument,no-self-use
-        return -1
 
     def run(self, **kwargs):
         """Run the feature.
index 61a2e8c..c3a1c38 100644 (file)
@@ -9,6 +9,7 @@
 
 """Define the parent class of all Xtesting TestCases."""
 
+import abc
 from datetime import datetime
 import json
 import logging
@@ -17,6 +18,7 @@ import re
 import requests
 
 import prettytable
+import six
 
 from xtesting.utils import decorators
 from xtesting.utils import env
@@ -24,7 +26,9 @@ from xtesting.utils import env
 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
 
 
-class TestCase(object):  # pylint: disable=too-many-instance-attributes
+@six.add_metaclass(abc.ABCMeta)
+class TestCase(object):
+    # pylint: disable=too-many-instance-attributes
     """Base model for single test case."""
 
     EX_OK = os.EX_OK
@@ -138,6 +142,7 @@ class TestCase(object):  # pylint: disable=too-many-instance-attributes
         """
         self.is_skipped = False
 
+    @abc.abstractmethod
     def run(self, **kwargs):
         """Run the test case.
 
@@ -156,13 +161,7 @@ class TestCase(object):  # pylint: disable=too-many-instance-attributes
 
         Args:
             kwargs: Arbitrary keyword arguments.
-
-        Returns:
-            TestCase.EX_RUN_ERROR.
         """
-        # pylint: disable=unused-argument
-        self.__logger.error("Run must be implemented")
-        return TestCase.EX_RUN_ERROR
 
     @decorators.can_dump_request_to_file
     def push_to_db(self):
index 392612b..423bf48 100644 (file)
@@ -20,6 +20,10 @@ from xtesting.core.testcase import TestCase
 class FakeModule(TestCase):
 
     def run(self, **kwargs):
+        self.start_time = 0
+        self.stop_time = 1
+        self.criteria = 100
+        self.result = 100
         return TestCase.EX_OK
 
 
@@ -140,9 +144,8 @@ class RunTestsTesting(unittest.TestCase):
         msg = "Cannot import the class for the test case."
         self.assertTrue(msg in str(context.exception))
 
-    @mock.patch('importlib.import_module', name="module",
-                return_value=mock.Mock(test_class=mock.Mock(
-                    side_effect=FakeModule)))
+    @mock.patch('stevedore.driver.DriverManager',
+                return_value=mock.Mock(driver=FakeModule()))
     @mock.patch('xtesting.ci.run_tests.Runner.get_dict_by_test')
     def test_run_tests_default(self, *args):
         mock_test = mock.Mock()
@@ -151,14 +154,15 @@ class RunTestsTesting(unittest.TestCase):
                   'is_enabled.return_value': True,
                   'needs_clean.return_value': True}
         mock_test.configure_mock(**kwargs)
-        test_run_dict = {'module': 'test_module',
-                         'class': 'test_class'}
+        test_run_dict = {'name': 'test_module'}
         with mock.patch('xtesting.ci.run_tests.Runner.get_run_dict',
                         return_value=test_run_dict):
             self.runner.clean_flag = True
-            self.runner.run_test(mock_test)
+            self.assertEqual(self.runner.run_test(mock_test), TestCase.EX_OK)
         args[0].assert_called_with('test_name')
-        args[1].assert_called_with('test_module')
+        args[1].assert_called_with(
+            invoke_kwds=mock.ANY, invoke_on_load=True, name='test_module',
+            namespace='xtesting.testcase')
         self.assertEqual(self.runner.overall_result,
                          run_tests.Result.EX_OK)
 
@@ -170,8 +174,7 @@ class RunTestsTesting(unittest.TestCase):
                   'is_enabled.return_value': False,
                   'needs_clean.return_value': True}
         mock_test.configure_mock(**kwargs)
-        test_run_dict = {'module': 'test_module',
-                         'class': 'test_class'}
+        test_run_dict = {'name': 'test_name'}
         with mock.patch('xtesting.ci.run_tests.Runner.get_run_dict',
                         return_value=test_run_dict):
             self.runner.clean_flag = True
index 72bc488..a4ac5af 100644 (file)
@@ -18,6 +18,19 @@ from xtesting.core import feature
 from xtesting.core import testcase
 
 
+class FakeTestCase(feature.Feature):
+
+    def execute(self, **kwargs):
+        pass
+
+
+class AbstractFeatureTesting(unittest.TestCase):
+
+    def test_run_unimplemented(self):
+        with self.assertRaises(TypeError):
+            feature.Feature(case_name="feature", project_name="xtesting")
+
+
 class FeatureTestingBase(unittest.TestCase):
 
     _case_name = "foo"
@@ -46,7 +59,7 @@ class FeatureTesting(FeatureTestingBase):
         # what will break these unit tests.
         logging.disable(logging.CRITICAL)
         with mock.patch('six.moves.builtins.open'):
-            self.feature = feature.Feature(
+            self.feature = FakeTestCase(
                 project_name=self._project_name, case_name=self._case_name)
 
     def test_run_exc(self):
index 6b83b97..51ea6f3 100644 (file)
@@ -7,6 +7,8 @@
 # which accompanies this distribution, and is available at
 # http://www.apache.org/licenses/LICENSE-2.0
 
+# pylint: disable=missing-docstring
+
 """Define the class required to fully cover testcase."""
 
 from datetime import datetime
@@ -23,10 +25,22 @@ from xtesting.core import testcase
 __author__ = "Cedric Ollivier <cedric.ollivier@orange.com>"
 
 
-class TestCaseTesting(unittest.TestCase):
-    """The class testing TestCase."""
+class FakeTestCase(testcase.TestCase):
+    # pylint: disable=too-many-instance-attributes
+
+    def run(self, **kwargs):
+        return testcase.TestCase.EX_OK
+
+
+class AbstractTestCaseTesting(unittest.TestCase):
+
+    def test_run_unimplemented(self):
+        with self.assertRaises(TypeError):
+            testcase.TestCase(case_name="base", project_name="xtesting")
 
-    # pylint: disable=missing-docstring,too-many-public-methods
+
+class TestCaseTesting(unittest.TestCase):
+    # pylint: disable=too-many-instance-attributes,too-many-public-methods
 
     _case_name = "base"
     _project_name = "xtesting"
@@ -35,8 +49,8 @@ class TestCaseTesting(unittest.TestCase):
     _headers = {'Content-Type': 'application/json'}
 
     def setUp(self):
-        self.test = testcase.TestCase(case_name=self._case_name,
-                                      project_name=self._project_name)
+        self.test = FakeTestCase(
+            case_name=self._case_name, project_name=self._project_name)
         self.test.start_time = 1
         self.test.stop_time = 2
         self.test.result = 100
@@ -47,9 +61,8 @@ class TestCaseTesting(unittest.TestCase):
         os.environ['NODE_NAME'] = "node_name"
         os.environ['BUILD_TAG'] = "foo-daily-master-bar"
 
-    def test_run_unimplemented(self):
-        self.assertEqual(self.test.run(),
-                         testcase.TestCase.EX_RUN_ERROR)
+    def test_run_fake(self):
+        self.assertEqual(self.test.run(), testcase.TestCase.EX_OK)
 
     def _test_pushdb_missing_attribute(self):
         self.assertEqual(self.test.push_to_db(),
@@ -255,13 +268,11 @@ class TestCaseTesting(unittest.TestCase):
 
     def test_str_project_name_ko(self):
         self.test.project_name = None
-        self.assertIn("<xtesting.core.testcase.TestCase object at",
-                      str(self.test))
+        self.assertIn("FakeTestCase object at", str(self.test))
 
     def test_str_case_name_ko(self):
         self.test.case_name = None
-        self.assertIn("<xtesting.core.testcase.TestCase object at",
-                      str(self.test))
+        self.assertIn("FakeTestCase object at", str(self.test))
 
     def test_str_pass(self):
         duration = '01:01'
index 83b182a..c08a7ea 100644 (file)
@@ -28,6 +28,13 @@ FILE = '{}/null'.format(DIR)
 URL = 'file://{}'.format(FILE)
 
 
+class FakeTestCase(testcase.TestCase):
+    # pylint: disable=missing-docstring
+
+    def run(self, **kwargs):
+        return testcase.TestCase.EX_OK
+
+
 class DecoratorsTesting(unittest.TestCase):
     # pylint: disable=missing-docstring
 
@@ -66,7 +73,7 @@ class DecoratorsTesting(unittest.TestCase):
         return json.dumps(data, sort_keys=True)
 
     def _get_testcase(self):
-        test = testcase.TestCase(
+        test = FakeTestCase(
             project_name=self._project_name, case_name=self._case_name)
         test.start_time = self._start_time
         test.stop_time = self._stop_time
@@ -74,6 +81,10 @@ class DecoratorsTesting(unittest.TestCase):
         test.details = {}
         return test
 
+    def test_run_fake(self):
+        test = self._get_testcase()
+        self.assertEqual(test.run(), testcase.TestCase.EX_OK)
+
     @mock.patch('requests.post')
     def test_http_shema(self, *args):
         os.environ['TEST_DB_URL'] = 'http://127.0.0.1'