diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index b49dbd7..f94b2a7 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -9,7 +9,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} @@ -59,15 +59,15 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.7' + python-version: '3.11' - name: Install dependencies run: | python -m pip install --upgrade pip - pip install setuptools wheel twine + pip install twine build - name: Build and publish env: TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | - python setup.py sdist bdist_wheel + python -m build twine upload dist/* diff --git a/.gitignore b/.gitignore index a18c62a..5c88b82 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,7 @@ wheels/ .installed.cfg *.egg MANIFEST -mlx/__xunit2rst_version__.py +mlx/xunit2rst/__version__.py # PyInstaller # Usually these files are written by a python script from a template diff --git a/MANIFEST.in b/MANIFEST.in index 1e8aedc..d4e0ca3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,8 +7,9 @@ recursive-include doc *.yml recursive-include doc Makefile include tox.ini recursive-include mlx *.mako +recursive-include mlx *.css -exclude mlx/__xunit2rst_version__.py +exclude mlx/xunit2rst/__version__.py exclude .pylintrc exclude doc/source/generated/* @@ -16,5 +17,3 @@ recursive-include tests *.py recursive-include tests *.rst recursive-include tests *.xml exclude tests/test_out/* - -include doc/source/_static/xunit2rst.css diff --git a/doc/source/conf.py b/doc/source/conf.py index 9a30a37..2f751f7 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -5,9 +5,11 @@ # https://www.sphinx-doc.org/en/master/usage/configuration.html import os -import mlx.traceability -from pkg_resources import get_distribution +from importlib.metadata import distribution +from pathlib import Path +import mlx.traceability +import mlx.xunit2rst # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the @@ -21,16 +23,16 @@ authors = ['Bavo Van Achte', 'Jasper Craeghs'] # The full version, including alpha/beta/rc tags -release = get_distribution('mlx.xunit2rst').version +release = distribution('mlx.xunit2rst').version version = '.'.join(release.split('.')[:2]) latex_documents = [ - ('index', 'xunit2rst.tex', 'Script to convert .robot files to .rst files with traceable items', + ('index', 'xunit2rst.tex', 'Script to convert xUnit files to .rst files with traceable items', ' \\and '.join(authors), 'manual', True), ] man_pages = [ - ('index', 'xunit2rst', 'Script to convert .robot files to .rst files with traceable items', + ('index', 'xunit2rst', 'Script to convert xUnit files to .rst files with traceable items', authors, 1) ] @@ -41,7 +43,7 @@ # dir menu entry, description, category) texinfo_documents = [ ('index', 'xunit2rst', 'xunit2rst conversion script', '@*'.join(authors), 'xunit2rst', - 'Script to convert .robot files to .rst files with traceable items.', 'Miscellaneous'), + 'Script to convert xUnit files to .rst files with traceable items.', 'Miscellaneous'), ] # -- General configuration --------------------------------------------------- @@ -114,17 +116,18 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [os.path.join(os.path.dirname(mlx.traceability.__file__), 'assets'), - '_static'] - -# These paths are either relative to html_static_path -# or fully qualified paths (eg. https://...) -html_css_files = [ - 'xunit2rst.css', +html_static_path = [ + str(Path(mlx.traceability.__file__).parent / 'assets'), + str(Path(mlx.xunit2rst.__file__).parent / 'assets'), ] traceability_render_relationship_per_item = True + def setup(app): + # Color Test Results + app.add_css_file('xunit2rst.css') + + # To demo --only input argument if os.environ.get('LAYER', 'FLASH') == 'FLASH': tags.add('FLASH') diff --git a/mlx/__init__.py b/mlx/__init__.py deleted file mode 100644 index de40ea7..0000000 --- a/mlx/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__import__('pkg_resources').declare_namespace(__name__) diff --git a/mlx/xunit2rst/__init__.py b/mlx/xunit2rst/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mlx/__main__.py b/mlx/xunit2rst/__main__.py similarity index 100% rename from mlx/__main__.py rename to mlx/xunit2rst/__main__.py diff --git a/doc/source/_static/xunit2rst.css b/mlx/xunit2rst/assets/xunit2rst.css similarity index 100% rename from doc/source/_static/xunit2rst.css rename to mlx/xunit2rst/assets/xunit2rst.css diff --git a/mlx/xunit2rst.mako b/mlx/xunit2rst/xunit2rst.mako similarity index 100% rename from mlx/xunit2rst.mako rename to mlx/xunit2rst/xunit2rst.mako diff --git a/mlx/xunit2rst.py b/mlx/xunit2rst/xunit2rst.py similarity index 98% rename from mlx/xunit2rst.py rename to mlx/xunit2rst/xunit2rst.py index 75acfb2..0e5d0fc 100644 --- a/mlx/xunit2rst.py +++ b/mlx/xunit2rst/xunit2rst.py @@ -3,12 +3,12 @@ import logging import xml.etree.ElementTree as ET from collections import namedtuple +from importlib.metadata import distribution, PackageNotFoundError from pathlib import Path from textwrap import indent from mako.exceptions import RichTraceback from mako.template import Template -from pkg_resources import DistributionNotFound, require from ruamel.yaml import YAML TraceableInfo = namedtuple("TraceableInfo", ['matrix_prefix', 'type', 'header_prefix']) @@ -229,8 +229,8 @@ def verify_prefix_set(prefix_set, prefix, type_): def create_parser(): """ Creates and returns the ArgumentParser instance to be used """ try: - version = require('mlx.xunit2rst')[0].version - except DistributionNotFound: + version = distribution('mlx.xunitrst').version + except PackageNotFoundError: version = '0.0.0.dev' arg_parser = argparse.ArgumentParser() arg_parser.add_argument('-i', '--input', diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d9adb8c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools-scm>=6.0.0", "setuptools>=69.0.3"] +build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index dd10d6d..cf5c428 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -from setuptools import setup, find_packages +from setuptools import find_namespace_packages, setup requires = [ 'mako', @@ -8,9 +8,8 @@ setup( name='mlx.xunit2rst', use_scm_version={ - 'write_to': 'mlx/__xunit2rst_version__.py' + 'write_to': 'mlx/xunit2rst/__version__.py' }, - setup_requires=['setuptools-scm>=6.0.0'], url='https://github.com/melexis/xunit2rst', license='Apache License Version 2.0', author='JasperCraeghs', @@ -27,27 +26,30 @@ 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', 'Topic :: Documentation', 'Topic :: Documentation :: Sphinx', 'Topic :: Utilities', ], platforms='any', - packages=find_packages(exclude=['tests', 'doc']), - package_data={'mlx.xunit2rst': ['mlx/*.mako']}, + packages=find_namespace_packages(where='.'), + package_dir={"": "."}, + package_data={ + 'mlx.xunit2rst': ['*.mako'], + 'mlx.xunit2rst.assets': ['*.css'] + }, include_package_data=True, install_requires=requires, - python_requires='>=3.7', - namespace_packages=['mlx'], + python_requires='>=3.8', keywords=['xUnit', 'JUnit', 'XML', 'reStructuredText', 'sphinx', 'rst', 'testing', 'traceability', 'documentation'], entry_points={ 'console_scripts': [ - 'mlx.xunit2rst = mlx.xunit2rst:main', - 'xunit2rst = mlx.xunit2rst:main', + 'mlx.xunit2rst = mlx.xunit2rst.xunit2rst:main', + 'xunit2rst = mlx.xunit2rst.xunit2rst:main', ] }, ) diff --git a/tests/acceptance_test.py b/tests/acceptance_test.py index d598afc..b70df25 100644 --- a/tests/acceptance_test.py +++ b/tests/acceptance_test.py @@ -8,7 +8,7 @@ import unittest from pathlib import Path -from mlx.xunit2rst import create_parser, generate_xunit_to_rst +from mlx.xunit2rst import xunit2rst as dut TOP_DIR = Path(__file__).parents[1] TEST_OUT_DIR = Path(__file__).parent / 'test_out' @@ -18,7 +18,7 @@ def xunit2rst_check(input_xml, output_rst, itemize_suites=False, prefix='', trim=False, type_=None, failures=False, log_file='', add_links=False): ''' Helper function for testing whether mlx.xunit2rst produces the expected output ''' - arg_parser = create_parser() + arg_parser = dut.create_parser() command = ['-i', input_xml, '-o', output_rst] if itemize_suites: command.append('--itemize-suites') @@ -37,7 +37,7 @@ def xunit2rst_check(input_xml, output_rst, itemize_suites=False, prefix='', trim print(command) args = arg_parser.parse_args(command) - generate_xunit_to_rst( + dut.generate_xunit_to_rst( args.input_file, args.rst_output_file, args.itemize_suites, diff --git a/tests/mako_test.py b/tests/mako_test.py index db92083..127e034 100644 --- a/tests/mako_test.py +++ b/tests/mako_test.py @@ -4,7 +4,7 @@ from pathlib import Path from unittest import TestCase -from mlx.xunit2rst import ITEST, render_template +from mlx.xunit2rst import xunit2rst as dut TEST_OUT_DIR = Path(__file__).parent / 'test_out' @@ -16,13 +16,13 @@ def test_mako_error_handling(self): variable ''' kwargs = { 'report_name': 'my_report', - 'info': ITEST, + 'info': dut.ITEST, 'prefix': 'MAKO_TEST-', } test_case = TestCase() with test_case.assertLogs() as log_cm: with self.assertRaises(TypeError): - render_template((TEST_OUT_DIR / 'never_created_file.rst'), **kwargs) + dut.render_template((TEST_OUT_DIR / 'never_created_file.rst'), **kwargs) test_case.assertIn('Exception raised in Mako template, which will be re-raised after logging line info:', log_cm.output[0]) test_case.assertIn('File ', log_cm.output[-1]) diff --git a/tests/prefix_test.py b/tests/prefix_test.py index cd61534..7753ca4 100644 --- a/tests/prefix_test.py +++ b/tests/prefix_test.py @@ -3,7 +3,7 @@ import unittest from pathlib import Path -from mlx.xunit2rst import build_prefix_and_set, parse_xunit_root, verify_prefix_set, ITEST, UTEST, QTEST +from mlx.xunit2rst import xunit2rst as dut TEST_IN_DIR = Path(__file__).parent / 'test_in' @@ -11,37 +11,37 @@ class TestPrefix(unittest.TestCase): def test_build_prefix_and_set_utest_default(self): ''' Use default prefix for unit test reports ''' - test_suites, initial_prefix_set, _ = parse_xunit_root(TEST_IN_DIR / 'utest_my_lib_no_prefix_report.xml') - self.assertEqual(initial_prefix_set, UTEST) + test_suites, initial_prefix_set, _ = dut.parse_xunit_root(TEST_IN_DIR / 'utest_my_lib_no_prefix_report.xml') + self.assertEqual(initial_prefix_set, dut.UTEST) - prefix_set, prefix = build_prefix_and_set(test_suites, initial_prefix_set, '', True, None) + prefix_set, prefix = dut.build_prefix_and_set(test_suites, initial_prefix_set, '', True, None) self.assertEqual(prefix_set, initial_prefix_set) self.assertEqual(prefix, 'UTEST-') def test_build_prefix_and_set_itest_default(self): ''' Use default prefix for integration test reports ''' - test_suites, initial_prefix_set, _ = parse_xunit_root(TEST_IN_DIR / 'itest_report.xml') - self.assertEqual(initial_prefix_set, ITEST) + test_suites, initial_prefix_set, _ = dut.parse_xunit_root(TEST_IN_DIR / 'itest_report.xml') + self.assertEqual(initial_prefix_set, dut.ITEST) - prefix_set, prefix = build_prefix_and_set(test_suites, initial_prefix_set, '', True, None) + prefix_set, prefix = dut.build_prefix_and_set(test_suites, initial_prefix_set, '', True, None) self.assertEqual(prefix_set, initial_prefix_set) self.assertEqual(prefix, 'ITEST-') def test_build_prefix_and_set_from_name(self): ''' Get prefix from element name ''' - test_suites, initial_prefix_set, _ = parse_xunit_root(TEST_IN_DIR / 'utest_my_lib_report.xml') - self.assertEqual(initial_prefix_set, UTEST) + test_suites, initial_prefix_set, _ = dut.parse_xunit_root(TEST_IN_DIR / 'utest_my_lib_report.xml') + self.assertEqual(initial_prefix_set, dut.UTEST) - prefix_set, prefix = build_prefix_and_set(test_suites, initial_prefix_set, '', True, None) + prefix_set, prefix = dut.build_prefix_and_set(test_suites, initial_prefix_set, '', True, None) self.assertEqual(prefix_set, initial_prefix_set) self.assertEqual(prefix, 'UTEST_MY_LIB-') def test_build_prefix_and_set_from_arg(self): ''' Get prefix from input argument `--prefix` and trim suffix of prefix ''' - test_suites, initial_prefix_set, _ = parse_xunit_root(TEST_IN_DIR / 'utest_my_lib_report.xml') - self.assertEqual(initial_prefix_set, UTEST) + test_suites, initial_prefix_set, _ = dut.parse_xunit_root(TEST_IN_DIR / 'utest_my_lib_report.xml') + self.assertEqual(initial_prefix_set, dut.UTEST) - prefix_set, prefix = build_prefix_and_set(test_suites, initial_prefix_set, 'TEST_MY_LIB_-', True, None) + prefix_set, prefix = dut.build_prefix_and_set(test_suites, initial_prefix_set, 'TEST_MY_LIB_-', True, None) self.assertEqual(prefix_set, initial_prefix_set) self.assertEqual(prefix, 'TEST_MY_LIB-') @@ -50,32 +50,32 @@ def test_build_prefix_and_set_from_arg_swap_set(self): Get prefix from input argument `--prefix` and base prefix_set on its first letter. Don't trim suffix of prefix. ''' - test_suites, initial_prefix_set, _ = parse_xunit_root(TEST_IN_DIR / 'itest_report.xml') - self.assertEqual(initial_prefix_set, ITEST) + test_suites, initial_prefix_set, _ = dut.parse_xunit_root(TEST_IN_DIR / 'itest_report.xml') + self.assertEqual(initial_prefix_set, dut.ITEST) - prefix_set, prefix = build_prefix_and_set(test_suites, initial_prefix_set, 'UTEST_MY_LIB_-', False, None) + prefix_set, prefix = dut.build_prefix_and_set(test_suites, initial_prefix_set, 'UTEST_MY_LIB_-', False, None) self.assertNotEqual(prefix_set, initial_prefix_set) - self.assertEqual(prefix_set, UTEST) + self.assertEqual(prefix_set, dut.UTEST) self.assertEqual(prefix, 'UTEST_MY_LIB_-') def test_build_prefix_and_set_priority(self): ''' Argument --type must have the highest priority for determining the correct prefix_set. ''' - test_suites, initial_prefix_set, _ = parse_xunit_root(TEST_IN_DIR / 'utest_my_lib_report.xml') - self.assertEqual(initial_prefix_set, UTEST) + test_suites, initial_prefix_set, _ = dut.parse_xunit_root(TEST_IN_DIR / 'utest_my_lib_report.xml') + self.assertEqual(initial_prefix_set, dut.UTEST) - prefix_set, prefix = build_prefix_and_set(test_suites, initial_prefix_set, 'UTEST_HOWDY-', False, 'i') + prefix_set, prefix = dut.build_prefix_and_set(test_suites, initial_prefix_set, 'UTEST_HOWDY-', False, 'i') self.assertNotEqual(prefix_set, initial_prefix_set) - self.assertEqual(prefix_set, ITEST) + self.assertEqual(prefix_set, dut.ITEST) self.assertEqual(prefix, 'UTEST_HOWDY-') def test_content_files(self): ''' Test the extraction of the content file path ''' - _, _, content_files = parse_xunit_root(TEST_IN_DIR / 'qtest_my_lib_report.xml') + _, _, content_files = dut.parse_xunit_root(TEST_IN_DIR / 'qtest_my_lib_report.xml') self.assertEqual(content_files, {3: Path("../../doc/source/extra_content.yml")}) def test_content_files_no_root(self): ''' Test the extraction of the content file path when the XML has no valid root element ''' - _, _, content_files = parse_xunit_root(TEST_IN_DIR / 'itest_report.xml') + _, _, content_files = dut.parse_xunit_root(TEST_IN_DIR / 'itest_report.xml') self.assertEqual(content_files, {0: Path('./extra_content1.yml')}) def test_verify_prefix_set(self): @@ -84,31 +84,31 @@ def test_verify_prefix_set(self): start with u/i/q (case-insensitive). The prefix argument has the second highest priority. A last resort is to keep the input prefix_set. ''' - self.assertEqual(verify_prefix_set(UTEST, 'UTEST-', 'Itest'), ITEST) - self.assertEqual(verify_prefix_set(UTEST, 'UTEST-', 'i'), ITEST) - self.assertEqual(verify_prefix_set(UTEST, '', 'i'), ITEST) - self.assertEqual(verify_prefix_set(ITEST, 'ITEST-', 'Utest'), UTEST) - self.assertEqual(verify_prefix_set(UTEST, 'ITEST-', 'u'), UTEST) - self.assertEqual(verify_prefix_set(UTEST, 'UTEST-', 'u'), UTEST) - self.assertEqual(verify_prefix_set(UTEST, 'BLAH-', None), UTEST) - self.assertEqual(verify_prefix_set(ITEST, 'BLAH-', None), ITEST) - self.assertEqual(verify_prefix_set(UTEST, 'ITEST-', None), ITEST) - self.assertEqual(verify_prefix_set(ITEST, 'UTEST-', None), UTEST) - self.assertEqual(verify_prefix_set(ITEST, 'QTEST-', None), QTEST) - self.assertEqual(verify_prefix_set(ITEST, 'ITEST-', 'q'), QTEST) - self.assertEqual(verify_prefix_set(UTEST, 'UTEST-', 'Qtest'), QTEST) - self.assertEqual(verify_prefix_set(QTEST, '', 'u'), UTEST) - self.assertEqual(verify_prefix_set(QTEST, '', 'i'), ITEST) - self.assertEqual(verify_prefix_set(QTEST, 'UTEST', 'i'), ITEST) - self.assertEqual(verify_prefix_set(QTEST, 'FOO-', None), QTEST) + self.assertEqual(dut.verify_prefix_set(dut.UTEST, 'UTEST-', 'Itest'), dut.ITEST) + self.assertEqual(dut.verify_prefix_set(dut.UTEST, 'UTEST-', 'i'), dut.ITEST) + self.assertEqual(dut.verify_prefix_set(dut.UTEST, '', 'i'), dut.ITEST) + self.assertEqual(dut.verify_prefix_set(dut.ITEST, 'ITEST-', 'Utest'), dut.UTEST) + self.assertEqual(dut.verify_prefix_set(dut.UTEST, 'ITEST-', 'u'), dut.UTEST) + self.assertEqual(dut.verify_prefix_set(dut.UTEST, 'UTEST-', 'u'), dut.UTEST) + self.assertEqual(dut.verify_prefix_set(dut.UTEST, 'BLAH-', None), dut.UTEST) + self.assertEqual(dut.verify_prefix_set(dut.ITEST, 'BLAH-', None), dut.ITEST) + self.assertEqual(dut.verify_prefix_set(dut.UTEST, 'ITEST-', None), dut.ITEST) + self.assertEqual(dut.verify_prefix_set(dut.ITEST, 'UTEST-', None), dut.UTEST) + self.assertEqual(dut.verify_prefix_set(dut.ITEST, 'QTEST-', None), dut.QTEST) + self.assertEqual(dut.verify_prefix_set(dut.ITEST, 'ITEST-', 'q'), dut.QTEST) + self.assertEqual(dut.verify_prefix_set(dut.UTEST, 'UTEST-', 'Qtest'), dut.QTEST) + self.assertEqual(dut.verify_prefix_set(dut.QTEST, '', 'u'), dut.UTEST) + self.assertEqual(dut.verify_prefix_set(dut.QTEST, '', 'i'), dut.ITEST) + self.assertEqual(dut.verify_prefix_set(dut.QTEST, 'UTEST', 'i'), dut.ITEST) + self.assertEqual(dut.verify_prefix_set(dut.QTEST, 'FOO-', None), dut.QTEST) with self.assertRaises(ValueError): - verify_prefix_set(UTEST, 'UTEST-', 't') + dut.verify_prefix_set(dut.UTEST, 'UTEST-', 't') with self.assertRaises(ValueError): - verify_prefix_set(ITEST, 'ITEST', 't') + dut.verify_prefix_set(dut.ITEST, 'ITEST', 't') with self.assertRaises(ValueError): - verify_prefix_set(ITEST, '', '') + dut.verify_prefix_set(dut.ITEST, '', '') with self.assertRaises(ValueError): - verify_prefix_set(QTEST, '', 't') + dut.verify_prefix_set(dut.QTEST, '', 't') if __name__ == '__main__': diff --git a/tox.ini b/tox.ini index 79b287a..06f44a8 100644 --- a/tox.ini +++ b/tox.ini @@ -2,25 +2,25 @@ envlist = clean, check, - py37, py38, py39, py310, py311 + py38, py39, py310, py311, py312 doc, [gh-actions] python = - 3.7: py37 3.8: py38 3.9: py39 3.10: py310 3.11: py311 + 3.12: py312 [testenv] basepython = py: python3 - py37: {env:TOXPYTHON:python3.7} py38: {env:TOXPYTHON:python3.8} py39: {env:TOXPYTHON:python3.9} py310: {env:TOXPYTHON:python3.10} py311: {env:TOXPYTHON:python3.11} + py312: {env:TOXPYTHON:python3.12} {clean,check,codecov,doc}: python3 passenv = * @@ -28,19 +28,21 @@ usedevelop = false deps = pytest-cov commands= + xunit2rst --help {posargs:py.test --cov=mlx --cov-report=term-missing -vv tests/} [testenv:check] deps = {[testenv]deps} docutils + build twine >= 1.12.0 check-manifest readme-renderer flake8 skip_install = true commands = - python setup.py sdist + python -m build twine check dist/* check-manifest {toxinidir} flake8 mlx tests setup.py @@ -50,7 +52,7 @@ deps= {[testenv]deps} sphinx_rtd_theme mlx.traceability >= 4.3.2 - mlx.warnings >= 1.2.0 + mlx.warnings >= 5.0.0 mlx.robot2rst >= 3.3.0, <4 robotframework >= 5.0.1 sphinx_selective_exclude >= 1.0.3