Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
home:crameleon:misc
python-junitparser
_service:obs_scm:junitparser-3.1.2.obscpio
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:obs_scm:junitparser-3.1.2.obscpio of Package python-junitparser
07070100000000000081A400000000000000000000000165D5C41600000123000000000000000000000000000000000000001E00000000junitparser-3.1.2/.coveragerc[run] omit = # Don't complain if non-runnable code isn't run: */__main__.py [report] exclude_lines = # Have to re-enable the standard pragma \#\s*pragma: no cover # Don't complain if non-runnable code isn't run: ^if __name__ == ['"]__main__['"]:$ ^\s*if False: 07070100000001000041ED00000000000000000000000365D5C41600000000000000000000000000000000000000000000001A00000000junitparser-3.1.2/.github07070100000002000081A400000000000000000000000165D5C416000001F5000000000000000000000000000000000000002900000000junitparser-3.1.2/.github/dependabot.yml# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "daily" 07070100000003000041ED00000000000000000000000265D5C41600000000000000000000000000000000000000000000002400000000junitparser-3.1.2/.github/workflows07070100000004000081A400000000000000000000000165D5C41600000A53000000000000000000000000000000000000002E00000000junitparser-3.1.2/.github/workflows/build.ymlname: build on: push: branches: ["master"] tags: - "*" pull_request: branches: ["master"] workflow_dispatch: jobs: build-test: # Python version < 3.7 requires ubuntu-20.04 runs-on: ubuntu-20.04 strategy: matrix: python-version: ["3.6", "3.x"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Setup locales run: | sudo locale-gen en_US.UTF-8 sudo locale-gen de_DE.UTF-8 sudo update-locale - name: Install dependencies run: | python -m pip install --upgrade pip pip install pytest if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Test run: | pytest - name: Test with lxml run: | pip install lxml pytest coverage-lint: needs: build-test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.x uses: actions/setup-python@v4 with: python-version: 3.x - name: Setup locales run: | sudo locale-gen en_US.UTF-8 sudo locale-gen de_DE.UTF-8 sudo update-locale - name: Install dependencies run: | python -m pip install --upgrade pip pip install lxml flake8 pytest coverage if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test and coverage run: | coverage run -m pytest bash <(curl -s https://codecov.io/bash) package-publish: needs: coverage-lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Set up Python 3.x uses: actions/setup-python@v4 with: python-version: 3.x - name: Build packages run: | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi pip install setuptools wheel python setup.py sdist bdist_wheel - name: Publish to pypi if: startsWith(github.ref, 'refs/tags') uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.pypi_password }} 07070100000005000081A400000000000000000000000165D5C4160000033D000000000000000000000000000000000000001D00000000junitparser-3.1.2/.gitignore# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *,cover # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ docs/api_generated/ # PyBuilder target/ # IDE Specific .vs/ *.pyproj *.sln .vscode/ .idea/ # Virtual env venv/ .venv/ .DS_Store .pytest_cache/ 07070100000006000081A400000000000000000000000165D5C416000001D5000000000000000000000000000000000000002300000000junitparser-3.1.2/.readthedocs.yml# ReadTheDocs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details version: 2 build: os: ubuntu-22.04 tools: python: "3.11" apt_packages: - graphviz jobs: pre_build: - sphinx-apidoc --force --output-dir docs/api_generated/ --doc-project "API" --separate junitparser python: install: - requirements: docs/requirements.txt - method: pip path: . sphinx: configuration: docs/conf.py 07070100000007000081A400000000000000000000000165D5C41600000FD8000000000000000000000000000000000000001F00000000junitparser-3.1.2/CHANGELOG.md# Changelog ## [3.1.2] - 2024-02-21 ### Fixed - Excluded `TestCase` and `TestSuite` from pytest discovery. Thanks to @kurtsansom ### Added - More type annotations. ## [3.1.1] - 2023-12-26 ### Fixed - Sphinx documentation, thanks to @cmarqu - type check by adding `py.typed` file. ### Other - Tests converted to `pytest` ## [3.1.0] - 2023-04-22 ### Added - Support for different schemas. - xunit2 flavor support - Type hints ## [3.0.0] - 2023-04-20 ### Breaking Python 2 is no longer supported. Version 2.x will keep supporting py2 and it will be maintained as long as possible, though no new features will be added. ## [2.8.0] - 2022-08-19 ### Added - `--suite-name` parameter for merging xmls with cli. Thanks to @yusijs ## [2.7.0] - 2022-06-25 ### Added - `fromroot` class method to create object from a root element. Thanks to @EnricoMi ## [2.6.0] - 2022-05-31 ### Added - `verify` subcommand. Thanks to @teake ## [2.5.0] - 2022-03-14 ### Added - Bulk add test cases. Thanks to @Goblenus - ## [2.4.3] - 2022-03-14 ### Fixed - Custom element not properly initiated according to the readme example. ## [2.4.2] - 2022-01-08 ### Fixed - Fix the package build for a specific install method ## [2.4.1] - 2021-12-31 ### Fixed - Parameter typo in the cli. Thanks to @petterssonandreas - ## [2.4.0] - 2021-12-30 This release addresses issues and PRs by @markgras. ### Fixed - Parameter typo in function `write_xml()`. - Properly closes file in `setup.py`. ### Enhancement - Use generators in stead of lists in a few occasions. ## [2.3.0] - 2021-11-20 ### Possibly Breaking - The time value now has a precision of 3 (#72). Thanks to @bryan-hunt. ## [2.2.0] - 2021-11-20 ### Fixed - Unescaping attribute values (#71). ## [2.1.1] - 2021-05-31 ### Fixed - CLI broken due to a quotation mark. ## [2.1.0] - 2021-05-30 ### Fixed - Should not have used default sys locale to parse numbers. Thanks to @EnricoMi ### Added - Merge parameter enhancement: output to console if output file name is set to "-" - Support testcase tags inside testcase tags. Thanks to @EnricoMi ## [2.0.0] - 2020-11-28 ### Breaking - `TestCase.result` is now a list instead of a single item. `Failure`, `Skip`, etc. are all treated as results. ### Added - `TestCase` constructor supports `time` and `classname` as params. - `Result` object supports `text` attribute. - Handles localized timestamps. Thanks to @ppalucha ## [1.6.3] - 2020-11-24 ### Fixed - `JunitXML.fromstring()` now handles various inputs. ## [1.6.2] - 2020-10-29 ### Changed - Exclude test file from package. Thanks to @Ishinomori ## [1.6.1] - 2020-10-29 ### Changed - Update licence and readme ## [1.6.0] - 2020-10-28 ### Added - Custom parser option for `fromfile` ## [1.5.1] - 2020-10-28 ### Fixed - #47 result error when running merge in cli ## [1.5.0] - 2020-10-26 ### Added - Runs with `python -m junitparser ...` Thanks to @jkowalleck - `junitparser merge --glob` also by @jkowalleck ## [1.4.2] - 2020-10-21 ### Fixed - command line versioning ## [1.4.1] - 2019-12-26 ### Fixed - A conditional statement error. Thanks to @dries007 ## [1.4.0] - 2019-10-28 ### Fixed - Retain suite name when merging test suites. Thanks to @alde - Add skipped member to JUnitXml. Thanks to @arichardson ## [1.3.5] - 2019-09-23 ### Fixed - Prevented an exception when test result is None. Thanks to @patbro ## [1.3.4] - 2019-09-15 ### Fixed - Performance improvement for file merging. Thanks to @arichardson ## [1.3.3] - 2019-09-02 ### Fixed - Ensure htmlentities are used in attributes. Thanks to @alde ## [1.3.1] - 2019-02-11 ### Fixed - Install with --no-binary ## [1.3.0] - 2019-02-11 ### Fixed - Merging test files doesn't merge test counts. Thanks to @andydawkins ## [1.2.0] ### Added - Support for reading custom attributes and elements. Thanks to @arewm ## [1.1.0] ### Added - a command to merge xml files. Thanks to @imsuwj ## [1.0.0] ### Added - Python 2 support. Thanks to @SteinHeselmans ## [0.9.0] ### Changed * Supports xmls with ``testcase`` as root node. * First beta release.07070100000008000081A400000000000000000000000165D5C41600000240000000000000000000000000000000000000001A00000000junitparser-3.1.2/LICENSECopyright 2020 Joel Wang Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.07070100000009000081A400000000000000000000000165D5C4160000211E000000000000000000000000000000000000001D00000000junitparser-3.1.2/README.rstjunitparser -- Pythonic JUnit/xUnit Result XML Parser ====================================================== .. image:: https://github.com/weiwei/junitparser/workflows/build/badge.svg?branch=master :target: https://github.com/weiwei/junitparser/actions .. image:: https://codecov.io/gh/weiwei/junitparser/branch/master/graph/badge.svg?token=UotlfRXNnK :target: https://codecov.io/gh/weiwei/junitparser junitparser handles JUnit/xUnit Result XML files. Use it to parse and manipulate existing Result XML files, or create new JUnit/xUnit result XMLs from scratch. Features -------- * Parse or modify existing JUnit/xUnit XML files. * Parse or modify non-standard or customized JUnit/xUnit XML files, by monkey patching existing element definitions. * Create JUnit/xUnit test results from scratch. * Merge test result XML files. * Specify XML parser. For example you can use lxml to speed things up. * Invoke from command line, or `python -m junitparser` * Python 2 and 3 support (As of Nov 2020, 1/4 of the users are still on Python 2, so there is no plan to drop Python 2 support) Note on version 2 ----------------- Version 2 improved support for pytest result XML files by fixing a few issues, notably that there could be multiple <Failure> or <Error> entries. There is a breaking change that ``TestCase.result`` is now a list instead of a single item. If you are using this attribute, please update your code accordingly. Installation ------------- :: pip install junitparser Usage ----- You should be relatively familiar with the Junit XML format. If not, run ``pydoc`` on the exposed classes and functions to see how it's structured. Create Junit XML format reports from scratch ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You have some test result data, and you want to convert them into junit.xml format. .. code-block:: python from junitparser import TestCase, TestSuite, JUnitXml, Skipped, Error # Create cases case1 = TestCase('case1', 'class.name', 0.5) # params are optional case1.classname = "modified.class.name" # specify or change case attrs case1.result = [Skipped()] # You can have a list of results case2 = TestCase('case2') case2.result = [Error('Example error message', 'the_error_type')] # Create suite and add cases suite = TestSuite('suite1') suite.add_property('build', '55') suite.add_testcase(case1) suite.add_testcase(case2) suite.remove_testcase(case2) #Bulk add cases to suite case3 = TestCase('case3') case4 = TestCase('case4') suite.add_testcases([case3, case4]) # Add suite to JunitXml xml = JUnitXml() xml.add_testsuite(suite) xml.write('junit.xml') Read and manipulate existing JUnit/xUnit XML files ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You have some existing junit.xml files, and you want to modify the content. .. code-block:: python from junitparser import JUnitXml xml = JUnitXml.fromfile('/path/to/junit.xml') for suite in xml: # handle suites for case in suite: # handle cases xml.write() # Writes back to file It is also possible to use a custom parser. For example lxml provides a plethora of parsing options_. We can use them this way: .. code-block:: python from lxml.etree import XMLParser, parse from junitparser import JUnitXml def parse_func(file_path): xml_parser = XMLParser(huge_tree=True) return parse(file_path, xml_parser) xml = JUnitXml.fromfile('/path/to/junit.xml', parse_func) # process xml... .. _options: https://lxml.de/api/lxml.etree.XMLParser-class.html Merge XML files ~~~~~~~~~~~~~~~ You have two or more XML files, and you want to merge them into one. .. code-block:: python from junitparser import JUnitXml xml1 = JUnitXml.fromfile('/path/to/junit1.xml') xml2 = JUnitXml.fromfile('/path/to/junit2.xml') newxml = xml1 + xml2 # Alternatively, merge in place xml1 += xml2 Note that it won't check for duplicate entries. You need to deal with them on your own. Schema Support ~~~~~~~~~~~~~~~ By default junitparser supports the schema of windyroad_, which is a relatively simple schema. .. _windyroad: https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd Junitparser also support extra schemas: .. code-block:: python from junitparser.xunit2 import JUnitParser, TestCase, TestSuite, \ RerunFailure # These classes are redefined to support extra properties and attributes # of the xunit2 schema. suite = TestSuite("mySuite") suite.system_err = "System err" # xunit2 specific property case = TestCase("myCase") rerun_failure = RerunFailure("Not found", "404") # case property rerun_failure.stack_trace = "Stack" rerun_failure.system_err = "E404" rerun_failure.system_out = "NOT FOUND" case.add_rerun_result(rerun_failure) Currently supported schemas including: - xunit2_, supported by pytest, Erlang/OTP, Maven Surefire, CppTest, etc. .. _xunit2: https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd PRs are welcome to support more schemas. Create XML with custom attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You want to use an attribute that is not supported by default. .. code-block:: python from junitparser import TestCase, Attr, IntAttr, FloatAttr # Add the custom attribute TestCase.id = IntAttr('id') TestCase.rate = FloatAttr('rate') TestCase.custom = Attr('custom') case = TestCase() case.id = 123 case.rate = 0.95 case.custom = 'foobar' Handling XML with custom element ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There may be once in 1000 years you want to it this way, but anyways. Suppose you want to add element CustomElement to TestCase. .. code-block:: python from junitparser import Element, Attr, TestSuite # Create the new element by subclassing Element, # and add custom attributes to it. class CustomElement(Element): _tag = 'custom' foo = Attr() bar = Attr() testcase = TestCase() custom = CustomElement() testcase.append(custom) # To find a single sub-element: testcase.child(CustomElement) # To iterate over custom elements: for custom in testcase.iterchildren(CustomElement): ... # Do things with custom element Handling custom XML attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Say you have some data stored in the XML as custom attributes and you want to read them out: .. code-block:: python from junitparser import TestCase, Attr, JUnitXml # Create the new element by subclassing Element or one of its child class, # and add custom attributes to it. class MyTestCase(TestCase): foo = Attr() xml = JUnitXml.fromfile('/path/to/junit.xml') for suite in xml: # handle suites for case in suite: my_case = MyTestCase.fromelem(case) print(my_case.foo) Command Line ------------ .. code-block:: console $ junitparser --help usage: junitparser [-h] [-v] {merge} ... Junitparser CLI helper. positional arguments: {merge} command merge Merge Junit XML format reports with junitparser. verify Return a non-zero exit code if one of the testcases failed or errored. optional arguments: -h, --help show this help message and exit -v, --version show program's version number and exit .. code-block:: console $ junitparser merge --help usage: junitparser merge [-h] [--glob] paths [paths ...] output positional arguments: paths Original XML path(s). output Merged XML Path, setting to "-" will output console optional arguments: -h, --help show this help message and exit --glob Treat original XML path(s) as glob(s). --suite-name SUITE_NAME Name added to <testsuites>. .. code-block:: console $ junitparser verify --help usage: junitparser verify [-h] [--glob] paths [paths ...] positional arguments: paths XML path(s) of reports to verify. optional arguments: -h, --help show this help message and exit --glob Treat original XML path(s) as glob(s). Test ---- The tests are written with python ``unittest``, to run them, use pytest:: pytest test.py Contribute ---------- PRs are welcome! 0707010000000A000041ED00000000000000000000000265D5C41600000000000000000000000000000000000000000000001700000000junitparser-3.1.2/docs0707010000000B000081A400000000000000000000000165D5C416000002C7000000000000000000000000000000000000002000000000junitparser-3.1.2/docs/Makefile# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SOURCEDIR = . BUILDDIR = _build SPHINXAPIDOC = sphinx-apidoc # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXAPIDOC) --force --output-dir api_generated/ --doc-project "API" --separate ../junitparser @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 0707010000000C000081A400000000000000000000000165D5C4160000005A000000000000000000000000000000000000001F00000000junitparser-3.1.2/docs/api.rst.. toctree:: :maxdepth: 2 :hidden: inheritance_diagram api_generated/modules 0707010000000D000081A400000000000000000000000165D5C41600001625000000000000000000000000000000000000001F00000000junitparser-3.1.2/docs/conf.py# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # # This file does only contain a selection of the most common options. For a # full list see the documentation: # http://www.sphinx-doc.org/en/master/config # -- 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 # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys sys.path.insert(0, os.path.abspath("../junitparser")) sys.path.insert(0, os.path.abspath("../")) # -- Project information ----------------------------------------------------- project = "junitparser" copyright = "2019-2024, Joel Wang" author = "Joel Wang" # The short X.Y version version = "" # The full version, including alpha/beta/rc tags release = "" # -- General configuration --------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.inheritance_diagram", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", "sphinx.ext.viewcode", ] intersphinx_mapping = { "python": ("https://docs.python.org/3", None), } # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = ".rst" # The master toctree document. master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # # html_theme_options = {} # 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 = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # The default sidebars (for documents that don't match any pattern) are # defined by theme itself. Builtin themes are using these templates by # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', # 'searchbox.html']``. # # html_sidebars = {} # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. htmlhelp_basename = "junitparserdoc" # -- Options for LaTeX output ------------------------------------------------ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, "junitparser.tex", "junitparser Documentation", "Joel Wang", "manual"), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [(master_doc, "junitparser", "junitparser Documentation", [author], 1)] # -- Options for Texinfo output ---------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ( master_doc, "junitparser", "junitparser Documentation", author, "junitparser", "One line description of project.", "Miscellaneous", ), ] # -- Options for Epub output ------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project # The unique identifier of the text. This can be a ISBN number # or the project homepage. # # epub_identifier = '' # A unique identification for the text. # # epub_uid = '' # A list of files that should not be packed into the epub file. epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- # -- Extra setup for inheritance_diagram directive which uses graphviz --------- graphviz_output_format = "svg" 0707010000000E000081A400000000000000000000000165D5C4160000011A000000000000000000000000000000000000002100000000junitparser-3.1.2/docs/index.rst.. include:: ../README.rst .. NOTE: the "index" self-reference in the toctree below produces a warning but makes sure it shows up in the left-side TOC like all the other pages .. toctree:: :maxdepth: 2 :hidden: index api genindex modindex search 0707010000000F000081A400000000000000000000000165D5C4160000008E000000000000000000000000000000000000002F00000000junitparser-3.1.2/docs/inheritance_diagram.rst************************* Class Inheritance Diagram ************************* .. inheritance-diagram:: junitparser.junitparser :parts: -1 07070100000010000081A400000000000000000000000165D5C41600000040000000000000000000000000000000000000002800000000junitparser-3.1.2/docs/requirements.txt# Python dependencies to build the documentation Sphinx ~= 7.2 07070100000011000041ED00000000000000000000000265D5C41600000000000000000000000000000000000000000000001E00000000junitparser-3.1.2/junitparser07070100000012000081A400000000000000000000000165D5C416000000E4000000000000000000000000000000000000002A00000000junitparser-3.1.2/junitparser/__init__.pyfrom .junitparser import ( JUnitXmlError, Attr, Element, JUnitXml, TestSuite, Property, Skipped, Failure, Error, TestCase, Properties, IntAttr, FloatAttr, ) version = "3.1.2" 07070100000013000081A400000000000000000000000165D5C41600000049000000000000000000000000000000000000002A00000000junitparser-3.1.2/junitparser/__main__.pyimport sys from .cli import main sys.exit(main(prog_name=__package__)) 07070100000014000081A400000000000000000000000165D5C41600000C23000000000000000000000000000000000000002500000000junitparser-3.1.2/junitparser/cli.pyfrom argparse import ArgumentParser from glob import iglob from itertools import chain from . import JUnitXml, version def merge(paths, output, suite_name): """Merge XML report.""" result = JUnitXml() for path in paths: result += JUnitXml.fromfile(path) result.update_statistics() if suite_name: result.name = suite_name result.write(output, to_console=output == "-") return 0 def verify(paths): """Verify if none of the testcases failed or errored.""" # We could grab the number of failures and errors from the statistics of the root element # or from the test suites elements, but those attributes are not guaranteed to be present # or correct. So we'll just loop over all the testcases. for path in paths: xml = JUnitXml.fromfile(path) for suite in xml: for case in suite: if not case.is_passed and not case.is_skipped: return 1 return 0 def _parser(prog_name=None): # pragma: no cover """Create the CLI arg parser.""" parser = ArgumentParser(description="Junitparser CLI helper.", prog=prog_name) parser.add_argument( "-v", "--version", action="version", version="%(prog)s " + version ) command_parser = parser.add_subparsers(dest="command", help="command") command_parser.required = True # command: merge merge_parser = command_parser.add_parser( "merge", help="Merge JUnit XML format reports with junitparser." ) merge_parser.add_argument( "--glob", help="Treat original XML path(s) as glob(s).", dest="paths_are_globs", action="store_true", default=False, ) merge_parser.add_argument("paths", nargs="+", help="Original XML path(s).") merge_parser.add_argument( "output", help='Merged XML Path, setting to "-" will output to the console' ) merge_parser.add_argument( "--suite-name", help="Name added to <testsuites>.", ) # command: verify merge_parser = command_parser.add_parser( "verify", help="Return a non-zero exit code if one of the testcases failed or errored.", ) merge_parser.add_argument( "--glob", help="Treat original XML path(s) as glob(s).", dest="paths_are_globs", action="store_true", default=False, ) merge_parser.add_argument( "paths", nargs="+", help="XML path(s) of reports to verify." ) return parser def main(args=None, prog_name=None): """CLI's main runner.""" args = args or _parser(prog_name=prog_name).parse_args() if args.command == "merge": return merge( chain.from_iterable(iglob(path) for path in args.paths) if args.paths_are_globs else args.paths, args.output, args.suite_name, ) if args.command == "verify": return verify( chain.from_iterable(iglob(path) for path in args.paths) if args.paths_are_globs else args.paths ) return 255 07070100000015000081A400000000000000000000000165D5C4160000555B000000000000000000000000000000000000002D00000000junitparser-3.1.2/junitparser/junitparser.py""" junitparser is a JUnit/xUnit Result XML Parser. Use it to parse and manipulate existing Result XML files, or create new JUnit/xUnit result XMLs from scratch. Reference schema: https://github.com/windyroad/JUnit-Schema/blob/master/JUnit.xsd This, according to the document, is Apache Ant's JUnit output. See the documentation for other supported schemas. """ import itertools from copy import deepcopy from typing import List try: from lxml import etree except ImportError: from xml.etree import ElementTree as etree def write_xml(obj, filepath=None, pretty=False, to_console=False): tree = etree.ElementTree(obj._elem) if filepath is None: filepath = obj.filepath if filepath is None: raise JUnitXmlError("Missing filepath argument.") if pretty: from xml.dom.minidom import parseString text = etree.tostring(obj._elem) xml = parseString(text) # nosec content = xml.toprettyxml(encoding="utf-8") if to_console: print(content) else: with open(filepath, "wb") as xmlfile: xmlfile.write(content) else: if to_console: print( etree.tostring( obj._elem, encoding="utf-8", xml_declaration=True ).decode("utf-8") ) else: tree.write(filepath, encoding="utf-8", xml_declaration=True) class JUnitXmlError(Exception): """Exception for JUnit XML related errors.""" class Attr(object): """An attribute for an XML element. By default they are all string values. To support different value types, inherit this class and define your own methods. Also see: :class:`IntAttr`, :class:`FloatAttr`. """ def __init__(self, name: str = None): self.name = name def __get__(self, instance, cls): """Get value from attribute, return ``None`` if attribute doesn't exist.""" return instance._elem.attrib.get(self.name) def __set__(self, instance, value: str): """Sets XML element attribute.""" if value is not None: instance._elem.attrib[self.name] = str(value) class IntAttr(Attr): """An integer attribute for an XML element. This class is used internally for counting testcases, but you could use it for any specific purpose. """ def __get__(self, instance, cls): result = super().__get__(instance, cls) if result is None and isinstance(instance, (JUnitXml, TestSuite)): instance.update_statistics() result = super().__get__(instance, cls) return int(result) if result else None def __set__(self, instance, value: int): if not isinstance(value, int): raise TypeError("Expected integer value.") super().__set__(instance, value) class FloatAttr(Attr): """A float attribute for an XML element. This class is used internally for counting test durations, but you could use it for any specific purpose. """ def __get__(self, instance, cls): result = super().__get__(instance, cls) if result is None and isinstance(instance, (JUnitXml, TestSuite)): instance.update_statistics() result = super().__get__(instance, cls) return float(result.replace(",", "")) if result else None def __set__(self, instance, value: float): if not (isinstance(value, float) or isinstance(value, int)): raise TypeError("Expected float value.") super().__set__(instance, value) def attributed(cls): """Decorator to read XML element attribute name from class attribute.""" for key, value in vars(cls).items(): if isinstance(value, Attr): value.name = key return cls class junitxml(type): """Metaclass to decorate the XML class.""" def __new__(meta, name, bases, methods): cls = super(junitxml, meta).__new__(meta, name, bases, methods) cls = attributed(cls) return cls class Element(metaclass=junitxml): """Base class for all JUnit XML elements.""" def __init__(self, name: str = None): if not name: name = self._tag self._elem = etree.Element(name) def __hash__(self): return hash(etree.tostring(self._elem)) def __repr__(self): tag = self._elem.tag keys = sorted(self._elem.attrib.keys()) if keys: attrs_str = " ".join( '%s="%s"' % (key, self._elem.attrib[key]) for key in keys ) return """<Element '%s' %s>""" % (tag, attrs_str) return """<Element '%s'>""" % tag def append(self, sub_elem): """Add the element subelement to the end of this elements internal list of subelements. """ self._elem.append(sub_elem._elem) def extend(self, sub_elems): """Add elements subelement to the end of this elements internal list of subelements. """ self._elem.extend((sub_elem._elem for sub_elem in sub_elems)) @classmethod def fromstring(cls, text: str): """Construct JUnit object *cls* from XML string *test*.""" instance = cls() instance._elem = etree.fromstring(text) # nosec return instance @classmethod def fromelem(cls, elem): """Construct JUnit objects from an ElementTree element *elem*.""" if elem is None: return instance = cls() if isinstance(elem, Element): instance._elem = elem._elem else: instance._elem = elem return instance def iterchildren(self, Child): """Iterate through specified *Child* type elements.""" elems = self._elem.iterfind(Child._tag) for elem in elems: yield Child.fromelem(elem) def child(self, Child): """Find a single child of specified *Child* type.""" elem = self._elem.find(Child._tag) return Child.fromelem(elem) def remove(self, sub_elem): """Remove subelement *sub_elem*.""" for elem in self._elem.iterfind(sub_elem._tag): child = sub_elem.__class__.fromelem(elem) if child == sub_elem: self._elem.remove(child._elem) def tostring(self): """Convert element to XML string.""" return etree.tostring(self._elem, encoding="utf-8") class Result(Element): """Base class for test result. Attributes: message: Result as message string. type: Message type. """ _tag = None message = Attr() type = Attr() def __init__(self, message: str = None, type_: str = None): super(Result, self).__init__(self._tag) if message: self.message = message if type_: self.type = type_ def __eq__(self, other): return ( self._tag == other._tag and self.type == other.type and self.message == other.message ) @property def text(self): return self._elem.text @text.setter def text(self, value: str): self._elem.text = value class Skipped(Result): """Test result when the case is skipped.""" _tag = "skipped" def __eq__(self, other): return super().__eq__(other) class Failure(Result): """Test result when the case failed.""" _tag = "failure" def __eq__(self, other): return super().__eq__(other) class Error(Result): """Test result when the case has errors during execution.""" _tag = "error" def __eq__(self, other): return super().__eq__(other) POSSIBLE_RESULTS = {Failure, Error, Skipped} class System(Element): """Parent class for :class:`SystemOut` and :class:`SystemErr`. Attributes: text: The output message. """ _tag = "" def __init__(self, content: str = None): super().__init__(self._tag) self.text = content @property def text(self): return self._elem.text @text.setter def text(self, value: str): self._elem.text = value class SystemOut(System): _tag = "system-out" class SystemErr(System): _tag = "system-err" class TestCase(Element): """Object to store a testcase and its result. Attributes: name: Name of the testcase. classname: The parent class of the testcase. time: The time consumed by the testcase. """ _tag = "testcase" name = Attr() classname = Attr() time = FloatAttr() __test__ = False def __init__(self, name: str = None, classname: str = None, time: float = None): super().__init__(self._tag) if name is not None: self.name = name if classname is not None: self.classname = classname if time is not None: self.time = float(time) def __hash__(self): return super().__hash__() def __iter__(self): all_types = set.union(POSSIBLE_RESULTS, {SystemOut}, {SystemErr}) for elem in self._elem.iter(): for entry_type in all_types: if elem.tag == entry_type._tag: yield entry_type.fromelem(elem) def __eq__(self, other): # TODO: May not work correctly if unreliable hash method is used. return hash(self) == hash(other) @property def is_passed(self): """Whether this testcase was a success (i.e. if it isn't skipped, failed, or errored).""" return not self.result @property def is_skipped(self): """Whether this testcase was skipped.""" for r in self.result: if isinstance(r, Skipped): return True return False @property def result(self): """A list of :class:`Failure`, :class:`Skipped`, or :class:`Error` objects.""" results = [] for entry in self: if isinstance(entry, tuple(POSSIBLE_RESULTS)): results.append(entry) return results @result.setter def result(self, value: Result): # First remove all existing results for entry in self.result: if any(isinstance(entry, r) for r in POSSIBLE_RESULTS): self.remove(entry) for entry in value: if any(isinstance(entry, r) for r in POSSIBLE_RESULTS): self.append(entry) @property def system_out(self): """stdout.""" elem = self.child(SystemOut) if elem is not None: return elem.text return None @system_out.setter def system_out(self, value: str): out = self.child(SystemOut) if out is not None: out.text = value else: out = SystemOut(value) self.append(out) @property def system_err(self): """stderr.""" elem = self.child(SystemErr) if elem is not None: return elem.text return None @system_err.setter def system_err(self, value: str): err = self.child(SystemErr) if err is not None: err.text = value else: err = SystemErr(value) self.append(err) class Property(Element): """A key/value pare that's stored in the testsuite. Use it to store anything you find interesting or useful. Attributes: name: The property name. value: The property value. """ _tag = "property" name = Attr() value = Attr() def __init__(self, name: str = None, value: str = None): super().__init__(self._tag) self.name = name self.value = value def __eq__(self, other): return self.name == other.name and self.value == other.value def __ne__(self, other): return not self == other def __lt__(self, other): """Supports sort() for properties.""" return self.name > other.name class Properties(Element): """A list of properties inside a testsuite. See :class:`Property` """ _tag = "properties" def __init__(self): super().__init__(self._tag) def add_property(self, property_: Property): self.append(property_) def __iter__(self): return super().iterchildren(Property) def __eq__(self, other): p1 = list(self) p2 = list(other) p1.sort() p2.sort() if len(p1) != len(p2): return False for e1, e2 in zip(p1, p2): if e1 != e2: return False return True class TestSuite(Element): """The <testsuite> object. Attributes: name: The name of the testsuite. hostname: Name of the test machine. time: Time consumed by the testsuite. timestamp: When the test was run. tests: Total number of tests. failures: Number of failed tests. errors: Number of cases with errors. skipped: Number of skipped cases. """ _tag = "testsuite" name = Attr() hostname = Attr() time = FloatAttr() timestamp = Attr() tests = IntAttr() failures = IntAttr() errors = IntAttr() skipped = IntAttr() __test__ = False def __init__(self, name=None): super().__init__(self._tag) self.name = name self.filepath = None def __iter__(self): return itertools.chain( super().iterchildren(TestCase), (case for suite in super().iterchildren(TestSuite) for case in suite), ) def __len__(self): return len(list(self.__iter__())) def __eq__(self, other): def props_eq(props1, props2): props1 = list(props1) props2 = list(props2) if len(props1) != len(props2): return False props1.sort(key=lambda x: x.name) props2.sort(key=lambda x: x.name) zipped = zip(props1, props2) return all(x == y for x, y in zipped) return ( self.name == other.name and self.hostname == other.hostname and self.timestamp == other.timestamp ) and props_eq(self.properties(), other.properties()) def __add__(self, other): if self == other: # Merge the two testsuites result = deepcopy(self) for case in other: result._add_testcase_no_update_stats(case) for suite in other.testsuites(): result.add_testsuite(suite) result.update_statistics() else: # Create a new test result containing two testsuites result = JUnitXml() result.add_testsuite(self) result.add_testsuite(other) return result def __iadd__(self, other): if self == other: for case in other: self._add_testcase_no_update_stats(case) for suite in other.testsuites(): self.add_testsuite(suite) self.update_statistics() return self result = JUnitXml() result.filepath = self.filepath result.add_testsuite(self) result.add_testsuite(other) return result def remove_testcase(self, testcase: TestCase): """Remove testcase *testcase* from the testsuite.""" for case in self: if case == testcase: super().remove(case) self.update_statistics() def update_statistics(self): """Update test count and test time.""" tests = errors = failures = skipped = 0 time = 0 for case in self: tests += 1 if case.time is not None: time += case.time for entry in case.result: if isinstance(entry, Failure): failures += 1 elif isinstance(entry, Error): errors += 1 elif isinstance(entry, Skipped): skipped += 1 self.tests = tests self.errors = errors self.failures = failures self.skipped = skipped self.time = round(time, 3) def add_property(self, name: str, value: str): """Add a property *name* = *value* to the testsuite. See :class:`Property` and :class:`Properties`. """ props = self.child(Properties) if props is None: props = Properties() self.append(props) prop = Property(name, value) props.add_property(prop) def add_testcase(self, testcase: TestCase): """Add a testcase *testcase* to the testsuite.""" self.append(testcase) self.update_statistics() def add_testcases(self, testcases: List[TestCase]): """Add testcases *testcases* to the testsuite.""" self.extend(testcases) self.update_statistics() def _add_testcase_no_update_stats(self, testcase: TestCase): """Add *testcase* to the testsuite (without updating statistics). For internal use only to avoid quadratic behaviour in merge. """ self.append(testcase) def add_testsuite(self, suite): """Add a testsuite *suite* to the testsuite.""" self.append(suite) def properties(self): """Iterate through all :class:`Property` elements in the testsuite.""" props = self.child(Properties) if props is None: return for prop in props: yield prop def remove_property(self, property_: Property): """Remove property *property_* from the testsuite.""" props = self.child(Properties) if props is None: return for prop in props: if prop == property_: props.remove(property_) def testsuites(self): """Iterate through all testsuites.""" for suite in self.iterchildren(TestSuite): yield suite def write(self, filepath: str = None, pretty=False): write_xml(self, filepath=filepath, pretty=pretty) class JUnitXml(Element): """The JUnitXml root object. It may contain ``<TestSuites>`` or a ``<TestSuite>``. Attributes: name: Name of the testsuite if it only contains one testsuite. time: Time consumed by the testsuites. tests: Total number of tests. failures: Number of failed cases. errors: Number of cases with errors. skipped: Number of skipped cases. """ _tag = "testsuites" name = Attr() time = FloatAttr() tests = IntAttr() failures = IntAttr() errors = IntAttr() skipped = IntAttr() def __init__(self, name=None): super().__init__(self._tag) self.filepath = None self.name = name def __iter__(self): return super().iterchildren(TestSuite) def __len__(self): return len(list(self.__iter__())) def __add__(self, other): result = JUnitXml() for suite in self: result.add_testsuite(suite) for suite in other: result.add_testsuite(suite) return result def __iadd__(self, other): if other._elem.tag == "testsuites": for suite in other: self.add_testsuite(suite) elif other._elem.tag == "testsuite": suite = TestSuite(name=other.name) for case in other: suite._add_testcase_no_update_stats(case) self.add_testsuite(suite) self.update_statistics() return self def add_testsuite(self, suite: TestSuite): """Add a testsuite.""" for existing_suite in self: if existing_suite == suite: for case in suite: existing_suite._add_testcase_no_update_stats(case) return self.append(suite) def update_statistics(self): """Update test count, time, etc.""" time = 0 tests = failures = errors = skipped = 0 for suite in self: suite.update_statistics() tests += suite.tests failures += suite.failures errors += suite.errors skipped += suite.skipped time += suite.time self.tests = tests self.failures = failures self.errors = errors self.skipped = skipped self.time = round(time, 3) @classmethod def fromroot(cls, root_elem: Element): """Construct JUnit objects from an elementTree root element.""" if root_elem.tag == "testsuites": instance = cls() elif root_elem.tag == "testsuite": instance = TestSuite() else: raise JUnitXmlError("Invalid format.") instance._elem = root_elem return instance @classmethod def fromstring(cls, text: str): """Construct JUnit objects from an XML string.""" root_elem = etree.fromstring(text) # nosec return cls.fromroot(root_elem) @classmethod def fromfile(cls, filepath: str, parse_func=None): """Initiate the object from a report file.""" if parse_func: tree = parse_func(filepath) else: tree = etree.parse(filepath) # nosec root_elem = tree.getroot() instance = cls.fromroot(root_elem) instance.filepath = filepath return instance def write(self, filepath: str = None, pretty=False, to_console=False): """Write the object into a JUnit XML file. If `file_path` is not specified, it will write to the original file. If `pretty` is True, the result file will be more human friendly. """ write_xml(self, filepath=filepath, pretty=pretty, to_console=to_console) 07070100000016000081A400000000000000000000000165D5C41600000000000000000000000000000000000000000000002700000000junitparser-3.1.2/junitparser/py.typed07070100000017000081A400000000000000000000000165D5C4160000145F000000000000000000000000000000000000002800000000junitparser-3.1.2/junitparser/xunit2.py""" The flavor based on Jenkins xunit plugin: https://github.com/jenkinsci/xunit-plugin/blob/xunit-2.3.2/src/main/resources/org/jenkinsci/plugins/xunit/types/model/xsd/junit-10.xsd According to the internet, the schema is compatible with: - Pytest (as default, though it also supports a "legacy" xunit1 flavor) - Erlang/OTP - Maven Surefire - CppTest There may be many others that I'm not aware of. """ import itertools from typing import List, TypeVar from . import junitparser T = TypeVar("T") class JUnitXml(junitparser.JUnitXml): # Pytest and xunit schema doesn't have "skipped" in testsuites skipped = None def update_statistics(self): """Update test count, time, etc.""" time = 0 tests = failures = errors = 0 for suite in self: suite.update_statistics() tests += suite.tests failures += suite.failures errors += suite.errors time += suite.time self.tests = tests self.failures = failures self.errors = errors self.time = round(time, 3) class TestSuite(junitparser.TestSuite): """TestSuite for Pytest, with some different attributes.""" group = junitparser.Attr() id = junitparser.Attr() package = junitparser.Attr() file = junitparser.Attr() log = junitparser.Attr() url = junitparser.Attr() version = junitparser.Attr() def __iter__(self): return itertools.chain( super().iterchildren(TestCase), (case for suite in super().iterchildren(TestSuite) for case in suite), ) @property def system_out(self): """<system-out>""" elem = self.child(junitparser.SystemOut) if elem is not None: return elem.text return None @system_out.setter def system_out(self, value: str): """<system-out>""" out = self.child(junitparser.SystemOut) if out is not None: out.text = value else: out = junitparser.SystemOut(value) self.append(out) @property def system_err(self): """<system-err>""" elem = self.child(junitparser.SystemErr) if elem is not None: return elem.text return None @system_err.setter def system_err(self, value: str): """<system-err>""" err = self.child(junitparser.SystemErr) if err is not None: err.text = value else: err = junitparser.SystemErr(value) self.append(err) class StackTrace(junitparser.System): _tag = "stackTrace" class RerunType(junitparser.Result): _tag = "rerunType" @property def stack_trace(self): """<stackTrace>""" elem = self.child(StackTrace) if elem is not None: return elem.text return None @stack_trace.setter def stack_trace(self, value: str): """<stackTrace>""" trace = self.child(StackTrace) if trace is not None: trace.text = value else: trace = StackTrace(value) self.append(trace) @property def system_out(self): """<system-out>""" elem = self.child(junitparser.SystemOut) if elem is not None: return elem.text return None @system_out.setter def system_out(self, value: str): """<system-out>""" out = self.child(junitparser.SystemOut) if out is not None: out.text = value else: out = junitparser.SystemOut(value) self.append(out) @property def system_err(self): """<system-err>""" elem = self.child(junitparser.SystemErr) if elem is not None: return elem.text return None @system_err.setter def system_err(self, value: str): """<system-err>""" err = self.child(junitparser.SystemErr) if err is not None: err.text = value else: err = junitparser.SystemErr(value) self.append(err) class RerunFailure(RerunType): _tag = "rerunFailure" class RerunError(RerunType): _tag = "rerunError" class FlakyFailure(RerunType): _tag = "flakyFailure" class FlakyError(RerunType): _tag = "flakyError" class TestCase(junitparser.TestCase): group = junitparser.Attr() def _rerun_results(self, _type: T) -> List[T]: elems = self.iterchildren(_type) results = [] for elem in elems: results.append(_type.fromelem(elem)) return results def rerun_failures(self): """<rerunFailure>""" return self._rerun_results(RerunFailure) def rerun_errors(self): """<rerunError>""" return self._rerun_results(RerunError) def flaky_failures(self): """<flakyFailure>""" return self._rerun_results(FlakyFailure) def flaky_errors(self): """<flakyError>""" return self._rerun_results(FlakyError) def add_rerun_result(self, result: RerunType): """Append a rerun result to the testcase. A testcase can have multiple rerun results.""" self.append(result) 07070100000018000081A400000000000000000000000165D5C41600000073000000000000000000000000000000000000002100000000junitparser-3.1.2/pyproject.toml[build-system] requires = ["setuptools >=44.0", "wheel >=0.37"] build-backend = "setuptools.build_meta:__legacy__" 07070100000019000081A400000000000000000000000165D5C41600000001000000000000000000000000000000000000002300000000junitparser-3.1.2/requirements.txt 0707010000001A000081A400000000000000000000000165D5C41600000019000000000000000000000000000000000000001C00000000junitparser-3.1.2/setup.cfg[bdist_wheel] universal=10707010000001B000081A400000000000000000000000165D5C41600000402000000000000000000000000000000000000001B00000000junitparser-3.1.2/setup.pyfrom setuptools import setup, find_packages import os from junitparser import version def read(fname): try: with open(os.path.join(os.path.dirname(__file__), fname)) as f: return f.read() except IOError: return "" setup( name="junitparser", version=version, description="Manipulates JUnit/xUnit Result XML files", long_description=read("README.rst"), classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: Apache Software License", "Topic :: Text Processing", "Programming Language :: Python :: 3", ], url="https://github.com/weiwei/junitparser", author="Weiwei Wang", author_email="gastlygem@gmail.com", license="Apache 2.0", install_requires=[], keywords="junit xunit xml parser", packages=find_packages(exclude=["tests"]), entry_points={"console_scripts": ["junitparser=junitparser.cli:main"]}, zip_safe=False, ) 0707010000001C000041ED00000000000000000000000365D5C41600000000000000000000000000000000000000000000001800000000junitparser-3.1.2/tests0707010000001D000081A400000000000000000000000165D5C41600000000000000000000000000000000000000000000002400000000junitparser-3.1.2/tests/__init__.py0707010000001E000041ED00000000000000000000000265D5C41600000000000000000000000000000000000000000000001D00000000junitparser-3.1.2/tests/data0707010000001F000081A400000000000000000000000165D5C416000005D0000000000000000000000000000000000000002900000000junitparser-3.1.2/tests/data/jenkins.xml<?xml version="1.0" encoding="UTF-8"?> <!-- according to https://llg.cubic.org/docs/junit/, <testsuite> can be child of <testsuite> --> <testsuites> <testsuite name="JUnitXmlReporter" errors="0" tests="0" failures="0" time="0" timestamp="2013-05-24T10:23:58" /> <testsuite name="JUnitXmlReporter.constructor" errors="0" skipped="1" tests="3" failures="1" time="0.006" timestamp="2013-05-24T10:23:58"> <properties> <property name="java.vendor" value="Sun Microsystems Inc." /> <property name="compiler.debug" value="on" /> <property name="project.jdk.classpath" value="jdk.classpath.1.6" /> </properties> <testcase classname="JUnitXmlReporter.constructor" name="should default path to an empty string" time="0.006"> <failure message="test failure">Assertion failed</failure> </testcase> <testsuite name="JUnitXmlReporter.constructor" errors="0" skipped="1" tests="2" failures="0" time="0.000" timestamp="2013-05-24T10:23:58"> <testcase classname="JUnitXmlReporter.constructor" name="should default consolidate to true" time="0"> <skipped /> </testcase> <testsuite name="JUnitXmlReporter.constructor" errors="0" skipped="0" tests="1" failures="0" time="0" timestamp="2013-05-24T10:23:58"> <testcase classname="JUnitXmlReporter.constructor" name="should default useDotNotation to true" time="0" /> </testsuite> </testsuite> </testsuite> </testsuites>07070100000020000081A400000000000000000000000165D5C416000003C1000000000000000000000000000000000000002A00000000junitparser-3.1.2/tests/data/no_fails.xml<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="JUnitXmlReporter" errors="0" tests="0" failures="0" time="0" timestamp="2013-05-24T10:23:58" /> <testsuite name="JUnitXmlReporter.constructor" errors="0" skipped="1" tests="3" failures="1" time="0.006" timestamp="2013-05-24T10:23:58"> <properties> <property name="java.vendor" value="Sun Microsystems Inc." /> <property name="compiler.debug" value="on" /> <property name="project.jdk.classpath" value="jdk.classpath.1.6" /> </properties> <testcase classname="JUnitXmlReporter.constructor" name="should default path to an empty string" time="0.006"/> <testcase classname="JUnitXmlReporter.constructor" name="should default consolidate to true" time="0"> <skipped /> </testcase> <testcase classname="JUnitXmlReporter.constructor" name="should default useDotNotation to true" time="0" /> </testsuite> </testsuites>07070100000021000081A400000000000000000000000165D5C4160000036E000000000000000000000000000000000000002F00000000junitparser-3.1.2/tests/data/no_suites_tag.xml<?xml version="1.0" encoding="UTF-8"?> <testsuite name="JUnitXmlReporter.constructor" errors="0" skipped="1" tests="3" failures="1" time="0.006" timestamp="2013-05-24T10:23:58"> <properties> <property name="java.vendor" value="Sun Microsystems Inc." /> <property name="compiler.debug" value="on" /> <property name="project.jdk.classpath" value="jdk.classpath.1.6" /> </properties> <testcase classname="JUnitXmlReporter.constructor" name="should default path to an empty string" time="0.006"> <failure message="test failure">Assertion failed</failure> </testcase> <testcase classname="JUnitXmlReporter.constructor" name="should default consolidate to true" time="0"> <skipped /> </testcase> <testcase classname="JUnitXmlReporter.constructor" name="should default useDotNotation to true" time="0" /> </testsuite>07070100000022000081A400000000000000000000000165D5C41600000416000000000000000000000000000000000000002800000000junitparser-3.1.2/tests/data/normal.xml<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="JUnitXmlReporter" errors="0" tests="0" failures="0" time="0" timestamp="2013-05-24T10:23:58" /> <testsuite name="JUnitXmlReporter.constructor" errors="0" skipped="1" tests="3" failures="1" time="0.006" timestamp="2013-05-24T10:23:58"> <properties> <property name="java.vendor" value="Sun Microsystems Inc." /> <property name="compiler.debug" value="on" /> <property name="project.jdk.classpath" value="jdk.classpath.1.6" /> </properties> <testcase classname="JUnitXmlReporter.constructor" name="should default path to an empty string" time="0.006"> <failure message="test failure">Assertion failed</failure> </testcase> <testcase classname="JUnitXmlReporter.constructor" name="should default consolidate to true" time="0"> <skipped /> </testcase> <testcase classname="JUnitXmlReporter.constructor" name="should default useDotNotation to true" time="0" /> </testsuite> </testsuites>07070100000023000081A400000000000000000000000165D5C41600000174000000000000000000000000000000000000002400000000junitparser-3.1.2/tests/test_cli.py# -*- coding: utf-8 -*- import os import pytest from junitparser.cli import verify @pytest.mark.parametrize( "file, expected_exitcode", [("data/jenkins.xml", 1), ("data/no_fails.xml", 0), ("data/normal.xml", 1)], ) def test_verify(file, expected_exitcode): path = os.path.join(os.path.dirname(__file__), file) assert verify([path]) == expected_exitcode 07070100000024000081A400000000000000000000000165D5C4160000185E000000000000000000000000000000000000002900000000junitparser-3.1.2/tests/test_fromfile.py# -*- coding: utf-8 -*- import os import pytest from junitparser import ( TestCase, TestSuite, Skipped, Failure, Error, Attr, JUnitXmlError, JUnitXml, Property, Properties, IntAttr, FloatAttr, ) try: from lxml.etree import XMLParser, parse has_lxml = True except ImportError: has_lxml = False @pytest.fixture(scope="module") def tmpfile(): import tempfile fd, tmp = tempfile.mkstemp(suffix=".xml") yield tmp os.close(fd) if os.path.exists(tmp): os.remove(tmp) def test_fromfile(): xml = JUnitXml.fromfile(os.path.join(os.path.dirname(__file__), "data/normal.xml")) suite1, suite2 = list(iter(xml)) assert len(list(suite1.properties())) == 0 assert len(list(suite2.properties())) == 3 assert len(suite2) == 3 assert suite2.name == "JUnitXmlReporter.constructor" assert suite2.tests == 3 cases = list(suite2.iterchildren(TestCase)) assert isinstance(cases[0].result[0], Failure) assert isinstance(cases[1].result[0], Skipped) assert len(cases[2].result) == 0 @pytest.mark.skipif(not has_lxml, reason="lxml required to run the case") def test_fromfile_with_parser(): def parse_func(file_path): xml_parser = XMLParser(huge_tree=True) return parse(file_path, xml_parser) xml = JUnitXml.fromfile( os.path.join(os.path.dirname(__file__), "data/normal.xml"), parse_func=parse_func, ) suite1, suite2 = list(iter(xml)) assert len(list(suite1.properties())) == 0 assert len(list(suite2.properties())) == 3 assert len(suite2) == 3 assert suite2.name == "JUnitXmlReporter.constructor" assert suite2.tests == 3 cases = list(suite2.iterchildren(TestCase)) assert isinstance(cases[0].result[0], Failure) assert isinstance(cases[1].result[0], Skipped) assert len(cases[2].result) == 0 def test_fromfile_without_testsuites_tag(): xml = JUnitXml.fromfile( os.path.join(os.path.dirname(__file__), "data/no_suites_tag.xml") ) cases = list(iter(xml)) properties = list(iter(xml.properties())) assert len(properties) == 3 assert len(cases) == 3 assert xml.name == "JUnitXmlReporter.constructor" assert xml.tests == 3 assert isinstance(cases[0].result[0], Failure) assert isinstance(cases[1].result[0], Skipped) assert len(cases[2].result) == 0 def test_fromfile_with_testsuite_in_testsuite(): xml = JUnitXml.fromfile(os.path.join(os.path.dirname(__file__), "data/jenkins.xml")) suite1, suite2 = list(iter(xml)) assert len(list(suite1.properties())) == 0 assert len(list(suite2.properties())) == 3 assert len(suite2) == 3 assert suite2.name == "JUnitXmlReporter.constructor" assert suite2.tests == 3 direct_cases = list(suite2.iterchildren(TestCase)) assert len(direct_cases) == 1 assert isinstance(direct_cases[0].result[0], Failure) all_cases = list(suite2) assert isinstance(all_cases[0].result[0], Failure) assert isinstance(all_cases[1].result[0], Skipped) assert len(all_cases[2].result) == 0 def test_write_xml_without_testsuite_tag(tmpfile): suite = TestSuite() suite.name = "suite1" case = TestCase() case.name = "case1" suite.add_testcase(case) suite.write(tmpfile) with open(tmpfile) as f: text = f.read() assert "suite1" in text assert "case1" in text def test_file_is_not_xml(tmpfile): text = "Not really an xml file" with open(tmpfile, "w") as f: f.write(text) with pytest.raises(Exception): JUnitXml.fromfile(tmpfile) # Raises lxml.etree.XMLSyntaxError def test_illegal_xml_file(tmpfile): text = "<some></some>" with open(tmpfile, "w") as f: f.write(text) with pytest.raises(JUnitXmlError): JUnitXml.fromfile(tmpfile) def test_write(tmpfile): suite1 = TestSuite() suite1.name = "suite1" case1 = TestCase() case1.name = "case1" suite1.add_testcase(case1) result = JUnitXml() result.add_testsuite(suite1) result.write(tmpfile) with open(tmpfile) as f: text = f.read() assert "suite1" in text assert "case1" in text def test_write_noarg(): suite1 = TestSuite() suite1.name = "suite1" case1 = TestCase() case1.name = "case1" suite1.add_testcase(case1) result = JUnitXml() result.add_testsuite(suite1) with pytest.raises(JUnitXmlError): result.write() def test_write_nonascii(tmpfile): suite1 = TestSuite() suite1.name = "suite1" case1 = TestCase() case1.name = "用例1" suite1.add_testcase(case1) result = JUnitXml() result.add_testsuite(suite1) result.write(tmpfile) with open(tmpfile, encoding="utf-8") as f: text = f.read() assert "suite1" in text assert "用例1" in text def test_read_written_xml(tmpfile): suite1 = TestSuite() suite1.name = "suite1" case1 = TestCase() case1.name = "用例1" suite1.add_testcase(case1) result = JUnitXml() result.add_testsuite(suite1) result.write(tmpfile) xml = JUnitXml.fromfile(tmpfile) suite = next(iter(xml)) case = next(iter(suite)) assert case.name == "用例1" def test_multi_results_in_case(): # Has to be a binary string to include xml declarations. text = b"""<?xml version="1.0" encoding="UTF-8"?> <testsuites> <testsuite name="JUnitXmlReporter.constructor"> <testcase classname="JUnitXmlReporter.constructor" name="should default path to an empty string" time="0.006"> <failure message="test failure">Assertion failed</failure> <skipped /> </testcase> </testsuite> </testsuites>""" xml = JUnitXml.fromstring(text) suite = next(iter(xml)) case = next(iter(suite)) assert len(case.result) == 2 def test_write_pretty(tmpfile): suite1 = TestSuite() suite1.name = "suite1" case1 = TestCase() case1.name = "用例1" suite1.add_testcase(case1) result = JUnitXml() result.add_testsuite(suite1) result.write(tmpfile, pretty=True) xml = JUnitXml.fromfile(tmpfile) suite = next(iter(xml)) case = next(iter(suite)) assert case.name == "用例1" 07070100000025000081A400000000000000000000000165D5C41600006680000000000000000000000000000000000000002800000000junitparser-3.1.2/tests/test_general.py# -*- coding: utf-8 -*- import locale from copy import deepcopy from xml.etree import ElementTree as etree import pytest from junitparser import ( TestCase, TestSuite, Skipped, Failure, Error, Attr, JUnitXmlError, JUnitXml, Property, Properties, IntAttr, FloatAttr, Element, ) try: from lxml import etree as expected_lxml_etree has_lxml = True except ImportError: from xml.etree import ElementTree as expected_xml_etree has_lxml = False class Test_XmlPackage: @pytest.mark.skipif(has_lxml, reason="xml package is used unless lxml is installed") def test_xml_etree(self): from junitparser.junitparser import etree as actual_etree assert actual_etree == expected_xml_etree @pytest.mark.skipif(not has_lxml, reason="lxml package has to be installed") def test_lxml_etree(self): from junitparser.junitparser import etree as actual_etree assert actual_etree == expected_lxml_etree class Test_MergeSuiteCounts: def test_merge_test_count(self): text1 = """<testsuite name="suitename1" tests="2" failures="1"> <testcase name="testname1"><failure message="failed"/></testcase> <testcase name="testname2"></testcase> </testsuite>""" test_suite1 = TestSuite.fromstring(text1) text2 = """<testsuite name="suitename2" tests="2" skipped="1"> <testcase name="testname3"><skipped message="no reason given"/></testcase> <testcase name="testname4"></testcase> </testsuite>""" test_suite2 = TestSuite.fromstring(text2) combined_suites = JUnitXml() combined_suites += test_suite1 combined_suites += test_suite2 assert combined_suites.tests == 4 assert combined_suites.failures == 1 assert combined_suites.skipped == 1 def test_merge_same_suite(self): text1 = """<testsuite name="suitename1" tests="2" failures="1"> <testcase name="testname1"><failure message="failed"/></testcase> <testcase name="testname2"></testcase> </testsuite>""" test_suite1 = TestSuite.fromstring(text1) text2 = """<testsuite name="suitename1" tests="2" skipped="1"> <testcase name="testname3"><skipped message="no reason given"/></testcase> <testcase name="testname4"></testcase> </testsuite>""" test_suite2 = TestSuite.fromstring(text2) combined_suites = JUnitXml() combined_suites += test_suite1 combined_suites += test_suite2 suites = list(suite for suite in combined_suites) assert len(suites) == 1 assert combined_suites.tests == 4 assert combined_suites.failures == 1 assert combined_suites.skipped == 1 @pytest.fixture() def locale_fixture(): old_locale = locale.getlocale(locale.LC_NUMERIC) yield locale.setlocale(locale.LC_NUMERIC, old_locale) class Test_Locale: @pytest.mark.parametrize("loc", ["", "en_US.UTF-8", "de_DE.UTF-8"]) def test_fromstring_numbers_locale_insensitive(self, loc, locale_fixture): "Case relies on that LC_ALL is set in the console." locale.setlocale(locale.LC_NUMERIC, loc) text = """<testsuites> <testsuite errors="0" failures="0" hostname="hooch" name="pytest" skipped="0" tests="2" time="1000.125" timestamp="2020-02-05T10:52:33.843536"> <testcase classname="test_x" file="test_x.py" line="7" name="test_comp_1" time="1,000.025"/> <testcase classname="test_x" file="test_x.py" line="10" name="test_comp_2" time="0.1"/> </testsuite> </testsuites>""" result = JUnitXml.fromstring(text) suite = list(iter(result))[0] assert suite.time == 1000.125 cases = list(iter(suite)) assert cases[0].time == 1000.025 assert cases[1].time == 0.1 class Test_JunitXml: def test_fromstring(self): text = """<testsuites><testsuite name="suitename1"> <testcase name="testname1"> </testcase></testsuite> <testsuite name="suitename2"> <testcase name="testname2"> </testcase></testsuite></testsuites>""" result = JUnitXml.fromstring(text) assert len(result) == 2 assert result.time == 0 def test_fromstring_no_testsuites(self): text = """<testsuite name="suitename1"> <testcase name="testname1"> </testcase></testsuite>""" result = JUnitXml.fromstring(text) assert len(result) == 1 assert result.time == 0 def test_fromstring_multiple_fails(self): text = """<testsuites> <testsuite errors="1" failures="0" hostname="hooch" name="pytest" skipped="1" tests="3" time="0.025" timestamp="2020-02-05T10:52:33.843536"> <testcase classname="test_x" file="test_x.py" line="7" name="test_comp_1" time="0.000"/> <testcase classname="test_x" file="test_x.py" line="10" name="test_comp_2" time="0.000"> <skipped message="unconditional skip" type="pytest.skip">test_x.py:11: unconditional skip</skipped> <error message="test teardown failure"> @pytest.fixture(scope="module") def compb(): yield > raise PermissionError E PermissionError test_x.py:6: PermissionError </error> </testcase> </testsuite> </testsuites>""" result = JUnitXml.fromstring(text) assert isinstance(result, JUnitXml) assert result.errors == 1 assert result.skipped == 1 suite = list(iter(result))[0] cases = list(iter(suite)) assert len(cases[0].result) == 0 assert len(cases[1].result) == 2 text = cases[1].result[1].text assert "@pytest.fixture" in text def test_fromroot_testsuite(self): text = """ <testsuite errors="1" failures="0" hostname="hooch" name="pytest" skipped="1" tests="3" time="0.025" timestamp="2020-02-05T10:52:33.843536"> <testcase classname="test_x" file="test_x.py" line="7" name="test_comp_1" time="0.000"/> <testcase classname="test_x" file="test_x.py" line="10" name="test_comp_2" time="0.000"> <skipped message="unconditional skip" type="pytest.skip">test_x.py:11: unconditional skip</skipped> <error message="test teardown failure"> @pytest.fixture(scope="module") def compb(): yield > raise PermissionError E PermissionError test_x.py:6: PermissionError </error> </testcase> </testsuite>""" root_elemt = etree.fromstring(text) result = JUnitXml.fromroot(root_elemt) assert isinstance(result, TestSuite) assert result.errors == 1 assert result.skipped == 1 cases = list(iter(result)) assert len(cases[0].result) == 0 assert len(cases[1].result) == 2 text = cases[1].result[1].text assert "@pytest.fixture" in text def test_fromroot_testsuites(self): text = """<testsuites> <testsuite errors="1" failures="0" hostname="hooch" name="pytest" skipped="1" tests="3" time="0.025" timestamp="2020-02-05T10:52:33.843536"> <testcase classname="test_x" file="test_x.py" line="7" name="test_comp_1" time="0.000"/> <testcase classname="test_x" file="test_x.py" line="10" name="test_comp_2" time="0.000"> <skipped message="unconditional skip" type="pytest.skip">test_x.py:11: unconditional skip</skipped> <error message="test teardown failure"> @pytest.fixture(scope="module") def compb(): yield > raise PermissionError E PermissionError test_x.py:6: PermissionError </error> </testcase> </testsuite> </testsuites>""" root_elemt = etree.fromstring(text) result = JUnitXml.fromroot(root_elemt) assert result.errors == 1 assert result.skipped == 1 suite = list(iter(result))[0] cases = list(iter(suite)) assert len(cases[0].result) == 0 assert len(cases[1].result) == 2 text = cases[1].result[1].text assert "@pytest.fixture" in text def test_fromstring_invalid(self): text = """<random name="suitename1"></random>""" with pytest.raises(Exception) as context: JUnitXml.fromstring(text) assert isinstance(context.value, JUnitXmlError) def test_add_suite(self): suite1 = TestSuite("suite1") suite2 = TestSuite("suite2") result = JUnitXml() result.add_testsuite(suite1) result.add_testsuite(suite2) assert len(result) == 2 def test_construct_xml(self): suite1 = TestSuite() suite1.name = "suite1" case1 = TestCase() case1.name = "case1" suite1.add_testcase(case1) result = JUnitXml() result.add_testsuite(suite1) assert result._elem.tag == "testsuites" suite = result._elem.findall("testsuite") assert len(suite) == 1 assert suite[0].attrib["name"] == "suite1" case = suite[0].findall("testcase") assert len(case) == 1 assert case[0].attrib["name"] == "case1" def test_add(self): result1 = JUnitXml() suite1 = TestSuite("suite1") result1.add_testsuite(suite1) result2 = JUnitXml() suite2 = TestSuite("suite2") result2.add_testsuite(suite2) result3 = result1 + result2 assert len(result3) == 2 def test_add_same_suite(self): result1 = JUnitXml() suite1 = TestSuite() result1.add_testsuite(suite1) result2 = JUnitXml() suite2 = TestSuite() result2.add_testsuite(suite2) result3 = result1 + result2 assert len(result3) == 1 def test_iadd(self): result1 = JUnitXml() suite1 = TestSuite("suite1") result1.add_testsuite(suite1) result2 = JUnitXml() suite2 = TestSuite("suite2") result2.add_testsuite(suite2) result1 += result2 assert len(result1) == 2 def test_iadd_same_suite(self): result1 = JUnitXml() suite1 = TestSuite() result1.add_testsuite(suite1) result2 = JUnitXml() suite2 = TestSuite() result2.add_testsuite(suite2) result1 += result2 assert len(result1) == 1 def test_add_two_same_suites(self): suite1 = TestSuite() case1 = TestCase(name="case1") suite1.add_testcase(case1) suite2 = TestSuite() case2 = TestCase(name="case2") suite2.add_testcase(case2) suite3 = TestSuite() suite2.add_testsuite(suite3) result = suite1 + suite2 assert isinstance(result, TestSuite) assert len(list(iter(result))) == 2 assert len(list(iter(result.testsuites()))) == 1 def test_iadd_two_same_suites(self): suite1 = TestSuite() case1 = TestCase(name="case1") suite1.add_testcase(case1) suite2 = TestSuite() case2 = TestCase(name="case2") suite2.add_testcase(case2) suite3 = TestSuite() suite2.add_testsuite(suite3) suite1 += suite2 assert isinstance(suite1, TestSuite) assert len(list(iter(suite1))) == 2 assert len(list(iter(suite1.testsuites()))) == 1 def test_add_two_different_suites(self): suite1 = TestSuite(name="suite1") case1 = TestCase(name="case1") suite1.add_testcase(case1) suite2 = TestSuite(name="suite2") case2 = TestCase(name="case2") suite2.add_testcase(case2) result = suite1 + suite2 assert isinstance(result, JUnitXml) assert len(list(iter(result))) == 2 def test_iadd_two_different_suites(self): suite1 = TestSuite(name="suite1") case1 = TestCase(name="case1") suite1.add_testcase(case1) suite2 = TestSuite(name="suite2") case2 = TestCase(name="case2") suite2.add_testcase(case2) suite1 += suite2 assert isinstance(suite1, JUnitXml) assert len(list(iter(suite1))) == 2 def test_xml_statistics(self): result1 = JUnitXml() suite1 = TestSuite() result1.add_testsuite(suite1) result2 = JUnitXml() suite2 = TestSuite() result2.add_testsuite(suite2) result3 = result1 + result2 result3.update_statistics() assert result3.tests == 0 class Test_TestSuite: def test_fromstring(self): text = """<testsuite name="suitename" time="1.32"> <testcase name="testname"> <failure message="failure message" type="FailureType"/> </testcase></testsuite>""" suite = TestSuite.fromstring(text) assert suite.time == 1.32 suite.update_statistics() assert suite.name == "suitename" assert suite.tests == 1 def test_props_fromstring(self): text = """<testsuite name="suitename"> <properties><property name="name1" value="value1"/></properties> </testsuite>""" suite = TestSuite.fromstring(text) for prop in suite.properties(): assert prop.name == "name1" assert prop.value == "value1" def test_quoted_attr(self): text = """<testsuite name="suitename with "quotes""> </testsuite>""" suite = TestSuite.fromstring(text) assert suite.name == 'suitename with "quotes"' def test_combining_testsuite_should_keep_name(self): text1 = """<testsuite name="suitename1" tests="2" failures="1"> <testcase name="testname1"><failure message="failed"/></testcase> <testcase name="testname2"></testcase> </testsuite>""" test_suite1 = TestSuite.fromstring(text1) text2 = """<testsuite name="suitename2" tests="2" skipped="1"> <testcase name="testname3"><skipped message="no reason given"/></testcase> <testcase name="testname4"></testcase> </testsuite>""" test_suite2 = TestSuite.fromstring(text2) combined_suites = JUnitXml() combined_suites += test_suite1 combined_suites += test_suite2 assert [s.name for s in combined_suites] == ["suitename1", "suitename2"] def test_len(self): text = """<testsuite name="suitename"><testcase name="testname"/> <testcase name="testname2"/> </testsuite>""" suite = TestSuite.fromstring(text) assert len(suite) == 2 def test_add_case(self): suite = TestSuite() assert suite.tests == 0 case1 = TestCase() case2 = TestCase() case2.result = [Failure()] case3 = TestCase() case3.result = [Error()] case4 = TestCase() case4.result = [Skipped()] suite.add_testcase(case1) suite.add_testcase(case2) suite.add_testcase(case3) suite.add_testcase(case4) suite.update_statistics() assert suite.tests == 4 assert suite.failures == 1 assert suite.errors == 1 assert suite.skipped == 1 def test_case_count(self): suite = TestSuite() case1 = TestCase() suite.add_testcase(case1) assert suite.tests == 1 assert suite.failures == 0 def test_add_property(self): suite = TestSuite() suite.add_property("name1", "value1") res_prop = next(suite.properties()) assert res_prop.name == "name1" assert res_prop.value == "value1" def test_remove_case(self): suite = TestSuite() case1 = TestCase() case1.name = "test1" case2 = TestCase() case2.name = "test2" suite.add_testcase(case1) suite.add_testcase(case2) suite.remove_testcase(case1) assert len(suite) == 1 def test_remove_property(self): suite = TestSuite() suite.add_property("name1", "value1") suite.add_property("name2", "value2") suite.add_property("name3", "value3") for prop in suite.properties(): if prop.name == "name2": suite.remove_property(prop) assert len(list(suite.properties())) == 2 def test_remove_property_from_none(self): suite = TestSuite() suite.remove_property(Property("key", "value")) # Nothing should happen def test_suite_in_suite(self): suite = TestSuite("parent") childsuite = TestSuite("child") suite.add_testsuite(childsuite) assert len(list(suite.testsuites())) == 1 def test_case_time(self): suite = TestSuite() case1 = TestCase() case1.name = "test1" case1.time = 15 suite.add_testcase(case1) suite.update_statistics() assert suite.time == 15 def test_wrong_attr_type(self): suite = TestSuite() with pytest.raises(TypeError): suite.time = "abc" with pytest.raises(TypeError): suite.tests = 10.5 def test_suite_eq(self): suite = TestSuite() suite.add_property("name1", "value1") suite2 = deepcopy(suite) assert suite == suite2 def test_suite_ne(self): suite = TestSuite() suite.add_property("name1", "value1") suite2 = deepcopy(suite) suite2.add_property("name2", "value2") assert suite != suite2 def test_add_cases(self): suite = TestSuite() assert suite.tests == 0 case1 = TestCase() case2 = TestCase() case2.result = [Failure()] case3 = TestCase() case3.result = [Error()] case4 = TestCase() case4.result = [Skipped()] suite.add_testcases([case1, case2, case3, case4]) suite.update_statistics() assert suite.tests == 4 assert suite.failures == 1 assert suite.errors == 1 assert suite.skipped == 1 class Test_TestCase: def test_case_fromstring(self): text = """<testcase name="testname"> <failure message="failure message" type="FailureType"/> <system-out>System out</system-out> <system-err>System err</system-err> </testcase>""" case = TestCase.fromstring(text) assert case.name == "testname" assert isinstance(case.result[0], Failure) assert case.system_out == "System out" assert case.system_err == "System err" def test_xml_multi_results(self): text = """<testcase name="testname"> <failure message="failure message" type="FailureType"/> <skipped message="skipped message" type="FailureType"/> </testcase> """ case = TestCase.fromstring(text) # no assertion raised assert case.name == "testname" assert len(case.result) == 2 def test_multi_results(self): case = TestCase("testname") err = Error("err msg", "err_type") fail1 = Failure("fail msg 1", "fail_type") fail2 = Failure("fail msg 2", "fail_type") fail3 = Failure("fail msg 3", "fail_type") fail4 = Failure("fail msg 4", "fail_type") case.result += [err] assert len(case.result) == 1 case.result += [fail1] assert len(case.result) == 2 case.result += [fail2] assert len(case.result) == 3 case.result += [fail3] assert len(case.result) == 4 case.result += [fail4] assert len(case.result) == 5 def test_case_attributes(self): case = TestCase() case.name = "testname" case.classname = "testclassname" case.time = 15.123 case.result = [Skipped()] case.result[0].text = "woah skipped" assert case.name == "testname" assert case.classname == "testclassname" assert case.time == 15.123 assert isinstance(case.result[0], Skipped) assert case.result[0].text == "woah skipped" def test_case_init_with_attributes(self): case = TestCase("testname", "testclassname", 15.123) case.result = [Skipped()] assert case.name == "testname" assert case.classname == "testclassname" assert case.time == 15.123 assert isinstance(case.result[0], Skipped) def test_case_output(self): case = TestCase() case.system_err = "error message" case.system_out = "out message" assert case.system_err == "error message" assert case.system_out == "out message" case.system_err = "error2" case.system_out = "out2" assert case.system_err == "error2" assert case.system_out == "out2" def test_update_results(self): case = TestCase() case.result = [Skipped()] case.result = [Failure(), Skipped()] assert len(case.result) == 2 def test_monkypatch(self): TestCase.id = Attr("id") case = TestCase() case.id = "100" assert case.id == "100" def test_equal(self): case = TestCase() case.name = "test1" case2 = TestCase() case2.name = "test1" assert case == case2 def test_not_equal(self): case = TestCase() case.name = "test1" case2 = TestCase() case2.name = "test2" assert case != case2 def test_from_elem(self): elem = etree.Element("testcase", name="case1") case = TestCase.fromelem(elem) assert case.name == "case1" def test_from_junit_elem(self): case = TestCase() case.name = "test1" class TestOtherCase(TestCase): _tag = "TestOtherCase" assertions = Attr() other_case = TestOtherCase.fromelem(case) assert case.name == other_case.name assert other_case.assertions is None pytest.raises(AttributeError, lambda: case.assertions) other_case.assertions = 20 assert other_case.assertions == "20" def test_to_string(self): case = TestCase() case.name = "test1" case_str = case.tostring() assert b"test1" in case_str def test_to_nonascii_string(self): case = TestCase() case.name = "测试1" case.result = [Failure("失败", "类型")] case_str = case.tostring() assert "测试1" in case_str.decode("utf-8") assert "失败" in case_str.decode("utf-8") assert "类型" in case_str.decode("utf-8") def test_system_out(self): case = TestCase() case.name = "case1" assert case.system_out is None case.system_out = "output" assert case.system_out == "output" def test_system_err(self): case = TestCase() case.name = "case1" assert case.system_err is None case.system_err = "error" assert case.system_err == "error" def test_result_eq(self): assert Failure("A") == Failure("A") assert Skipped("B") != Skipped("A") assert Error("C") != Error("B") def test_result_attrs(self): res1 = Failure("A") # NOTE: lxml gives spaceless result assert res1.tostring() in [ b'<failure message="A" />', b'<failure message="A"/>', ] def test_add_child_element(self): class CustomElement(Element): _tag = "custom" foo = Attr() bar = Attr() testcase = TestCase() custom = CustomElement() testcase.append(custom) assert testcase.tostring() in [ b"<testcase><custom /></testcase>", b"<testcase><custom/></testcase>", ] def test_case_is_skipped(self): case = TestCase() case.result = [Skipped()] assert case.is_skipped assert not case.is_passed def test_case_is_passed(self): case = TestCase() case.result = [] assert not case.is_skipped assert case.is_passed def test_case_is_failed(self): case = TestCase() case.result = [Failure()] assert not case.is_skipped assert not case.is_passed class Test_Properties: def test_property_repr1(self): prop1 = Property("prop1", "1") assert prop1.__repr__() == '<Element \'property\' name="prop1" value="1">' def test_property_repr2(self): prop1 = TestSuite() assert prop1.__repr__() == "<Element 'testsuite'>" def test_property_eq(self): prop1 = Property("prop1", "1") prop2 = Property("prop1", "1") assert prop1 == prop2 def test_property_ne(self): prop1 = Property("prop1", "1") prop2 = Property("prop1", "2") assert prop1 != prop2 def test_properties_eq(self): prop1 = Property("prop1", "1") prop2 = Property("prop1", "2") # Note: an attribute can only be used at one place. prop3 = deepcopy(prop1) prop4 = deepcopy(prop2) props1 = Properties() props1.add_property(prop1) props1.add_property(prop2) props2 = Properties() props2.add_property(prop3) props2.add_property(prop4) assert props1 == props2 def test_properties_ne(self): prop1 = Property("prop1", "1") prop2 = Property("prop1", "2") prop3 = deepcopy(prop1) prop4 = deepcopy(prop1) props1 = Properties() props1.add_property(prop1) props1.add_property(prop2) props2 = Properties() props2.add_property(prop3) props2.add_property(prop4) assert props1 != props2 def test_properties_ne2(self): prop1 = Property("prop1", "1") prop2 = Property("prop1", "2") prop3 = deepcopy(prop1) props1 = Properties() props1.add_property(prop1) props1.add_property(prop2) props2 = Properties() props2.add_property(prop3) assert props1 != props2 class Test_Attrs: def test_attr(self): TestCase.text = Attr("text") TestCase.int = IntAttr("int") TestCase.float = FloatAttr("float") element = TestCase("foo") element.text = "foo" element.int = 10 element.float = 8.5 assert element.text == "foo" assert element.int == 10 assert element.float == 8.5 07070100000026000081A400000000000000000000000165D5C41600000F4B000000000000000000000000000000000000002700000000junitparser-3.1.2/tests/test_xunit2.py# -*- coding: utf-8 -*- from junitparser.xunit2 import JUnitXml, TestSuite, TestCase, RerunFailure from junitparser import Failure from copy import deepcopy class Test_TestCase: def test_case_fromstring(self): text = """<testcase name="testname"> <failure message="failure message" type="FailureType"/> <rerunFailure message="Not found" type="404"> <system-out>No ha encontrado</system-out> </rerunFailure> <rerunFailure message="Server error" type="500"> <system-err>Error del servidor</system-err> <stackTrace>Stacktrace</stackTrace> </rerunFailure> <system-out>System out</system-out> <system-err>System err</system-err> </testcase>""" case = TestCase.fromstring(text) assert case.name == "testname" assert isinstance(case.result[0], Failure) assert case.system_out == "System out" assert case.system_err == "System err" rerun_failures = case.rerun_failures() assert len(rerun_failures) == 2 assert rerun_failures[0].message == "Not found" assert rerun_failures[0].stack_trace is None assert rerun_failures[0].system_out == "No ha encontrado" assert rerun_failures[0].system_err is None assert rerun_failures[1].message == "Server error" assert rerun_failures[1].stack_trace == "Stacktrace" assert rerun_failures[1].system_out is None assert rerun_failures[1].system_err == "Error del servidor" assert len(case.rerun_errors()) == 0 assert len(case.flaky_failures()) == 0 assert len(case.flaky_errors()) == 0 def test_case_rerun(self): case = TestCase("testname") rerun_failure = RerunFailure("Not found", "404") assert rerun_failure.system_out == None assert rerun_failure.system_err == None rerun_failure.stack_trace = "Stack" rerun_failure.system_err = "E404" rerun_failure.system_out = "NOT FOUND" case.add_rerun_result(rerun_failure) assert len(case.rerun_failures()) == 1 # Interesting, same object is only added once by xml libs failure2 = deepcopy(rerun_failure) failure2.stack_trace = "Stack2" failure2.system_err = "E401" failure2.system_out = "401 Error" case.add_rerun_result(failure2) assert len(case.rerun_failures()) == 2 class Test_TestSuite: def test_properties(self): suite = TestSuite("mySuite") assert suite.system_out is None assert suite.system_err is None suite.system_err = "System err" suite.system_out = "System out" assert suite.system_out == "System out" assert suite.system_err == "System err" suite.system_err = "System err2" suite.system_out = "System out2" assert suite.system_out == "System out2" assert suite.system_err == "System err2" def test_iterate_case(self): suite = TestSuite("mySuite") suite.add_testcase(TestCase("test1")) case = next(iter(suite)) assert case.name == "test1" def test_iterate_suite(self): suite = TestSuite("mySuite") suite.add_testsuite(TestSuite("suite1")) suite = next(suite.testsuites()) assert suite.name == "suite1" def test_remove_case(self): suite = TestSuite("mySuite") test = TestCase("test1") suite.add_testcase(test) suite.remove_testcase(test) assert len(suite) == 0 class Test_JUnitXml: def test_init(self): xml = JUnitXml("tests") assert xml.name == "tests" xml = JUnitXml("myname") xml.add_testsuite(TestSuite("suite1")) xml.update_statistics() assert xml.skipped is None assert xml.tostring().count(b"errors") == 2 assert xml.tostring().count(b"skipped") == 1 07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!203 blocks
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor