Enforce self.details as a collection
[functest-xtesting.git] / xtesting / core / pytest.py
1 #!/usr/bin/env python
2
3 # Copyright (c) 2023 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 classes required to run any Pytest suites."""
11
12 import contextlib
13 import io
14 import logging
15 import time
16
17 import pytest
18
19 from xtesting.core import testcase
20
21
22 @pytest.hookimpl(hookwrapper=True)
23 def pytest_runtest_makereport(item, call):
24     """Collect all Pytest results"""
25     # pylint: disable=unused-argument
26     yreport = yield
27     report = yreport.get_result()
28     if report.when == 'call':
29         test = {"name": report.nodeid, "status": report.outcome.upper()}
30         if report.passed:
31             Pytest.passed += 1
32         elif report.failed:
33             Pytest.failed += 1
34             test['failure'] = report.longreprtext
35         Pytest.tests.append(test)
36
37
38 class Pytest(testcase.TestCase):
39
40     """Pytest driver
41
42     The pytest driver can be used on every test set written for pytest.
43     Given a pytest package that is launched with the command:
44
45     .. code-block:: shell
46
47         pytest --opt1 arg1 --opt2 testdir
48
49     it can be executed by xtesting with the following testcase.yaml:
50
51     .. code-block:: yaml
52
53         run:
54           name: pytest
55           args:
56             opt1: arg1
57             opt2: arg2
58     """
59
60     __logger = logging.getLogger(__name__)
61     tests = []
62     passed = 0
63     failed = 0
64
65     def run(self, **kwargs):
66         # parsing args
67         #  - 'dir' is mandatory
68         #  - 'options' is an optional list or a dict flatten to a list
69         status = self.EX_RUN_ERROR
70         self.start_time = time.time()
71         try:
72             pydir = kwargs.pop('dir')
73             options = kwargs.pop('options', {})
74             options['html'] = f'{self.res_dir}/results.html'
75             options['junitxml'] = f'{self.res_dir}/results.xml'
76             if 'tb' not in options:
77                 options['tb'] = 'no'
78             options = [
79                 str(item) for opt in zip(
80                     [f'--{k}' if len(str(k)) > 1 else
81                         f'-{k}' for k in options.keys()],
82                     options.values())
83                 for item in opt if item is not None]
84             with contextlib.redirect_stdout(io.StringIO()) as output:
85                 pytest.main(
86                     args=[pydir] + ['-p', __name__] + options)
87             with open(f'{self.res_dir}/stdout.log',
88                       'w', encoding='utf-8') as output_file:
89                 output_file.write(output.getvalue())
90             self.__logger.info(
91                 "\n\n %s \n",
92                 output.getvalue().splitlines()[-1].replace('=', ''))
93             self.details["tests"] = Pytest.tests
94             if Pytest.passed + Pytest.failed:
95                 self.result = Pytest.passed / (
96                     Pytest.passed + Pytest.failed) * 100
97             status = self.EX_OK
98         except Exception:  # pylint: # pylint: disable=broad-except
99             self.__logger.exception("Cannot execute pytest")
100         self.stop_time = time.time()
101         return status