Add pytest driver in Xtesting 40/73940/11
authorCédric Ollivier <cedric.ollivier@orange.com>
Fri, 31 Mar 2023 13:52:21 +0000 (15:52 +0200)
committerCédric Ollivier <cedric.ollivier@orange.com>
Fri, 14 Apr 2023 14:05:50 +0000 (16:05 +0200)
Co-authored-by: Edouard Hinard <edouard.hinard@orange.com>
Signed-off-by: Cédric Ollivier <cedric.ollivier@orange.com>
Change-Id: Ice364f7f4287aacc7f375ed4a23503fd38c8e543

api/apidoc/xtesting.core.pytest.rst [new file with mode: 0644]
api/apidoc/xtesting.core.rst
docker/core/testcases.yaml
requirements.txt
setup.cfg
test-requirements.txt
upper-constraints.txt
xtesting/ci/testcases.yaml
xtesting/core/pytest.py [new file with mode: 0644]

diff --git a/api/apidoc/xtesting.core.pytest.rst b/api/apidoc/xtesting.core.pytest.rst
new file mode 100644 (file)
index 0000000..fcc649f
--- /dev/null
@@ -0,0 +1,7 @@
+xtesting\.core\.pytest module
+=============================
+
+.. automodule:: xtesting.core.pytest
+    :members:
+    :undoc-members:
+    :show-inheritance:
index 5707a28..3027671 100644 (file)
@@ -15,6 +15,7 @@ Submodules
    xtesting.core.behaveframework
    xtesting.core.campaign
    xtesting.core.feature
+   xtesting.core.pytest
    xtesting.core.robotframework
    xtesting.core.testcase
    xtesting.core.unit
index 360869d..deee73b 100644 (file)
@@ -78,3 +78,14 @@ tiers:
           args:
             private_data_dir: /usr/lib/python3.10/site-packages/xtesting/samples
             playbook: helloworld.yml
+      - case_name: nineth
+        project_name: xtesting
+        enabled: true
+        criteria: 100
+        blocking: true
+        clean_flag: false
+        description: ''
+        run:
+          name: pytest
+          args:
+            dir: /usr/lib/python3.10/site-packages/xtesting/samples/fourth.py
index 7159652..1db7968 100644 (file)
@@ -17,3 +17,5 @@ junitxml
 boto3 # Apache-2.0
 lxml!=3.7.0 # BSD
 ansible-runner!=1.3.5 # Apache 2.0
+pytest  # MIT
+pytest-html  #MPL-2.0
index 944e2d0..06ff995 100644 (file)
--- a/setup.cfg
+++ b/setup.cfg
@@ -31,6 +31,7 @@ xtesting.testcase =
     second = xtesting.samples.second:Test
     mts = xtesting.core.mts:MTSLauncher
     ansible = xtesting.core.ansible:Ansible
+    pytest = xtesting.core.pytest:Pytest
 
 [build_sphinx]
 all_files = 1
index 4be1d67..22457d0 100644 (file)
@@ -3,8 +3,6 @@
 # process, which may cause wedges in the gate later.
 coverage!=4.4 # Apache-2.0
 mock!=4.0.0,!=4.0.1 # BSD
-pytest  # MIT
-pytest-html  #MPL-2.0
 pytest-cov
 flake8 # MIT
 pylint # GPLv2
index 59d9f4d..e15894a 100644 (file)
@@ -5,7 +5,4 @@ behave-html-formatter===0.9.8
 pylint===2.11.1
 flake8===4.0.1
 ansible-lint===5.2.1
-pytest===7.1.2
-pytest-cov===3.0.0
-pytest-html===3.1.1
 pre-commit===3.1.1
index ead3441..488bf95 100644 (file)
@@ -99,3 +99,14 @@ tiers:
           args:
             private_data_dir: /usr/lib/python3.10/site-packages/xtesting/samples
             playbook: helloworld.yml
+      - case_name: nineth
+        project_name: xtesting
+        enabled: true
+        criteria: 100
+        blocking: true
+        clean_flag: false
+        description: ''
+        run:
+          name: pytest
+          args:
+            dir: /usr/lib/python3.10/site-packages/xtesting/samples/fourth.py
diff --git a/xtesting/core/pytest.py b/xtesting/core/pytest.py
new file mode 100644 (file)
index 0000000..1dadf30
--- /dev/null
@@ -0,0 +1,101 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2023 Orange and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+
+"""Define classes required to run any Pytest suites."""
+
+import contextlib
+import io
+import logging
+import time
+
+import pytest
+
+from xtesting.core import testcase
+
+
+@pytest.hookimpl(hookwrapper=True)
+def pytest_runtest_makereport(item, call):
+    """Collect all Pytest results"""
+    # pylint: disable=unused-argument
+    yreport = yield
+    report = yreport.get_result()
+    if report.when == 'call':
+        test = {"name": report.nodeid, "status": report.outcome.upper()}
+        if report.passed:
+            Pytest.passed += 1
+        elif report.failed:
+            Pytest.failed += 1
+            test['failure'] = report.longreprtext
+        Pytest.tests.append(test)
+
+
+class Pytest(testcase.TestCase):
+
+    """Pytest driver
+
+    The pytest driver can be used on every test set written for pytest.
+    Given a pytest package that is launched with the command:
+
+    .. code-block:: shell
+
+        pytest --opt1 arg1 --opt2 testdir
+
+    it can be executed by xtesting with the following testcase.yaml:
+
+    .. code-block:: yaml
+
+        run:
+          name: pytest
+          args:
+            opt1: arg1
+            opt2: arg2
+    """
+
+    __logger = logging.getLogger(__name__)
+    tests = []
+    passed = 0
+    failed = 0
+
+    def run(self, **kwargs):
+        # parsing args
+        #  - 'dir' is mandatory
+        #  - 'options' is an optional list or a dict flatten to a list
+        status = self.EX_RUN_ERROR
+        self.start_time = time.time()
+        try:
+            pydir = kwargs.pop('dir')
+            options = kwargs.pop('options', {})
+            options['html'] = f'{self.res_dir}/results.html'
+            options['junitxml'] = f'{self.res_dir}/results.xml'
+            if 'tb' not in options:
+                options['tb'] = 'no'
+            options = [
+                str(item) for opt in zip(
+                    [f'--{k}' if len(str(k)) > 1 else
+                        f'-{k}' for k in options.keys()],
+                    options.values())
+                for item in opt if item is not None]
+            with contextlib.redirect_stdout(io.StringIO()) as output:
+                pytest.main(
+                    args=[pydir] + ['-p', __name__] + options)
+            with open(f'{self.res_dir}/stdout.log',
+                      'w', encoding='utf-8') as output_file:
+                output_file.write(output.getvalue())
+            self.__logger.info(
+                "\n\n %s \n",
+                output.getvalue().splitlines()[-1].replace('=', ''))
+            self.details = Pytest.tests
+            if Pytest.passed + Pytest.failed:
+                self.result = Pytest.passed / (
+                    Pytest.passed + Pytest.failed) * 100
+            status = self.EX_OK
+        except Exception:  # pylint: # pylint: disable=broad-except
+            self.__logger.exception("Cannot execute pytest")
+        self.stop_time = time.time()
+        return status