diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/CodeCoverage/index.html b/CodeCoverage/index.html new file mode 100644 index 00000000..644a8b8e --- /dev/null +++ b/CodeCoverage/index.html @@ -0,0 +1,194 @@ + + + + + + + + + Code Coverage — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Code Coverage

+

Code coverage measures used and unused code lines, statements, branches, etc. Depending on the programming language this +is measured by instrumenting the code/binary and running the program, it’s test cases or simulating the code. In +generate code coverage is a measure of test coverage. Unused code is not (yet) covered by tests.

+

The code coverage metric in percent is a ratio of used code versus all possibly usable code. A coverage of <100% +indicates unused code. This can be dead code (unreachable) or untested code (⇒ needs more test cases).

+

Code Coverage Kinds

+
    +
  • Coverage

    +
      +
    • Code coverage

      +
        +
      • statement coverage / line coverage

      • +
      • branch coverage

      • +
      • expression coverage

      • +
      • toggle coverage

      • +
      • state coverage (of state machines)

      • +
      • transition coverage (of state machines)

      • +
      +
    • +
    • Functional coverage

    • +
    • Documentation coverage

    • +
    • Test coverage

    • +
    +
  • +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/CommandLineInterface.html b/CommandLineInterface.html new file mode 100644 index 00000000..04f69dac --- /dev/null +++ b/CommandLineInterface.html @@ -0,0 +1,176 @@ + + + + + + + + + Command Line Interfaces — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Command Line Interfaces

+

When installed via PIP, the command line program pyedaa-reports is registered in the Python installation’s +Scripts directory. Usually this path is listed in PATH, thus this program is globally available after +installation.

+

The program is self-describing. Use pyedaa-reports without parameters or pyedaa-reports help to see all +available common options and commands. Each command has then it’s own help page for command specific options, which can +be listed by calling pyedaa-reports <cmd> -h or pyedaa-reports help <cmd>. The pyedaa-reports’s version and +license information is shown by calling pyedaa-reports version.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Dependency.html b/Dependency.html new file mode 100644 index 00000000..afe1702e --- /dev/null +++ b/Dependency.html @@ -0,0 +1,385 @@ + + + + + + + + + Dependency — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Dependency

+ + + + + + + + + + + +

Libraries.io

Requires.io

Libraries.io status for latest release

Requires.io

+
+

pyEDAA.Reports Package

+ + + + + + + + + + + + + + + + + + + + + + + + + +

Package

Version

License

Dependencies

pyTooling

≥7.0

Apache License, 2.0

None

ruamel.yaml

≥0.18

MIT

Not yet evaluated.

lxml

≥5.3

BSD 3-Clause

Not yet evaluated.

+
+
+

Unit Testing / Coverage / Type Checking (Optional)

+

Additional Python packages needed for testing, code coverage collection and static type checking. These packages are +only needed for developers or on a CI server, thus sub-dependencies are not evaluated further.

+

Manually Installing Test Requirements

+

Use the tests/requirements.txt file to install all dependencies via pip3. The file will recursively install +the mandatory dependencies too.

+
pip3 install -U -r tests/requirements.txt
+
+
+

Dependency List

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Package

Version

License

Dependencies

pytest

≥8.3

MIT

Not yet evaluated.

pytest-cov

≥6.0

MIT

Not yet evaluated.

Coverage

≥7.6

Apache License, 2.0

Not yet evaluated.

mypy

≥1.13

MIT

Not yet evaluated.

typing-extensions

≥4.12

PSF-2.0

Not yet evaluated.

lxml

≥5.3

BSD 3-Clause

Not yet evaluated.

+
+
+

Sphinx Documentation (Optional)

+

Additional Python packages needed for documentation generation. These packages are only needed for developers or on a +CI server, thus sub-dependencies are not evaluated further.

+

Manually Installing Documentation Requirements

+

Use the doc/requirements.txt file to install all dependencies via pip3. The file will recursively install +the mandatory dependencies too.

+
pip3 install -U -r doc/requirements.txt
+
+
+

Dependency List

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Package

Version

License

Dependencies

pyTooling

≥7.0

Apache License, 2.0

None

Sphinx

≥8.1

BSD 3-Clause

Not yet evaluated.

sphinx_btd_theme

≥0.5.2

MIT

Not yet evaluated.

!! sphinx_fontawesome

≥0.0.6

GPL 2.0

Not yet evaluated.

sphinx_autodoc_typehints

≥2.5

MIT

Not yet evaluated.

+
+
+

Packaging (Optional)

+

Additional Python packages needed for installation package generation. These packages are only needed for developers or +on a CI server, thus sub-dependencies are not evaluated further.

+

Manually Installing Packaging Requirements

+

Use the build/requirements.txt file to install all dependencies via pip3. The file will recursively +install the mandatory dependencies too.

+
pip3 install -U -r build/requirements.txt
+
+
+

Dependency List

+ + + + + + + + + + + + + + + + + + + + +

Package

Version

License

Dependencies

pyTooling

≥7.0

Apache License, 2.0

None

wheel

≥0.44

MIT

Not yet evaluated.

+
+
+

Publishing (CI-Server only)

+

Additional Python packages needed for publishing the generated installation package to e.g, PyPI or any equivalent +services. These packages are only needed for maintainers or on a CI server, thus sub-dependencies are not evaluated +further.

+

Manually Installing Publishing Requirements

+

Use the dist/requirements.txt file to install all dependencies via pip3. The file will recursively +install the mandatory dependencies too.

+
pip3 install -U -r dist/requirements.txt
+
+
+

Dependency List

+ + + + + + + + + + + + + + + + + + + + +

Package

Version

License

Dependencies

wheel

≥0.44

MIT

Not yet evaluated.

Twine

≥5.1

Apache License, 2.0

Not yet evaluated.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Doc-License.html b/Doc-License.html new file mode 100644 index 00000000..15c1c9b1 --- /dev/null +++ b/Doc-License.html @@ -0,0 +1,513 @@ + + + + + + + + + Creative Commons Attribution 4.0 International — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ + +
+

Attention

+

This CC BY 4.0 license applies only to the documentation of this project.

+
+
+

Creative Commons Attribution 4.0 International

+

Creative Commons Corporation (“Creative Commons”) is not a law firm and does not +provide legal services or legal advice. Distribution of Creative Commons public +licenses does not create a lawyer-client or other relationship. Creative Commons +makes its licenses and related information available on an “as-is” basis. +Creative Commons gives no warranties regarding its licenses, any material +licensed under their terms and conditions, or any related information. Creative +Commons disclaims all liability for damages resulting from their use to the +fullest extent possible.

+ +

Creative Commons Attribution 4.0 International Public License

+

By exercising the Licensed Rights (defined below), You accept and agree to be +bound by the terms and conditions of this Creative Commons Attribution 4.0 +International Public License (“Public License”). To the extent this Public +License may be interpreted as a contract, You are granted the Licensed Rights +in consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the Licensor +receives from making the Licensed Material available under these terms and +conditions.

+
+

Section 1 – Definitions.

+
    +
  1. Adapted Material means material subject to Copyright and Similar +Rights that is derived from or based upon the Licensed Material and in +which the Licensed Material is translated, altered, arranged, transformed, or +otherwise modified in a manner requiring permission under the Copyright and +Similar Rights held by the Licensor. For purposes of this Public License, +where the Licensed Material is a musical work, performance, or sound +recording, Adapted Material is always produced where the Licensed Material +is synched in timed relation with a moving image.

  2. +
  3. Adapter’s License means the license You apply to Your Copyright and +Similar Rights in Your contributions to Adapted Material in accordance with +the terms and conditions of this Public License.

  4. +
  5. Copyright and Similar Rights means copyright and/or similar rights +closely related to copyright including, without limitation, performance, +broadcast, sound recording, and Sui Generis Database Rights, without regard +to how the rights are labeled or categorized. For purposes of this Public +License, the rights specified in Section 2(b)(1)-(2) are not Copyright and +Similar Rights.

  6. +
  7. Effective Technological Measures means those measures that, in the +absence of proper authority, may not be circumvented under laws fulfilling +obligations under Article 11 of the WIPO Copyright Treaty adopted on +December 20, 1996, and/or similar international agreements.

  8. +
  9. Exceptions and Limitations means fair use, fair dealing, and/or any +other exception or limitation to Copyright and Similar Rights that applies to +Your use of the Licensed Material.

  10. +
  11. Licensed Material means the artistic or literary work, database, or +other material to which the Licensor applied this Public License.

  12. +
  13. Licensed Rights means the rights granted to You subject to the terms +and conditions of this Public License, which are limited to all Copyright and +Similar Rights that apply to Your use of the Licensed Material and that the +Licensor has authority to license.

  14. +
  15. Licensor means the individual(s) or entity(ies) granting rights under +this Public License.

  16. +
  17. Share means to provide material to the public by any means or process +that requires permission under the Licensed Rights, such as reproduction, +public display, public performance, distribution, dissemination, +communication, or importation, and to make material available to the public +including in ways that members of the public may access the material from a +place and at a time individually chosen by them.

  18. +
  19. Sui Generis Database Rights means rights other than copyright +resulting from Directive 96/9/EC of the European Parliament and of the +Council of 11 March 1996 on the legal protection of databases, as amended +and/or succeeded, as well as other essentially equivalent rights anywhere +in the world.

  20. +
  21. You means the individual or entity exercising the Licensed Rights +under this Public License. Your has a corresponding meaning.

  22. +
+
+
+

Section 2 – Scope.

+
    +
  1. License grant.

    +
      +
    1. Subject to the terms and conditions of this Public License, the Licensor +hereby grants You a worldwide, royalty-free, non-sublicensable, +non-exclusive, irrevocable license to exercise the Licensed Rights in the +Licensed Material to:

      +
      +
        +
      1. reproduce and Share the Licensed Material, in whole or in part; and

      2. +
      3. produce, reproduce, and Share Adapted Material.

      4. +
      +
      +
    2. +
    3. Exceptions and Limitations. For the avoidance of doubt, where +Exceptions and Limitations apply to Your use, this Public License does not +apply, and You do not need to comply with its terms and conditions.

    4. +
    5. Term. The term of this Public License is specified in Section 6(a).

    6. +
    7. Media and formats; technical modifications allowed. The Licensor +authorizes You to exercise the Licensed Rights in all media and formats +whether now known or hereafter created, and to make technical +modifications necessary to do so. The Licensor waives and/or agrees not to +assert any right or authority to forbid You from making technical +modifications necessary to exercise the Licensed Rights, including +technical modifications necessary to circumvent Effective Technological +Measures. For purposes of this Public License, simply making modifications +authorized by this Section 2(a)(4) never produces Adapted Material.

    8. +
    9. Downstream recipients.

      +
      +
        +
      1. Offer from the Licensor – Licensed Material. Every recipient of +the Licensed Material automatically receives an offer from the +Licensor to exercise the Licensed Rights under the terms and +conditions of this Public License.

      2. +
      3. No downstream restrictions. You may not offer or impose any +additional or different terms or conditions on, or apply any Effective +Technological Measures to, the Licensed Material if doing so restricts +exercise of the Licensed Rights by any recipient of the Licensed +Material.

      4. +
      +
      +
    10. +
    11. No endorsement. Nothing in this Public License constitutes or may +be construed as permission to assert or imply that You are, or that Your +use of the Licensed Material is, connected with, or sponsored, endorsed, +or granted official status by, the Licensor or others designated to +receive attribution as provided in Section 3(a)(1)(A)(i).

    12. +
    +
  2. +
  3. Other rights.

    +
      +
    1. Moral rights, such as the right of integrity, are not licensed under this +Public License, nor are publicity, privacy, and/or other similar +personality rights; however, to the extent possible, the Licensor waives +and/or agrees not to assert any such rights held by the Licensor to the +limited extent necessary to allow You to exercise the Licensed Rights, but +not otherwise.

    2. +
    3. Patent and trademark rights are not licensed under this Public License.

    4. +
    5. To the extent possible, the Licensor waives any right to collect royalties +from You for the exercise of the Licensed Rights, whether directly or +through a collecting society under any voluntary or waivable statutory or +compulsory licensing scheme. In all other cases the Licensor expressly +reserves any right to collect such royalties.

    6. +
    +
  4. +
+
+
+

Section 3 – License Conditions.

+

Your exercise of the Licensed Rights is expressly made subject to the following conditions.

+
    +
  1. Attribution.

    +
      +
    1. If You Share the Licensed Material (including in modified form), You must:

      +
      +
        +
      1. retain the following if it is supplied by the Licensor with the +Licensed Material:

      2. +
      +
      +
        +
      1. identification of the creator(s) of the Licensed Material and any +others designated to receive attribution, in any reasonable manner +requested by the Licensor (including by pseudonym if designated);

      2. +
      3. a copyright notice;

      4. +
      5. a notice that refers to this Public License;

      6. +
      7. a notice that refers to the disclaimer of warranties;

      8. +
      9. a URI or hyperlink to the Licensed Material to the extent reasonably +practicable;

      10. +
      +
      +
        +
      1. indicate if You modified the Licensed Material and retain an +indication of any previous modifications; and

      2. +
      3. indicate the Licensed Material is licensed under this Public License, +and include the text of, or the URI or hyperlink to, this Public +License.

      4. +
      +
      +
    2. +
    3. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner +based on the medium, means, and context in which You Share the Licensed +Material. For example, it may be reasonable to satisfy the conditions by +providing a URI or hyperlink to a resource that includes the required +information.

    4. +
    5. If requested by the Licensor, You must remove any of the information +required by Section 3(a)(1)(A) to the extent reasonably practicable.

    6. +
    7. If You Share Adapted Material You produce, the Adapter’s License You apply +must not prevent recipients of the Adapted Material from complying with +this Public License.

    8. +
    +
  2. +
+
+
+

Section 4 – Sui Generis Database Rights.

+

Where the Licensed Rights include Sui Generis Database Rights that apply to Your +use of the Licensed Material:

+
    +
  1. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, +reuse, reproduce, and Share all or a substantial portion of the contents of +the database;

  2. +
  3. if You include all or a substantial portion of the database contents in a +database in which You have Sui Generis Database Rights, then the database +in which You have Sui Generis Database Rights (but not its individual +contents) is Adapted Material; and

  4. +
  5. You must comply with the conditions in Section 3(a) if You Share all or a +substantial portion of the contents of the database.

  6. +
+

For the avoidance of doubt, this Section 4 supplements and does not replace +Your obligations under this Public License where the Licensed Rights include +other Copyright and Similar Rights.

+
+
+

Section 5 – Disclaimer of Warranties and Limitation of Liability.

+
    +
  1. Unless otherwise separately undertaken by the Licensor, to the extent +possible, the Licensor offers the Licensed Material as-is and as-available, +and makes no representations or warranties of any kind concerning the +Licensed Material, whether express, implied, statutory, or other. This +includes, without limitation, warranties of title, merchantability, +fitness for a particular purpose, non-infringement, absence of latent or +other defects, accuracy, or the presence or absence of errors, whether or +not known or discoverable. Where disclaimers of warranties are not allowed +in full or in part, this disclaimer may not apply to You.

  2. +
  3. To the extent possible, in no event will the Licensor be liable to You +on any legal theory (including, without limitation, negligence) or +otherwise for any direct, special, indirect, incidental, consequential, +punitive, exemplary, or other losses, costs, expenses, or damages arising +out of this Public License or use of the Licensed Material, even if the +Licensor has been advised of the possibility of such losses, costs, expenses, +or damages. Where a limitation of liability is not allowed in full or in +part, this limitation may not apply to You.

  4. +
  5. The disclaimer of warranties and limitation of liability provided above +shall be interpreted in a manner that, to the extent possible, most +closely approximates an absolute disclaimer and waiver of all liability.

  6. +
+
+
+

Section 6 – Term and Termination.

+
    +
  1. This Public License applies for the term of the Copyright and Similar Rights +licensed here. However, if You fail to comply with this Public License, then +Your rights under this Public License terminate automatically.

  2. +
  3. Where Your right to use the Licensed Material has terminated under +Section 6(a), it reinstates:

    +
      +
    1. automatically as of the date the violation is cured, provided it is cured +within 30 days of Your discovery of the violation; or

    2. +
    3. upon express reinstatement by the Licensor.

    4. +
    +

    For the avoidance of doubt, this Section 6(b) does not affect any right the +Licensor may have to seek remedies for Your violations of this Public License.

    +
  4. +
  5. For the avoidance of doubt, the Licensor may also offer the Licensed Material +under separate terms or conditions or stop distributing the Licensed Material +at any time; however, doing so will not terminate this Public License.

  6. +
  7. Sections 1, 5, 6, 7, and 8 survive termination of this Public License.

  8. +
+
+
+

Section 7 – Other Terms and Conditions.

+
    +
  1. The Licensor shall not be bound by any additional or different terms or +conditions communicated by You unless expressly agreed.

  2. +
  3. Any arrangements, understandings, or agreements regarding the Licensed +Material not stated herein are separate from and independent of the terms +and conditions of this Public License.

  4. +
+
+
+

Section 8 – Interpretation.

+
    +
  1. For the avoidance of doubt, this Public License does not, and shall not be +interpreted to, reduce, limit, restrict, or impose conditions on any use of +the Licensed Material that could lawfully be made without permission under +this Public License.

  2. +
  3. To the extent possible, if any provision of this Public License is deemed +unenforceable, it shall be automatically reformed to the minimum extent +necessary to make it enforceable. If the provision cannot be reformed, it +shall be severed from this Public License without affecting the +enforceability of the remaining terms and conditions.

  4. +
  5. No term or condition of this Public License will be waived and no failure to +comply consented to unless expressly agreed to by the Licensor.

  6. +
  7. Nothing in this Public License constitutes or may be interpreted as a +limitation upon, or waiver of, any privileges and immunities that apply to +the Licensor or You, including from the legal processes of any jurisdiction +or authority.

  8. +
+
+

Creative Commons is not a party to its public licenses. Notwithstanding, +Creative Commons may elect to apply one of its public licenses to material it +publishes and in those instances will be considered the “Licensor.” Except for +the limited purpose of indicating that material is shared under a Creative +Commons public license or as otherwise permitted by the Creative Commons +policies published at creativecommons.org/policies, +Creative Commons does not authorize the use of the trademark “Creative Commons” +or any other trademark or logo of Creative Commons without its prior written +consent including, without limitation, in connection with any unauthorized +modifications to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For the +avoidance of doubt, this paragraph does not form part of the public licenses.

+

Creative Commons may be contacted at creativecommons.org

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/DocCoverage/index.html b/DocCoverage/index.html new file mode 100644 index 00000000..8832320a --- /dev/null +++ b/DocCoverage/index.html @@ -0,0 +1,175 @@ + + + + + + + + + Documentation Coverage — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Documentation Coverage

+

Documentation coverage measures the presence of code documentation. It primarily counts for public language entities +like publicly visible constants and variables, parameters, types, functions, methods, classes, modules, packages, etc. +The documentation goal depends on the used coverage collection tool’s settings. E.g. usually, private language entities +are not required to be documented.

+

The documentation coverage metric in percent is a ratio of documented language entity versus all documentation worthy +langauge entities. A coverage of <100% indicates undocumented code.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Glossary.html b/Glossary.html new file mode 100644 index 00000000..c4ded7fd --- /dev/null +++ b/Glossary.html @@ -0,0 +1,194 @@ + + + + + + + + + Glossary — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Glossary

+
+
Coverage

code, functional, documentation, test

+
+
Code Coverage

tbd

+
+
Documentation Coverage

tbd

+
+
Functional Coverage

tbd

+
+
JUnit

Java Unit Testing framework.

+
+
Test Coverage

tbd

+
+
Test Entity

A Test Entity is an entity in the test entity hierarchy. It can be a Test Case, Test Suite or +Test Suite Summary. Some hierarchies (like JUnit) got extended by a Test Class.

+
+
Test Case

tbd

+
+
Test Class

tbd

+
+
Test Suite

tbd

+
+
Test Suite Summary

tbd

+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Installation.html b/Installation.html new file mode 100644 index 00000000..952e36dc --- /dev/null +++ b/Installation.html @@ -0,0 +1,375 @@ + + + + + + + + + Installation/Updates — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Installation/Updates

+
+

Using PIP to Install from PyPI

+

The following instruction are using PIP (Package Installer for Python) as a package manager and PyPI (Python Package +Index) as a source of Python packages.

+
+

Installing a Wheel Package from PyPI using PIP

+
+ +
+
# Basic pyTooling package
+pip3 install pyEDAA.Reports
+
+
+
+ +
+
# Basic pyTooling package
+pip install pyEDAA.Reports
+
+
+
+
+

Developers can install further dependencies for documentation generation (doc) or running unit tests (test) or +just all (all) dependencies.

+
+ +
+
+ +
+
+
# Install with dependencies to generate documentation
+pip3 install pyEDAA.Reports[doc]
+
+
+
+
+ +
+
+
# Install with dependencies to run unit tests
+pip3 install pyEDAA.Reports[test]
+
+
+
+
+ +
+
+
# Install with all developer dependencies
+pip install pyEDAA.Reports[all]
+
+
+
+
+
+
+ +
+
+ +
+
+
# Install with dependencies to generate documentation
+pip install pyEDAA.Reports[doc]
+
+
+
+
+ +
+
+
# Install with dependencies to run unit tests
+pip install pyEDAA.Reports[test]
+
+
+
+
+ +
+
+
# Install with all developer dependencies
+pip install pyEDAA.Reports[all]
+
+
+
+
+
+
+
+
+
+

Updating from PyPI using PIP

+
+ +
+
pip install -U pyEDAA.Reports
+
+
+
+ +
+
pip3 install -U pyEDAA.Reports
+
+
+
+
+
+
+

Uninstallation using PIP

+
+ +
+
pip uninstall pyEDAA.Reports
+
+
+
+ +
+
pip3 uninstall pyEDAA.Reports
+
+
+
+
+
+
+
+

Using setup.py (legacy)

+

See sections above on how to use PIP.

+
+

Installation using setup.py

+
setup.py install
+
+
+
+
+
+

Local Packaging and Installation via PIP

+

For development and bug fixing it might be handy to create a local wheel package and also install it locally on the +development machine. The following instructions will create a local wheel package (*.whl) and then use PIP to +install it. As a user might have a pyEDAA.Reports installation from PyPI, it’s recommended to uninstall any previous +pyEDAA.Reports packages. (This step is also needed if installing an updated local wheel file with same version number. +PIP will not detect a new version and thus not overwrite/reinstall the updated package contents.)

+

Ensure packaging requirements are installed.

+
+ +
+
cd <pyEDAA.Reports>
+
+# Package the code in a wheel (*.whl)
+python -m build --wheel
+
+# Uninstall the old package
+python -m pip uninstall -y pyEDAA.Reports
+
+# Install from wheel
+python -m pip install ./dist/pyEDAA.Reports-0.1.0-py3-none-any.whl
+
+
+
+ +
+
cd <pyEDAA.Reports>
+
+# Package the code in a wheel (*.whl)
+py -m build --wheel
+
+# Uninstall the old package
+py -m pip uninstall -y pyEDAA.Reports
+
+# Install from wheel
+py -m pip install .\dist\pyEDAA.Reports-0.1.0-py3-none-any.whl
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/License.html b/License.html new file mode 100644 index 00000000..74cdcd97 --- /dev/null +++ b/License.html @@ -0,0 +1,304 @@ + + + + + + + + + Apache License 2.0 — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Note

+

This is a local copy of the Apache License Version 2.0.

+
+
+

Attention

+

This Apache License, 2.0 applies to all source and configuration files of project, except documentation.

+
+
+

Apache License 2.0

+

Version 2.0, January 2004

+

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

+
+

1. Definitions.

+

“License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.

+

“Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.

+

“Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that +entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether +by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.

+

“You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.

+

“Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and +configuration files.

+

“Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types.

+

“Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is +included in or attached to the work (an example is provided in the Appendix below).

+

“Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.

+

“Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative +Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to +submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication +sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue +tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is +conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”

+

“Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work.

+
+ +
+

3. Grant of Patent License.

+

Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such +license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of +their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim +or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then +any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.

+
+
+

4. Redistribution.

+

You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions:

+
    +
  • You must give any other recipients of the Work or Derivative Works a copy of this License; and

  • +
  • You must cause any modified files to carry prominent notices stating that You changed the files; and

  • +
  • You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source +form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and

  • +
  • If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the +Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE +file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, +alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.

  • +
+

You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License.

+
+
+

5. Submission of Contributions.

+

Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any +separate license agreement you may have executed with Licensor regarding such Contributions.

+
+
+

6. Trademarks.

+

This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable +and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.

+
+
+

7. Disclaimer of Warranty.

+

Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, +MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and +assume any risks associated with Your exercise of permissions under this License.

+
+
+

8. Limitation of Liability.

+

In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or +consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages +for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been +advised of the possibility of such damages.

+
+
+

9. Accepting Warranty or Additional Liability.

+

While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other +liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole +responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.

+
+

Appendix: How to apply the Apache License to your work

+

To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets “[]” replaced with your own identifying +information. (Don’t include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or +class name and description of purpose be included on the same “printed page” as the copyright notice for easier identification within third-party archives.

+
Copyright [yyyy] [name of copyright owner]
+
+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.
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Logging/index.html b/Logging/index.html new file mode 100644 index 00000000..f71b38cc --- /dev/null +++ b/Logging/index.html @@ -0,0 +1,183 @@ + + + + + + + + + EDA Tool Logs — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

EDA Tool Logs

+
    +
  • Simulation logs

  • +
  • Synthesis logs

  • +
  • Implementation logs

  • +
+

Goals

+
    +
  • structured machine readable logs with labeling and classifications

  • +
  • extract resource utilisation

  • +
  • extract timing results

  • +
  • extract warnings, critical warnings, errors

  • +
  • check for critical behavior (e.g. generated latches, unreachable states, …)

  • +
  • log filtering

  • +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/TODO.html b/TODO.html new file mode 100644 index 00000000..ce1ca38f --- /dev/null +++ b/TODO.html @@ -0,0 +1,202 @@ + + + + + + + + + TODOs — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

TODOs

+
+

Todo

+

TestsuiteBase APIs

+
+

original entry

+
+

Todo

+

TestsuiteBase APIs

+
+

original entry

+
+

Todo

+

Planned feature.

+
+

original entry

+
+

Todo

+

TestsuiteBase APIs

+
+

original entry

+
+

Todo

+

TestsuiteBase APIs

+
+

original entry

+
+

Todo

+

Planned feature.

+
+

original entry

+
+

Todo

+

add license texts here

+
+

original entry

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Unittesting/DataModel.html b/Unittesting/DataModel.html new file mode 100644 index 00000000..e55d7d9f --- /dev/null +++ b/Unittesting/DataModel.html @@ -0,0 +1,891 @@ + + + + + + + + + Unified data model — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Unified data model

+

The unified data model for test entities (test summary, test suite, test case) implements a super-set of all (so far +known) unit test result summary file formats. pyEDAA.Report’s data model is a structural and functional cleanup of the +Ant JUnit data model. Naming has been cleaned up and missing features have been added.

+

As some of the JUnit XML dialects are too divergent from the original Ant + JUnit4 format, these dialects have an +independent test entity inheritance hierarchy. Nonetheless, instances of each data format can be converted to and from +the unified data model.

+
+
+
+
+
+
+
+ +
+

A test case is the leaf-element in the test entity hierarchy and describes an individual test run. +Test cases are grouped by test suites.

+
+
+
+
+
+ +
+

A test suite is a group of test cases and/or test suites. Test suites itself can be grouped by test +suites. The test suite hierarchy’s root element is a test suite summary.

+
+
+
+
+
+ +
+

The test suite summary is derived from test suite and defines the root of the test suite hierarchy.

+
+
+
+
+
+ +
+

The document is derived from a test suite summary and represents a file containing a test suite +summary.

+
+
+
+
+
+
+
+
+        graph TD;
+  doc[Document]
+  sum[Summary]
+  ts1[Testsuite]
+  ts2[Testsuite]
+  ts21[Testsuite]
+  tc11[Testcase]
+  tc12[Testcase]
+  tc13[Testcase]
+  tc21[Testcase]
+  tc22[Testcase]
+  tc211[Testcase]
+  tc212[Testcase]
+  tc213[Testcase]
+
+  doc:::root -.-> sum:::summary
+  sum --> ts1:::suite
+  sum --> ts2:::suite
+  ts2 --> ts21:::suite
+  ts1 --> tc11:::case
+  ts1 --> tc12:::case
+  ts1 --> tc13:::case
+  ts2 --> tc21:::case
+  ts2 --> tc22:::case
+  ts21 --> tc211:::case
+  ts21 --> tc212:::case
+  ts21 --> tc213:::case
+
+  classDef root fill:#4dc3ff
+  classDef summary fill:#80d4ff
+  classDef suite fill:#b3e6ff
+  classDef case fill:#eeccff
+    
+
+
+
+

Testcase Status

+
+
+
+

TestcaseStatus and TestsuiteStatus are +flag enumerations to describe the overall status of a test case or test suite.

+
+
Unknown

tbd

+
+
Excluded

tbd

+
+
Skipped

tbd

+
+
Weak

tbd

+
+
Passed

tbd

+
+
Failed

tbd

+
+
Inverted

tbd

+
+
Warned

tbd

+
+
Errored

tbd

+
+
Failed

tbd

+
+
SetupError

tbd

+
+
TearDownError

tbd

+
+
Inconsistent

tbd

+
+
+
+
+
@export
+class TestcaseStatus(Flag):
+  """A flag enumeration describing the status of a test case."""
+  Unknown =    0                         #: Testcase status is uninitialized and therefore unknown.
+  Excluded =   1                         #: Testcase was permanently excluded / disabled
+  Skipped =    2                         #: Testcase was temporarily skipped (e.g. based on a condition)
+  Weak =       4                         #: No assertions were recorded.
+  Passed =     8                         #: A passed testcase, because all assertions were successful.
+  Failed =    16                         #: A failed testcase due to at least one failed assertion.
+
+  Mask = Excluded | Skipped | Weak | Passed | Failed
+
+  Inverted = 128                         #: To mark inverted results
+  UnexpectedPassed = Failed | Inverted
+  ExpectedFailed =   Passed | Inverted
+
+  Warned =  1024                         #: Runtime warning
+  Errored = 2048                         #: Runtime error (mostly caught exceptions)
+  Aborted = 4096                         #: Uncaught runtime exception
+
+  SetupError =     8192                  #: Preparation / compilation error
+  TearDownError = 16384                  #: Cleanup error / resource release error
+  Inconsistent = 32768                   #: Dataset is inconsistent
+
+  Flags = Warned | Errored | Aborted | SetupError | TearDownError | Inconsistent
+
+
+
@export
+class TestsuiteStatus(Flag):
+  """A flag enumeration describing the status of a test suite."""
+  Unknown =    0
+  Excluded =   1                         #: Testcase was permanently excluded / disabled
+  Skipped =    2                         #: Testcase was temporarily skipped (e.g. based on a condition)
+  Empty =      4                         #: No tests in suite
+  Passed =     8                         #: Passed testcase, because all assertions succeeded
+  Failed =    16                         #: Failed testcase due to failing assertions
+
+  Mask = Excluded | Skipped | Empty | Passed | Failed
+
+  Inverted = 128                         #: To mark inverted results
+  UnexpectedPassed = Failed | Inverted
+  ExpectedFailed =   Passed | Inverted
+
+  Warned =  1024                         #: Runtime warning
+  Errored = 2048                         #: Runtime error (mostly caught exceptions)
+  Aborted = 4096                         #: Uncaught runtime exception
+
+  SetupError =     8192                  #: Preparation / compilation error
+  TearDownError = 16384                  #: Cleanup error / resource release error
+
+  Flags = Warned | Errored | Aborted | SetupError | TearDownError
+
+
+
+
+
+
+
+

Testcase

+
+
+
+

A Testcase is the leaf-element in the test entity hierarchy and describes an +individual test run. Besides a test case status, it also contains statistics like the start time or the test +duration. Test cases are grouped by test suites and need to be unique per parent test suite.

+

A test case (or its base classes) implements the following properties and methods:

+
+
Parent

The test case has a reference to it’s parent test suite in the hierarchy. By iterating parent references, the +root element (test suite summary) be be found, which has no parent reference (None).

+
+
Name

The test case has a name. This name must be unique per hierarchy parent, but can exist multiple times in the +overall test hierarchy.

+

In case the data format uses hierarchical names like pyEDAA.Reports.CLI.Application, the name is split at +the separator and multiple hierarchy levels (test suites) are created in the unified data model. To be able to +recreate such an hierarchical name, TestsuiteKind is applied accordingly +to test suite’s Kind field.

+
+
StartTime

The test case stores a time when the individual test run was started. In combination with +TotalDuration, the end time can be calculated. If the start time is +unknown, set this value to None.

+
+
SetupDuration, TestDuration, TeardownDuration, TotalDuration

The test case has fields to capture the setup duration, test run duration and teardown duration. The sum of all +durations is provided by total duration.

+

TotalDuration := SetupDuration + TestDuration + TeardownDuration

+

The setup duration is the time spend on setting up a test run. If the setup duration can’t be +distinguished from the test’s runtime, set this value to None.

+

The test’s runtime without setup and teardown portions is captured by test duration. If the duration is +unknown, set this value to None.

+

The teardown duration of a test run is the time spend on tearing down a test run. If the teardown +duration can’t be distinguished from the test’s runtime, set this value to None.

+

The test case has a field total duration to sum up setup duration, test duration and teardown duration. +If the duration is unknown, this value will be None.

+
+
WarningCount, ErrorCount, FatalCount

The test case counts for warnings, errors and fatal errors observed in a test run while the test was executed.

+
+
__len__(), __getitem__(), __setitem__(), __delitem__(), __contains__(), __iter__()

The test case implements a dictionary interface, so arbitrary key-value pairs can be annotated per test entity.

+
+
Status

The overall status of a test case.

+

See also: Testcase Status.

+
+
AssertionCount, PassedAssertionCount, FailedAssertionCount

The assertion count represents the overall number of assertions (checks) in a test case. It can be +distinguished into passed assertions and failed assertions. If it can’t be distinguished, set +passed and failed assertions to None.

+

AssertionCount := PassedAssertionCount + FailedAssertionCount

+
+
Copy()

tbd

+
+
Aggregate()

Aggregate (recalculate) all durations, warnings, errors, assertions, etc.

+
+
__str__()

tbd

+
+
+
+
+
@export
+class Testcase(Base):
+   def __init__(
+      self,
+      name: str,
+      startTime: Nullable[datetime] = None,
+      setupDuration: Nullable[timedelta] = None,
+      testDuration: Nullable[timedelta] = None,
+      teardownDuration: Nullable[timedelta] = None,
+      totalDuration:  Nullable[timedelta] = None,
+      status: TestcaseStatus = TestcaseStatus.Unknown,
+      assertionCount: Nullable[int] = None,
+      failedAssertionCount: Nullable[int] = None,
+      passedAssertionCount: Nullable[int] = None,
+      warningCount: int = 0,
+      errorCount: int = 0,
+      fatalCount: int = 0,
+      parent: Nullable["Testsuite"] = None
+   ):
+     ...
+
+   @readonly
+   def Parent(self) -> Nullable["Testsuite"]:
+     ...
+
+   @readonly
+   def Name(self) -> str:
+     ...
+
+   @readonly
+   def StartTime(self) -> Nullable[datetime]:
+     ...
+
+   @readonly
+   def SetupDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TestDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TeardownDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TotalDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def WarningCount(self) -> int:
+     ...
+
+   @readonly
+   def ErrorCount(self) -> int:
+     ...
+
+   @readonly
+   def FatalCount(self) -> int:
+     ...
+
+   def __len__(self) -> int:
+     ...
+
+   def __getitem__(self, key: str) -> Any:
+     ...
+
+   def __setitem__(self, key: str, value: Any) -> None:
+     ...
+
+   def __delitem__(self, key: str) -> None:
+     ...
+
+   def __contains__(self, key: str) -> bool:
+     ...
+
+   def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
+     ...
+
+   @readonly
+   def Status(self) -> TestcaseStatus:
+     ...
+
+   @readonly
+   def AssertionCount(self) -> int:
+     ...
+
+   @readonly
+   def FailedAssertionCount(self) -> int:
+     ...
+
+   @readonly
+   def PassedAssertionCount(self) -> int:
+     ...
+
+   def Copy(self) -> "Testcase":
+     ...
+
+   def Aggregate(self, strict: bool = True) -> TestcaseAggregateReturnType:
+     ...
+
+   def __str__(self) -> str:
+     ...
+
+
+
+
+
+
+
+

Testsuite

+
+
+
+

A Testsuite is a grouping element in the test entity hierarchy and describes +a group of test runs. Besides a list of test cases and a test suite status, it also contains statistics like the +start time or the test duration for the group of tests. Test suites are grouped by other test suites or a test +suite summary and need to be unique per parent test suite.

+

A test suite (or its base classes) implements the following properties and methods:

+
+
Parent

The test suite has a reference to it’s parent test entity in the hierarchy. By iterating parent references, the +root element (test suite summary) be be found, which has no parent reference (None).

+
+
Name

The test suite has a name. This name must be unique per hierarchy parent, but can exist multiple times in the +overall test hierarchy.

+

In case the data format uses hierarchical names like pyEDAA.Reports.CLI.Application, the name is split at +the separator and multiple hierarchy levels (test suites) are created in the unified data model. To be able to +recreate such an hierarchical name, TestsuiteKind is applied accordingly +to test suite’s Kind field.

+
+
StartTime

The test suite stores a time when the first test run was started. In combination with +TotalDuration, the end time can be calculated. If the start time is +unknown, set this value to None.

+
+
SetupDuration, TestDuration, TeardownDuration, TotalDuration

The test suite has fields to capture the suite’s setup duration, test group run duration and suite’s +teardown duration. The sum of all durations is provided by total duration.

+

TotalDuration := SetupDuration + TestDuration + TeardownDuration

+

The setup duration is the time spend on setting up a test suite. If the setup duration can’t be +distinguished from the test group’s runtime, set this value to None.

+

The test group’s runtime without setup and teardown portions is captured by test duration. If the +duration is unknown, set this value to None.

+

The teardown duration of a test suite is the time spend on tearing down a test suite. If the teardown +duration can’t be distinguished from the test group’s runtime, set this value to None.

+

The test suite has a field total duration to sum up setup duration, test duration and teardown duration. +If the duration is unknown, this value will be None.

+
+
WarningCount, ErrorCount, FatalCount

The test suite counts for warnings, errors and fatal errors observed in a test suite while the tests were +executed.

+
+
__len__(), __getitem__(), __setitem__(), __delitem__(), __contains__(), __iter__()

The test suite implements a dictionary interface, so arbitrary key-value pairs can be annotated.

+
+
+
+

Todo

+

TestsuiteBase APIs

+
+
+
Testcases

tbd

+
+
TestcaseCount

tbd

+
+
AssertionCount

The overall number of assertions (checks) in a test case.

+
+
Aggregate()

Aggregate (recalculate) all durations, warnings, errors, assertions, etc.

+
+
Iterate()

tbd

+
+
__str__()

tbd

+
+
+
+
+
@export
+class Testsuite(TestsuiteBase[TestsuiteType]):
+   def __init__(
+      self,
+      name: str,
+      kind: TestsuiteKind = TestsuiteKind.Logical,
+      startTime: Nullable[datetime] = None,
+      setupDuration: Nullable[timedelta] = None,
+      testDuration: Nullable[timedelta] = None,
+      teardownDuration: Nullable[timedelta] = None,
+      totalDuration:  Nullable[timedelta] = None,
+      status: TestsuiteStatus = TestsuiteStatus.Unknown,
+      warningCount: int = 0,
+      errorCount: int = 0,
+      fatalCount: int = 0,
+      testsuites: Nullable[Iterable[TestsuiteType]] = None,
+      testcases: Nullable[Iterable["Testcase"]] = None,
+      parent: Nullable[TestsuiteType] = None
+   ):
+     ...
+
+   @readonly
+   def Parent(self) -> Nullable["Testsuite"]:
+     ...
+
+   @readonly
+   def Name(self) -> str:
+     ...
+
+   @readonly
+   def StartTime(self) -> Nullable[datetime]:
+     ...
+
+   @readonly
+   def SetupDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TestDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TeardownDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TotalDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def WarningCount(self) -> int:
+     ...
+
+   @readonly
+   def ErrorCount(self) -> int:
+     ...
+
+   @readonly
+   def FatalCount(self) -> int:
+     ...
+
+   def __len__(self) -> int:
+     ...
+
+   def __getitem__(self, key: str) -> Any:
+     ...
+
+   def __setitem__(self, key: str, value: Any) -> None:
+     ...
+
+   def __delitem__(self, key: str) -> None:
+     ...
+
+   def __contains__(self, key: str) -> bool:
+     ...
+
+   def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
+     ...
+
+   # TestsuiteBase API
+
+
+   @readonly
+   def Testcases(self) -> Dict[str, "Testcase"]:
+     ...
+
+   @readonly
+   def TestcaseCount(self) -> int:
+     ...
+
+   @readonly
+   def AssertionCount(self) -> int:
+     ...
+
+   def Aggregate(self, strict: bool = True) -> TestsuiteAggregateReturnType:
+     ...
+
+   def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]:
+     ...
+
+   def __str__(self) -> str:
+     ...
+
+
+
+
+
+
+
+

TestsuiteSummary

+
+
+
+

A TestsuiteSummary is the root element in the test entity hierarchy and +describes a group of test suites as well as overall statistics for the whole set of test cases. A test suite +summary is derived for the same base-class as a test suite, thus they share almost all properties and methods.

+

A test suite summary (or its base classes) implements the following properties and methods:

+
+
Parent

The test suite summary has a parent reference, but as the root element in the test entity hierarchy, its always +None.

+
+
Name

The test suite summary has a name.

+
+
StartTime

The test suite summary stores a time when the first test runs was started. In combination with +TotalDuration, the end time can be calculated. If the start time is +unknown, set this value to None.

+
+
SetupDuration, TestDuration, TeardownDuration, TotalDuration

The test suite summary has fields to capture the suite summary’s setup duration, overall run duration and +suite summary’s teardown duration. The sum of all durations is provided by total duration.

+

TotalDuration := SetupDuration + TestDuration + TeardownDuration

+

The setup duration is the time spend on setting up an overall test run. If the setup duration can’t be +distinguished from the test’s runtimes, set this value to None.

+

The test suite summary’s runtime without setup and teardown portions is captured by test duration. If +the duration is unknown, set this value to None.

+

The teardown duration of a test suite summary is the time spend on tearing down a test suite summary. If +the teardown duration can’t be distinguished from the test’s runtimes, set this value to None.

+

The test suite summary has a field total duration to sum up setup duration, overall run duration and +teardown duration. If the duration is unknown, this value will be None.

+
+
WarningCount, ErrorCount, FatalCount

The test suite summary counts for warnings, errors and fatal errors observed in a test suite summary while the +tests were executed.

+
+
__len__(), __getitem__(), __setitem__(), __delitem__(), __contains__(), __iter__()

The test suite summary implements a dictionary interface, so arbitrary key-value pairs can be annotated.

+
+
+
+

Todo

+

TestsuiteBase APIs

+
+
+
Aggregate()

tbd

+
+
Iterate()

tbd

+
+
__str__()

tbd

+
+
+
+
+
@export
+class TestsuiteSummary(TestsuiteBase[TestsuiteType]):
+   def __init__(
+      self,
+      name: str,
+      startTime: Nullable[datetime] = None,
+      setupDuration: Nullable[timedelta] = None,
+      testDuration: Nullable[timedelta] = None,
+      teardownDuration: Nullable[timedelta] = None,
+      totalDuration:  Nullable[timedelta] = None,
+      status: TestsuiteStatus = TestsuiteStatus.Unknown,
+      warningCount: int = 0,
+      errorCount: int = 0,
+      fatalCount: int = 0,
+      testsuites: Nullable[Iterable[TestsuiteType]] = None,
+      parent: Nullable[TestsuiteType] = None
+   ):
+     ...
+
+   @readonly
+   def Parent(self) -> Nullable["Testsuite"]:
+     ...
+
+   @readonly
+   def Name(self) -> str:
+     ...
+
+   @readonly
+   def StartTime(self) -> Nullable[datetime]:
+     ...
+
+   @readonly
+   def SetupDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TestDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TeardownDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TotalDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def WarningCount(self) -> int:
+     ...
+
+   @readonly
+   def ErrorCount(self) -> int:
+     ...
+
+   @readonly
+   def FatalCount(self) -> int:
+     ...
+
+   def __len__(self) -> int:
+     ...
+
+   def __getitem__(self, key: str) -> Any:
+     ...
+
+   def __setitem__(self, key: str, value: Any) -> None:
+     ...
+
+   def __delitem__(self, key: str) -> None:
+     ...
+
+   def __contains__(self, key: str) -> bool:
+     ...
+
+   def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
+     ...
+
+   # TestsuiteBase API
+
+   def Aggregate(self) -> TestsuiteAggregateReturnType:
+     ...
+
+   def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]:
+     ...
+
+   def __str__(self) -> str:
+     ...
+
+
+
+
+
+
+
+

Document

+
+
+
+

A Document is a mixin-class …

+
+
Path

tbd

+
+
AnalysisDuration

tbd

+
+
ModelConversionDuration

tbd

+
+
Analyze()

tbd

+
+
Convert()

tbd

+
+
+
+
+
@export
+class Document(metaclass=ExtendedType, mixin=True):
+   def __init__(self, path: Path):
+     ...
+
+   @readonly
+   def Path(self) -> Path:
+     ...
+
+   @readonly
+   def AnalysisDuration(self) -> timedelta:
+     ...
+
+   @readonly
+   def ModelConversionDuration(self) -> timedelta:
+     ...
+
+   @abstractmethod
+   def Analyze(self) -> None:
+     ...
+
+   @abstractmethod
+   def Convert(self):
+     ...
+
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Unittesting/Features.html b/Unittesting/Features.html new file mode 100644 index 00000000..aeeec673 --- /dev/null +++ b/Unittesting/Features.html @@ -0,0 +1,634 @@ + + + + + + + + + Features — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Features

+
+

Create test entities

+
+
+
+

The hierarchy of test entities (test cases, test suites and test summaries) can be constructed top-down or +bottom-up.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting import Testsuite, Testcase
+
+# Top-down
+ts1 = Testsuite("ts1")
+
+tc = Testcase("tc", parent=ts)
+
+# Bottom-up
+tc1 = Testcase("tc1")
+tc2 = Testcase("tc2")
+
+ts2 = Testsuite("ts2", testcases=(tc1, tc2))
+
+# ts.AddTestcase(...)
+tc3 = Testcase("tc3")
+tc4 = Testcase("tc4")
+
+ts3 = Testsuite("ts3")
+ts3.AddTestcase(tc3)
+ts3.AddTestcase(tc4)
+
+# ts.AddTestcases(...)
+tc3 = Testcase("tc3")
+tc4 = Testcase("tc4")
+
+ts3 = Testsuite("ts3")
+ts3.AddTestcases((tc3, tc4))
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting import Testsuite, TestsuiteSummary
+
+# Top-down
+ts = Testsuite("ts")
+
+ts1 = Testsuite("ts1", parent=tss)
+
+# Bottom-up
+ts2 = Testsuite("ts2")
+ts3 = Testsuite("ts3")
+
+ts4 = Testsuite("ts4", testsuites=(ts2, ts3))
+
+# ts.AddTestsuite(...)
+ts5 = Testcase("ts5")
+ts6 = Testcase("ts6")
+
+ts7 = Testsuite("ts7")
+ts7.AddTestsuite(ts5)
+ts7.AddTestsuite(ts6)
+
+# ts.AddTestsuites(...)
+ts8 = Testcase("ts8")
+ts9 = Testcase("ts9")
+
+ts10 = Testsuite("ts10")
+ts10.AddTestsuites((ts8, ts9))
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting import Testsuite, TestsuiteSummary
+
+# Top-down
+
+# Bottom-up
+
+
+
+
+
+
+
+
+
+

Reading unittest reports

+
+
+
+

A JUnit XML test report summary file can be read by creating an instance of the Document +class. Because JUnit has so many dialects, a derived subclass for the dialect might be required. By choosing the +right Document class, also the XML schema for XML schema validation gets pre-selected.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit import Document
+
+xmlReport = Path("AntJUnit4-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+xmlReport = Path("CTest-JUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+xmlReport = Path("GoogleTest-JUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+xmlReport = Path("pyTest-JUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

Converting unittest reports

+
+
+
+

Any JUnit dialect specific data model can be converted to the generic hierarchy of test entities.

+
+

Note

+

This conversion is identical for all derived dialects.

+
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+# Read from XML file
+xmlReport = Path("JUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert to a tree
+rootNode = doc.ToTree()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+newDoc.Write()
+
+
+
+
+
+
+
+
+
+

Annotations

+
+
+
+

Every test entity can be annotated with arbitrary key-value pairs.

+
+
+
+ +
+
# Add annotate a key-value pair
+testcase["key"] = value
+
+# Update existing annotation with new value
+testcase["key"] = newValue
+
+# Check if key exists
+if "key" in testcase:
+  pass
+
+# Access annoation by key
+value = testcase["key"]
+
+# Get number of annotations
+annotationCount = len(testcase)
+
+# Delete annotation
+del testcase["key"]
+
+# Iterate annotations
+for key, value in testcases:
+   pass
+
+
+
+ +
+
# Add annotate a key-value pair
+testsuite["key"] = value
+
+# Update existing annotation with new value
+testsuite["key"] = newValue
+
+# Check if key exists
+if "key" in testsuite:
+  pass
+
+# Access annoation by key
+value = testsuite["key"]
+
+# Get number of annotations
+annotationCount = len(testsuite)
+
+# Delete annotation
+del testsuite["key"]
+
+# Iterate annotations
+for key, value in testsuite:
+   pass
+
+
+
+ +
+
# Add annotate a key-value pair
+testsuiteSummary["key"] = value
+
+# Update existing annotation with new value
+testsuiteSummary["key"] = newValue
+
+# Check if key exists
+if "key" in testsuiteSummary:
+  pass
+
+# Access annoation by key
+value = testsuiteSummary["key"]
+
+# Get number of annotations
+annotationCount = len(testsuiteSummary)
+
+# Delete annotation
+del testsuiteSummary["key"]
+
+# Iterate annotations
+for key, value in testsuiteSummary:
+   pass
+
+
+
+
+
+
+
+
+
+

Merging unittest reports

+
+
+
+

add description here

+
+
+
+ +
+
# add code here
+
+
+
+
+
+
+
+
+
+

Concatenate unittest reports

+
+

Todo

+

Planned feature.

+
+
+
+

Transforming the reports’ hierarchy

+
+

pytest specific transformations

+
+
+
+

add description here

+
+
+
+ +
+
# add code here
+
+
+
+
+
+
+
+
+
+
+

Writing unittest reports

+
+
+
+

A test suite summary can be converted to a document of any JUnit dialect. Internally a deep-copy is created to +convert from a hierarchy of the unified test entities to a hierarchy of specific test entities (e.g. JUnit +entities).

+

When the document was created, it can be written to disk.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Unittesting/JUnitDataModel.html b/Unittesting/JUnitDataModel.html new file mode 100644 index 00000000..7cd6dd4a --- /dev/null +++ b/Unittesting/JUnitDataModel.html @@ -0,0 +1,621 @@ + + + + + + + + + JUnit Data Model — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

JUnit Data Model

+
+
+
+
+
+
+
+ +
+

A test case is the leaf-element in the test entity hierarchy and describes an individual test run. +Test cases are grouped by test classes.

+
+
+
+
+
+ +
+

A test class is the mid-level element in the test entity hierarchy and describes a group of test +runs. Test classes are grouped by test suites.

+
+
+
+
+
+ +
+

A test suite is a group of test classes. Test suites are grouped by a test suite summary.

+
+
+
+
+
+ +
+

The test suite summary is derived from test suite and defines the root of the test suite hierarchy.

+
+
+
+
+
+ +
+

The document is derived from a test suite summary and represents a file containing a test suite +summary.

+
+
+
+
+
+ +
+

The JUnit format is not well defined, thus multiple dialects developed over time.

+
+
+
+
+
+
+
+
+        graph TD;
+  doc[Document]
+  sum[Summary]
+  ts1[Testsuite]
+  ts11[Testsuite]
+  ts2[Testsuite]
+
+  tc111[Testclass]
+  tc112[Testclass]
+  tc23[Testclass]
+
+  tc1111[Testcase]
+  tc1112[Testcase]
+  tc1113[Testcase]
+  tc1121[Testcase]
+  tc1122[Testcase]
+  tc231[Testcase]
+  tc232[Testcase]
+  tc233[Testcase]
+
+  doc:::root -.-> sum:::summary
+  sum --> ts1:::suite
+  sum ---> ts2:::suite
+  ts1 --> ts11:::suite
+
+  ts11 --> tc111:::cls
+  ts11 --> tc112:::cls
+  ts2  --> tc23:::cls
+
+  tc111 --> tc1111:::case
+  tc111 --> tc1112:::case
+  tc111 --> tc1113:::case
+  tc112 --> tc1121:::case
+  tc112 --> tc1122:::case
+  tc23 --> tc231:::case
+  tc23 --> tc232:::case
+  tc23 --> tc233:::case
+
+  classDef root fill:#4dc3ff
+  classDef summary fill:#80d4ff
+  classDef suite fill:#b3e6ff
+  classDef cls fill:#ff9966
+  classDef case fill:#eeccff
+    
+
+
+
+

Testcase

+
+
+

Testclass

+
+
+

Testsuite

+
+
+

TestsuiteSummary

+
+
+

Document

+
+
+
+

JUnit Dialects

+

As the JUnit XML format was not well specified and no XML Schema Definition (XSD) was provided, many variants and +dialects (and simplifications) were created by the various frameworks emitting JUnit XML files.

+

JUnit Dialect Comparison

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Feature

Any JUnit

Ant + JUnit4

CTest JUnit

GoogleTest JUnit

pyTest JUnit

Root element

testsuites

testsuite

testsuite

testsuites

testsuites

Supports properties

Testcase status

more status values

+
+

Any JUnit

+
+
+
+

The Any JUnit format uses a relaxed XML schema definition aiming to parse many JUnit XML dialects, which use a +<testsuites> root element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

Ant + JUnit4

+
+
+
+

The original JUnit format created by Ant for JUnit4 +uses <testsuite> as a root element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document
+
+xmlReport = Path("AntJUnit4-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

CTest JUnit

+
+
+
+

The CTest JUnit format written by CTest uses <testsuite> as a root +element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+xmlReport = Path("CTestJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

GoogleTest JUnit

+
+
+
+

The GoogleTest JUnit format written by GoogleTest (sometimes GTest) +uses <testsuites> as a root element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+xmlReport = Path("GoogleTestJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

pyTest JUnit

+
+
+
+

The pyTest JUnit format written by pyTest uses <testsuites> as a +root element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+xmlReport = Path("PyTestJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Unittesting/OSVVMDataModel.html b/Unittesting/OSVVMDataModel.html new file mode 100644 index 00000000..e69f7319 --- /dev/null +++ b/Unittesting/OSVVMDataModel.html @@ -0,0 +1,167 @@ + + + + + + + + + OSVVM — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

OSVVM

+

Open Source VHDL Verification Methodology writes test results as YAML files for its +internal data model storage. Some YAML files are written by the VHDL code of the verification framework, others are +written by OSVVM-Scripts as a test runner.

+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/Unittesting/index.html b/Unittesting/index.html new file mode 100644 index 00000000..390b8244 --- /dev/null +++ b/Unittesting/index.html @@ -0,0 +1,2035 @@ + + + + + + + + + Unittesting — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Unittesting

+

pyEDAA.Reports provides a unified and generic unittest summary data model. The data model allows the description of +testcases grouped in testsuites. Testsuites can be nested in other testsuites. The data model’s root element is a +special testsuite called testsuite summary. It contains only testsuites, but no testcases.

+

The data model can be filled from various sources like Ant JUnit test reports or OSVVM testsuite summaries (more +to be added). Many programming languages and/or unit testing frameworks support exporting results in the Ant JUnit +format. See below for supported formats and their variations (dialects).

+
+

Attention

+

The so called JUnit XML format is the weakest file format and standard ever seen. At first was not created by JUnit +(version 4). It was added by the built system Ant, but it’s not called Ant XML format nor Ant JUnit XML format. The +latest JUnit 5 uses a completely different format called open test reporting. As +JUnit is not the formats author, no file format documentation nor XML schema was provided. Also Ant isn’t providing +any file format documentation or XML schema. Various Ant JUnit XML adopters have tried to reverse engineer a +description and XML schemas, but unfortunately many are not even compatible to each other.

+
+
+

Unified data model

+

The unified data model for test entities (test summary, test suite, test case) implements a super-set of all (so far +known) unit test result summary file formats. pyEDAA.Report’s data model is a structural and functional cleanup of the +Ant JUnit data model. Naming has been cleaned up and missing features have been added.

+

As some of the JUnit XML dialects are too divergent from the original Ant + JUnit4 format, these dialects have an +independent test entity inheritance hierarchy. Nonetheless, instances of each data format can be converted to and from +the unified data model.

+
+
+
+
+
+
+
+ +
+

A test case is the leaf-element in the test entity hierarchy and describes an individual test run. +Test cases are grouped by test suites.

+
+
+
+
+
+ +
+

A test suite is a group of test cases and/or test suites. Test suites itself can be grouped by test +suites. The test suite hierarchy’s root element is a test suite summary.

+
+
+
+
+
+ +
+

The test suite summary is derived from test suite and defines the root of the test suite hierarchy.

+
+
+
+
+
+ +
+

The document is derived from a test suite summary and represents a file containing a test suite +summary.

+
+
+
+
+
+
+
+
+        graph TD;
+  doc[Document]
+  sum[Summary]
+  ts1[Testsuite]
+  ts2[Testsuite]
+  ts21[Testsuite]
+  tc11[Testcase]
+  tc12[Testcase]
+  tc13[Testcase]
+  tc21[Testcase]
+  tc22[Testcase]
+  tc211[Testcase]
+  tc212[Testcase]
+  tc213[Testcase]
+
+  doc:::root -.-> sum:::summary
+  sum --> ts1:::suite
+  sum --> ts2:::suite
+  ts2 --> ts21:::suite
+  ts1 --> tc11:::case
+  ts1 --> tc12:::case
+  ts1 --> tc13:::case
+  ts2 --> tc21:::case
+  ts2 --> tc22:::case
+  ts21 --> tc211:::case
+  ts21 --> tc212:::case
+  ts21 --> tc213:::case
+
+  classDef root fill:#4dc3ff
+  classDef summary fill:#80d4ff
+  classDef suite fill:#b3e6ff
+  classDef case fill:#eeccff
+    
+
+
+
+

Testcase Status

+
+
+
+

TestcaseStatus and TestsuiteStatus are +flag enumerations to describe the overall status of a test case or test suite.

+
+
Unknown

tbd

+
+
Excluded

tbd

+
+
Skipped

tbd

+
+
Weak

tbd

+
+
Passed

tbd

+
+
Failed

tbd

+
+
Inverted

tbd

+
+
Warned

tbd

+
+
Errored

tbd

+
+
Failed

tbd

+
+
SetupError

tbd

+
+
TearDownError

tbd

+
+
Inconsistent

tbd

+
+
+
+
+
@export
+class TestcaseStatus(Flag):
+  """A flag enumeration describing the status of a test case."""
+  Unknown =    0                         #: Testcase status is uninitialized and therefore unknown.
+  Excluded =   1                         #: Testcase was permanently excluded / disabled
+  Skipped =    2                         #: Testcase was temporarily skipped (e.g. based on a condition)
+  Weak =       4                         #: No assertions were recorded.
+  Passed =     8                         #: A passed testcase, because all assertions were successful.
+  Failed =    16                         #: A failed testcase due to at least one failed assertion.
+
+  Mask = Excluded | Skipped | Weak | Passed | Failed
+
+  Inverted = 128                         #: To mark inverted results
+  UnexpectedPassed = Failed | Inverted
+  ExpectedFailed =   Passed | Inverted
+
+  Warned =  1024                         #: Runtime warning
+  Errored = 2048                         #: Runtime error (mostly caught exceptions)
+  Aborted = 4096                         #: Uncaught runtime exception
+
+  SetupError =     8192                  #: Preparation / compilation error
+  TearDownError = 16384                  #: Cleanup error / resource release error
+  Inconsistent = 32768                   #: Dataset is inconsistent
+
+  Flags = Warned | Errored | Aborted | SetupError | TearDownError | Inconsistent
+
+
+
@export
+class TestsuiteStatus(Flag):
+  """A flag enumeration describing the status of a test suite."""
+  Unknown =    0
+  Excluded =   1                         #: Testcase was permanently excluded / disabled
+  Skipped =    2                         #: Testcase was temporarily skipped (e.g. based on a condition)
+  Empty =      4                         #: No tests in suite
+  Passed =     8                         #: Passed testcase, because all assertions succeeded
+  Failed =    16                         #: Failed testcase due to failing assertions
+
+  Mask = Excluded | Skipped | Empty | Passed | Failed
+
+  Inverted = 128                         #: To mark inverted results
+  UnexpectedPassed = Failed | Inverted
+  ExpectedFailed =   Passed | Inverted
+
+  Warned =  1024                         #: Runtime warning
+  Errored = 2048                         #: Runtime error (mostly caught exceptions)
+  Aborted = 4096                         #: Uncaught runtime exception
+
+  SetupError =     8192                  #: Preparation / compilation error
+  TearDownError = 16384                  #: Cleanup error / resource release error
+
+  Flags = Warned | Errored | Aborted | SetupError | TearDownError
+
+
+
+
+
+
+
+

Testcase

+
+
+
+

A Testcase is the leaf-element in the test entity hierarchy and describes an +individual test run. Besides a test case status, it also contains statistics like the start time or the test +duration. Test cases are grouped by test suites and need to be unique per parent test suite.

+

A test case (or its base classes) implements the following properties and methods:

+
+
Parent

The test case has a reference to it’s parent test suite in the hierarchy. By iterating parent references, the +root element (test suite summary) be be found, which has no parent reference (None).

+
+
Name

The test case has a name. This name must be unique per hierarchy parent, but can exist multiple times in the +overall test hierarchy.

+

In case the data format uses hierarchical names like pyEDAA.Reports.CLI.Application, the name is split at +the separator and multiple hierarchy levels (test suites) are created in the unified data model. To be able to +recreate such an hierarchical name, TestsuiteKind is applied accordingly +to test suite’s Kind field.

+
+
StartTime

The test case stores a time when the individual test run was started. In combination with +TotalDuration, the end time can be calculated. If the start time is +unknown, set this value to None.

+
+
SetupDuration, TestDuration, TeardownDuration, TotalDuration

The test case has fields to capture the setup duration, test run duration and teardown duration. The sum of all +durations is provided by total duration.

+

TotalDuration := SetupDuration + TestDuration + TeardownDuration

+

The setup duration is the time spend on setting up a test run. If the setup duration can’t be +distinguished from the test’s runtime, set this value to None.

+

The test’s runtime without setup and teardown portions is captured by test duration. If the duration is +unknown, set this value to None.

+

The teardown duration of a test run is the time spend on tearing down a test run. If the teardown +duration can’t be distinguished from the test’s runtime, set this value to None.

+

The test case has a field total duration to sum up setup duration, test duration and teardown duration. +If the duration is unknown, this value will be None.

+
+
WarningCount, ErrorCount, FatalCount

The test case counts for warnings, errors and fatal errors observed in a test run while the test was executed.

+
+
__len__(), __getitem__(), __setitem__(), __delitem__(), __contains__(), __iter__()

The test case implements a dictionary interface, so arbitrary key-value pairs can be annotated per test entity.

+
+
Status

The overall status of a test case.

+

See also: Testcase Status.

+
+
AssertionCount, PassedAssertionCount, FailedAssertionCount

The assertion count represents the overall number of assertions (checks) in a test case. It can be +distinguished into passed assertions and failed assertions. If it can’t be distinguished, set +passed and failed assertions to None.

+

AssertionCount := PassedAssertionCount + FailedAssertionCount

+
+
Copy()

tbd

+
+
Aggregate()

Aggregate (recalculate) all durations, warnings, errors, assertions, etc.

+
+
__str__()

tbd

+
+
+
+
+
@export
+class Testcase(Base):
+   def __init__(
+      self,
+      name: str,
+      startTime: Nullable[datetime] = None,
+      setupDuration: Nullable[timedelta] = None,
+      testDuration: Nullable[timedelta] = None,
+      teardownDuration: Nullable[timedelta] = None,
+      totalDuration:  Nullable[timedelta] = None,
+      status: TestcaseStatus = TestcaseStatus.Unknown,
+      assertionCount: Nullable[int] = None,
+      failedAssertionCount: Nullable[int] = None,
+      passedAssertionCount: Nullable[int] = None,
+      warningCount: int = 0,
+      errorCount: int = 0,
+      fatalCount: int = 0,
+      parent: Nullable["Testsuite"] = None
+   ):
+     ...
+
+   @readonly
+   def Parent(self) -> Nullable["Testsuite"]:
+     ...
+
+   @readonly
+   def Name(self) -> str:
+     ...
+
+   @readonly
+   def StartTime(self) -> Nullable[datetime]:
+     ...
+
+   @readonly
+   def SetupDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TestDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TeardownDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TotalDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def WarningCount(self) -> int:
+     ...
+
+   @readonly
+   def ErrorCount(self) -> int:
+     ...
+
+   @readonly
+   def FatalCount(self) -> int:
+     ...
+
+   def __len__(self) -> int:
+     ...
+
+   def __getitem__(self, key: str) -> Any:
+     ...
+
+   def __setitem__(self, key: str, value: Any) -> None:
+     ...
+
+   def __delitem__(self, key: str) -> None:
+     ...
+
+   def __contains__(self, key: str) -> bool:
+     ...
+
+   def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
+     ...
+
+   @readonly
+   def Status(self) -> TestcaseStatus:
+     ...
+
+   @readonly
+   def AssertionCount(self) -> int:
+     ...
+
+   @readonly
+   def FailedAssertionCount(self) -> int:
+     ...
+
+   @readonly
+   def PassedAssertionCount(self) -> int:
+     ...
+
+   def Copy(self) -> "Testcase":
+     ...
+
+   def Aggregate(self, strict: bool = True) -> TestcaseAggregateReturnType:
+     ...
+
+   def __str__(self) -> str:
+     ...
+
+
+
+
+
+
+
+

Testsuite

+
+
+
+

A Testsuite is a grouping element in the test entity hierarchy and describes +a group of test runs. Besides a list of test cases and a test suite status, it also contains statistics like the +start time or the test duration for the group of tests. Test suites are grouped by other test suites or a test +suite summary and need to be unique per parent test suite.

+

A test suite (or its base classes) implements the following properties and methods:

+
+
Parent

The test suite has a reference to it’s parent test entity in the hierarchy. By iterating parent references, the +root element (test suite summary) be be found, which has no parent reference (None).

+
+
Name

The test suite has a name. This name must be unique per hierarchy parent, but can exist multiple times in the +overall test hierarchy.

+

In case the data format uses hierarchical names like pyEDAA.Reports.CLI.Application, the name is split at +the separator and multiple hierarchy levels (test suites) are created in the unified data model. To be able to +recreate such an hierarchical name, TestsuiteKind is applied accordingly +to test suite’s Kind field.

+
+
StartTime

The test suite stores a time when the first test run was started. In combination with +TotalDuration, the end time can be calculated. If the start time is +unknown, set this value to None.

+
+
SetupDuration, TestDuration, TeardownDuration, TotalDuration

The test suite has fields to capture the suite’s setup duration, test group run duration and suite’s +teardown duration. The sum of all durations is provided by total duration.

+

TotalDuration := SetupDuration + TestDuration + TeardownDuration

+

The setup duration is the time spend on setting up a test suite. If the setup duration can’t be +distinguished from the test group’s runtime, set this value to None.

+

The test group’s runtime without setup and teardown portions is captured by test duration. If the +duration is unknown, set this value to None.

+

The teardown duration of a test suite is the time spend on tearing down a test suite. If the teardown +duration can’t be distinguished from the test group’s runtime, set this value to None.

+

The test suite has a field total duration to sum up setup duration, test duration and teardown duration. +If the duration is unknown, this value will be None.

+
+
WarningCount, ErrorCount, FatalCount

The test suite counts for warnings, errors and fatal errors observed in a test suite while the tests were +executed.

+
+
__len__(), __getitem__(), __setitem__(), __delitem__(), __contains__(), __iter__()

The test suite implements a dictionary interface, so arbitrary key-value pairs can be annotated.

+
+
+
+

Todo

+

TestsuiteBase APIs

+
+
+
Testcases

tbd

+
+
TestcaseCount

tbd

+
+
AssertionCount

The overall number of assertions (checks) in a test case.

+
+
Aggregate()

Aggregate (recalculate) all durations, warnings, errors, assertions, etc.

+
+
Iterate()

tbd

+
+
__str__()

tbd

+
+
+
+
+
@export
+class Testsuite(TestsuiteBase[TestsuiteType]):
+   def __init__(
+      self,
+      name: str,
+      kind: TestsuiteKind = TestsuiteKind.Logical,
+      startTime: Nullable[datetime] = None,
+      setupDuration: Nullable[timedelta] = None,
+      testDuration: Nullable[timedelta] = None,
+      teardownDuration: Nullable[timedelta] = None,
+      totalDuration:  Nullable[timedelta] = None,
+      status: TestsuiteStatus = TestsuiteStatus.Unknown,
+      warningCount: int = 0,
+      errorCount: int = 0,
+      fatalCount: int = 0,
+      testsuites: Nullable[Iterable[TestsuiteType]] = None,
+      testcases: Nullable[Iterable["Testcase"]] = None,
+      parent: Nullable[TestsuiteType] = None
+   ):
+     ...
+
+   @readonly
+   def Parent(self) -> Nullable["Testsuite"]:
+     ...
+
+   @readonly
+   def Name(self) -> str:
+     ...
+
+   @readonly
+   def StartTime(self) -> Nullable[datetime]:
+     ...
+
+   @readonly
+   def SetupDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TestDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TeardownDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TotalDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def WarningCount(self) -> int:
+     ...
+
+   @readonly
+   def ErrorCount(self) -> int:
+     ...
+
+   @readonly
+   def FatalCount(self) -> int:
+     ...
+
+   def __len__(self) -> int:
+     ...
+
+   def __getitem__(self, key: str) -> Any:
+     ...
+
+   def __setitem__(self, key: str, value: Any) -> None:
+     ...
+
+   def __delitem__(self, key: str) -> None:
+     ...
+
+   def __contains__(self, key: str) -> bool:
+     ...
+
+   def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
+     ...
+
+   # TestsuiteBase API
+
+
+   @readonly
+   def Testcases(self) -> Dict[str, "Testcase"]:
+     ...
+
+   @readonly
+   def TestcaseCount(self) -> int:
+     ...
+
+   @readonly
+   def AssertionCount(self) -> int:
+     ...
+
+   def Aggregate(self, strict: bool = True) -> TestsuiteAggregateReturnType:
+     ...
+
+   def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]:
+     ...
+
+   def __str__(self) -> str:
+     ...
+
+
+
+
+
+
+
+

TestsuiteSummary

+
+
+
+

A TestsuiteSummary is the root element in the test entity hierarchy and +describes a group of test suites as well as overall statistics for the whole set of test cases. A test suite +summary is derived for the same base-class as a test suite, thus they share almost all properties and methods.

+

A test suite summary (or its base classes) implements the following properties and methods:

+
+
Parent

The test suite summary has a parent reference, but as the root element in the test entity hierarchy, its always +None.

+
+
Name

The test suite summary has a name.

+
+
StartTime

The test suite summary stores a time when the first test runs was started. In combination with +TotalDuration, the end time can be calculated. If the start time is +unknown, set this value to None.

+
+
SetupDuration, TestDuration, TeardownDuration, TotalDuration

The test suite summary has fields to capture the suite summary’s setup duration, overall run duration and +suite summary’s teardown duration. The sum of all durations is provided by total duration.

+

TotalDuration := SetupDuration + TestDuration + TeardownDuration

+

The setup duration is the time spend on setting up an overall test run. If the setup duration can’t be +distinguished from the test’s runtimes, set this value to None.

+

The test suite summary’s runtime without setup and teardown portions is captured by test duration. If +the duration is unknown, set this value to None.

+

The teardown duration of a test suite summary is the time spend on tearing down a test suite summary. If +the teardown duration can’t be distinguished from the test’s runtimes, set this value to None.

+

The test suite summary has a field total duration to sum up setup duration, overall run duration and +teardown duration. If the duration is unknown, this value will be None.

+
+
WarningCount, ErrorCount, FatalCount

The test suite summary counts for warnings, errors and fatal errors observed in a test suite summary while the +tests were executed.

+
+
__len__(), __getitem__(), __setitem__(), __delitem__(), __contains__(), __iter__()

The test suite summary implements a dictionary interface, so arbitrary key-value pairs can be annotated.

+
+
+
+

Todo

+

TestsuiteBase APIs

+
+
+
Aggregate()

tbd

+
+
Iterate()

tbd

+
+
__str__()

tbd

+
+
+
+
+
@export
+class TestsuiteSummary(TestsuiteBase[TestsuiteType]):
+   def __init__(
+      self,
+      name: str,
+      startTime: Nullable[datetime] = None,
+      setupDuration: Nullable[timedelta] = None,
+      testDuration: Nullable[timedelta] = None,
+      teardownDuration: Nullable[timedelta] = None,
+      totalDuration:  Nullable[timedelta] = None,
+      status: TestsuiteStatus = TestsuiteStatus.Unknown,
+      warningCount: int = 0,
+      errorCount: int = 0,
+      fatalCount: int = 0,
+      testsuites: Nullable[Iterable[TestsuiteType]] = None,
+      parent: Nullable[TestsuiteType] = None
+   ):
+     ...
+
+   @readonly
+   def Parent(self) -> Nullable["Testsuite"]:
+     ...
+
+   @readonly
+   def Name(self) -> str:
+     ...
+
+   @readonly
+   def StartTime(self) -> Nullable[datetime]:
+     ...
+
+   @readonly
+   def SetupDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TestDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TeardownDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def TotalDuration(self) -> Nullable[timedelta]:
+     ...
+
+   @readonly
+   def WarningCount(self) -> int:
+     ...
+
+   @readonly
+   def ErrorCount(self) -> int:
+     ...
+
+   @readonly
+   def FatalCount(self) -> int:
+     ...
+
+   def __len__(self) -> int:
+     ...
+
+   def __getitem__(self, key: str) -> Any:
+     ...
+
+   def __setitem__(self, key: str, value: Any) -> None:
+     ...
+
+   def __delitem__(self, key: str) -> None:
+     ...
+
+   def __contains__(self, key: str) -> bool:
+     ...
+
+   def __iter__(self) -> Generator[Tuple[str, Any], None, None]:
+     ...
+
+   # TestsuiteBase API
+
+   def Aggregate(self) -> TestsuiteAggregateReturnType:
+     ...
+
+   def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]:
+     ...
+
+   def __str__(self) -> str:
+     ...
+
+
+
+
+
+
+
+

Document

+
+
+
+

A Document is a mixin-class …

+
+
Path

tbd

+
+
AnalysisDuration

tbd

+
+
ModelConversionDuration

tbd

+
+
Analyze()

tbd

+
+
Convert()

tbd

+
+
+
+
+
@export
+class Document(metaclass=ExtendedType, mixin=True):
+   def __init__(self, path: Path):
+     ...
+
+   @readonly
+   def Path(self) -> Path:
+     ...
+
+   @readonly
+   def AnalysisDuration(self) -> timedelta:
+     ...
+
+   @readonly
+   def ModelConversionDuration(self) -> timedelta:
+     ...
+
+   @abstractmethod
+   def Analyze(self) -> None:
+     ...
+
+   @abstractmethod
+   def Convert(self):
+     ...
+
+
+
+
+
+
+
+
+

Specific Data Models

+
+

JUnit Data Model

+
+
+
+
+
+
+
+ +
+

A test case is the leaf-element in the test entity hierarchy and describes an individual test run. +Test cases are grouped by test classes.

+
+
+
+
+
+ +
+

A test class is the mid-level element in the test entity hierarchy and describes a group of test +runs. Test classes are grouped by test suites.

+
+
+
+
+
+ +
+

A test suite is a group of test classes. Test suites are grouped by a test suite summary.

+
+
+
+
+
+ +
+

The test suite summary is derived from test suite and defines the root of the test suite hierarchy.

+
+
+
+
+
+ +
+

The document is derived from a test suite summary and represents a file containing a test suite +summary.

+
+
+
+
+
+ +
+

The JUnit format is not well defined, thus multiple dialects developed over time.

+
+
+
+
+
+
+
+
+        graph TD;
+  doc[Document]
+  sum[Summary]
+  ts1[Testsuite]
+  ts11[Testsuite]
+  ts2[Testsuite]
+
+  tc111[Testclass]
+  tc112[Testclass]
+  tc23[Testclass]
+
+  tc1111[Testcase]
+  tc1112[Testcase]
+  tc1113[Testcase]
+  tc1121[Testcase]
+  tc1122[Testcase]
+  tc231[Testcase]
+  tc232[Testcase]
+  tc233[Testcase]
+
+  doc:::root -.-> sum:::summary
+  sum --> ts1:::suite
+  sum ---> ts2:::suite
+  ts1 --> ts11:::suite
+
+  ts11 --> tc111:::cls
+  ts11 --> tc112:::cls
+  ts2  --> tc23:::cls
+
+  tc111 --> tc1111:::case
+  tc111 --> tc1112:::case
+  tc111 --> tc1113:::case
+  tc112 --> tc1121:::case
+  tc112 --> tc1122:::case
+  tc23 --> tc231:::case
+  tc23 --> tc232:::case
+  tc23 --> tc233:::case
+
+  classDef root fill:#4dc3ff
+  classDef summary fill:#80d4ff
+  classDef suite fill:#b3e6ff
+  classDef cls fill:#ff9966
+  classDef case fill:#eeccff
+    
+
+
+
+

Testcase

+
+
+

Testclass

+
+
+

Testsuite

+
+
+

TestsuiteSummary

+
+
+

Document

+
+
+
+

JUnit Dialects

+

As the JUnit XML format was not well specified and no XML Schema Definition (XSD) was provided, many variants and +dialects (and simplifications) were created by the various frameworks emitting JUnit XML files.

+

JUnit Dialect Comparison

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Feature

Any JUnit

Ant + JUnit4

CTest JUnit

GoogleTest JUnit

pyTest JUnit

Root element

testsuites

testsuite

testsuite

testsuites

testsuites

Supports properties

Testcase status

more status values

+
+

Any JUnit

+
+
+
+

The Any JUnit format uses a relaxed XML schema definition aiming to parse many JUnit XML dialects, which use a +<testsuites> root element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

Ant + JUnit4

+
+
+
+

The original JUnit format created by Ant for JUnit4 +uses <testsuite> as a root element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document
+
+xmlReport = Path("AntJUnit4-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

CTest JUnit

+
+
+
+

The CTest JUnit format written by CTest uses <testsuite> as a root +element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+xmlReport = Path("CTestJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

GoogleTest JUnit

+
+
+
+

The GoogleTest JUnit format written by GoogleTest (sometimes GTest) +uses <testsuites> as a root element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+xmlReport = Path("GoogleTestJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

pyTest JUnit

+
+
+
+

The pyTest JUnit format written by pyTest uses <testsuites> as a +root element.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+xmlReport = Path("PyTestJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  newDoc.Write(xmlReport)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+
+

OSVVM

+

Open Source VHDL Verification Methodology writes test results as YAML files for its +internal data model storage. Some YAML files are written by the VHDL code of the verification framework, others are +written by OSVVM-Scripts as a test runner.

+
+
+
+

Features

+
+

Create test entities

+
+
+
+

The hierarchy of test entities (test cases, test suites and test summaries) can be constructed top-down or +bottom-up.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting import Testsuite, Testcase
+
+# Top-down
+ts1 = Testsuite("ts1")
+
+tc = Testcase("tc", parent=ts)
+
+# Bottom-up
+tc1 = Testcase("tc1")
+tc2 = Testcase("tc2")
+
+ts2 = Testsuite("ts2", testcases=(tc1, tc2))
+
+# ts.AddTestcase(...)
+tc3 = Testcase("tc3")
+tc4 = Testcase("tc4")
+
+ts3 = Testsuite("ts3")
+ts3.AddTestcase(tc3)
+ts3.AddTestcase(tc4)
+
+# ts.AddTestcases(...)
+tc3 = Testcase("tc3")
+tc4 = Testcase("tc4")
+
+ts3 = Testsuite("ts3")
+ts3.AddTestcases((tc3, tc4))
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting import Testsuite, TestsuiteSummary
+
+# Top-down
+ts = Testsuite("ts")
+
+ts1 = Testsuite("ts1", parent=tss)
+
+# Bottom-up
+ts2 = Testsuite("ts2")
+ts3 = Testsuite("ts3")
+
+ts4 = Testsuite("ts4", testsuites=(ts2, ts3))
+
+# ts.AddTestsuite(...)
+ts5 = Testcase("ts5")
+ts6 = Testcase("ts6")
+
+ts7 = Testsuite("ts7")
+ts7.AddTestsuite(ts5)
+ts7.AddTestsuite(ts6)
+
+# ts.AddTestsuites(...)
+ts8 = Testcase("ts8")
+ts9 = Testcase("ts9")
+
+ts10 = Testsuite("ts10")
+ts10.AddTestsuites((ts8, ts9))
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting import Testsuite, TestsuiteSummary
+
+# Top-down
+
+# Bottom-up
+
+
+
+
+
+
+
+
+
+

Reading unittest reports

+
+
+
+

A JUnit XML test report summary file can be read by creating an instance of the Document +class. Because JUnit has so many dialects, a derived subclass for the dialect might be required. By choosing the +right Document class, also the XML schema for XML schema validation gets pre-selected.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+xmlReport = Path("AnyJUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit import Document
+
+xmlReport = Path("AntJUnit4-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+xmlReport = Path("CTest-JUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+xmlReport = Path("GoogleTest-JUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+xmlReport = Path("pyTest-JUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+

Converting unittest reports

+
+
+
+

Any JUnit dialect specific data model can be converted to the generic hierarchy of test entities.

+
+

Note

+

This conversion is identical for all derived dialects.

+
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+# Read from XML file
+xmlReport = Path("JUnit-Report.xml")
+try:
+  doc = Document(xmlReport, parse=True)
+except UnittestException as ex:
+  ...
+
+# Convert to unified test data model
+summary = doc.ToTestsuiteSummary()
+
+# Convert to a tree
+rootNode = doc.ToTree()
+
+# Convert back to a document
+newXmlReport = Path("New JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+newDoc.Write()
+
+
+
+
+
+
+
+
+
+

Annotations

+
+
+
+

Every test entity can be annotated with arbitrary key-value pairs.

+
+
+
+ +
+
# Add annotate a key-value pair
+testcase["key"] = value
+
+# Update existing annotation with new value
+testcase["key"] = newValue
+
+# Check if key exists
+if "key" in testcase:
+  pass
+
+# Access annoation by key
+value = testcase["key"]
+
+# Get number of annotations
+annotationCount = len(testcase)
+
+# Delete annotation
+del testcase["key"]
+
+# Iterate annotations
+for key, value in testcases:
+   pass
+
+
+
+ +
+
# Add annotate a key-value pair
+testsuite["key"] = value
+
+# Update existing annotation with new value
+testsuite["key"] = newValue
+
+# Check if key exists
+if "key" in testsuite:
+  pass
+
+# Access annoation by key
+value = testsuite["key"]
+
+# Get number of annotations
+annotationCount = len(testsuite)
+
+# Delete annotation
+del testsuite["key"]
+
+# Iterate annotations
+for key, value in testsuite:
+   pass
+
+
+
+ +
+
# Add annotate a key-value pair
+testsuiteSummary["key"] = value
+
+# Update existing annotation with new value
+testsuiteSummary["key"] = newValue
+
+# Check if key exists
+if "key" in testsuiteSummary:
+  pass
+
+# Access annoation by key
+value = testsuiteSummary["key"]
+
+# Get number of annotations
+annotationCount = len(testsuiteSummary)
+
+# Delete annotation
+del testsuiteSummary["key"]
+
+# Iterate annotations
+for key, value in testsuiteSummary:
+   pass
+
+
+
+
+
+
+
+
+
+

Merging unittest reports

+
+
+
+

add description here

+
+
+
+ +
+
# add code here
+
+
+
+
+
+
+
+
+
+

Concatenate unittest reports

+
+

Todo

+

Planned feature.

+
+
+
+

Transforming the reports’ hierarchy

+
+

pytest specific transformations

+
+
+
+

add description here

+
+
+
+ +
+
# add code here
+
+
+
+
+
+
+
+
+
+
+

Writing unittest reports

+
+
+
+

A test suite summary can be converted to a document of any JUnit dialect. Internally a deep-copy is created to +convert from a hierarchy of the unified test entities to a hierarchy of specific test entities (e.g. JUnit +entities).

+

When the document was created, it can be written to disk.

+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.AntJUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+ +
+
from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document
+
+# Convert a TestsuiteSummary back to a Document
+newXmlReport = Path("JUnit-Report.xml")
+newDoc = Document.FromTestsuiteSummary(newXmlReport, summary)
+
+# Write to XML file
+try:
+   newDoc.Write()
+except UnittestException as ex:
+  ...
+
+
+
+
+
+
+
+
+
+
+

Command Line Tool

+
pyedaa-reports unittest --input=Ant-JUnit:data/JUnit.xml
+
+
+
+
+

File Formats

+

Unittest summary reports can be stored in various file formats. Usually these files are XML based. Due to missing +(clear) specifications and XML schema definitions, some file formats have developed dialects. Either because the +specification was unclear/not existing or because the format was specific for a single programming language, so tools +added extensions or misused XML attributes instead of designing their own file format.

+
+

Ant and JUnit 4 XML

+

The so-called JUnit XML format was defined by Ant when running JUnit4 test suites. Because the format was not specified +by JUnit4, many dialects spread out. Many tools and test frameworks have minor or major differences compared to the +original format. While some modifications seam logical additions or adjustments to the needs of the respective +framework, others undermine the ideas and intents of the data format.

+

Many issues arise because the Ant + JUnit4 format is +specific to unit testing with Java. Other languages and frameworks were lazy and didn’t derive their own format, but +rather stuffed their language constructs into the concepts and limitations of the Ant + JUnit4 XML format.

+

JUnit Dialects

+ +
+
+

JUnit 5 XML

+

JUnit5 uses a new format called Open Test Reporting (see the following section for details). This format +isn’t specific to Java (packages, classes, methods, …), but describes a generic data model. Of cause an extension for +Java specifics is provided too.

+
+
+

Open Test Reporting

+

The Open Test Alliance created a new format called +Open Test Reporting (OTR) to overcome the shortcommings of a +missing file format for JUnit5 as well as the problems of Ant + JUnit4.

+

OTR defines a structure of test groups and tests, but no specifics of a certain programming languge. The logical +structure of tests and test groups is decoupled from language specifics like namespaces, packages or classes hosting the +individual tests.

+
+
+

OSVVM YAML

+

The Open Source VHDL Verification Methodology (OSVVM) defines its own test report format +in YAML. While OSVVM is able to convert its own YAML files to JUnit XML files, it’s recommended to use the YAML files as +data source, because these contain additional information, which can’t be expressed with JUnit XML.

+

The YAML files are created when OSVVM-based testbenches are executed with OSVVM’s embedded TCL scripting environment +OSVVM-Scripts.

+
+

Hint

+

YAML was chosen instead of JSON or XML, because a YAML document isn’t corrupted in case of a runtime error. The +document might be incomplete (content), but not corrupted (structural). Such a scenario is possible if a VHDL +simulator stops execution, then the document structure can’t be finalized.

+
+
+
+
+

Frameworks / Tools

+
+

CTest

+ +
+
+

GoogleTest (gtest)

+ +
+
+

JUnit4

+ +
+
+

JUnit5

+
+
+

OSVVM

+ +
+
+

pytest

+ +
+
+

VUnit

+ +
+
+
+

Consumers

+
+

GitLab

+
+
+

Jenkins

+
+
+

Dorney (GitHub Action)

+ +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_images/inheritance-078ba5779aaa1e96a808c676921778b61325093f.svg b/_images/inheritance-078ba5779aaa1e96a808c676921778b61325093f.svg new file mode 100644 index 00000000..2364edaf --- /dev/null +++ b/_images/inheritance-078ba5779aaa1e96a808c676921778b61325093f.svg @@ -0,0 +1,14 @@ + + +inheritance42359a937f + + +Base + + +Base + + + + + \ No newline at end of file diff --git a/_images/inheritance-0d9d1e228f2331e61819e736891bacd280cb4412.svg b/_images/inheritance-0d9d1e228f2331e61819e736891bacd280cb4412.svg new file mode 100644 index 00000000..39f4b894 --- /dev/null +++ b/_images/inheritance-0d9d1e228f2331e61819e736891bacd280cb4412.svg @@ -0,0 +1,14 @@ + + +inheritancebbbf33e7c3 + + +Combined + + +Combined + + + + + \ No newline at end of file diff --git a/_images/inheritance-0e168f8503e17cab80d44eca747f2f0f7393fb19.svg b/_images/inheritance-0e168f8503e17cab80d44eca747f2f0f7393fb19.svg new file mode 100644 index 00000000..1ca28321 --- /dev/null +++ b/_images/inheritance-0e168f8503e17cab80d44eca747f2f0f7393fb19.svg @@ -0,0 +1,14 @@ + + +inheritanced00ef7988f + + +DocStrCoverage + + +DocStrCoverage + + + + + \ No newline at end of file diff --git a/_images/inheritance-11783204054c71e1697b1c306a90aee4637eeb68.svg b/_images/inheritance-11783204054c71e1697b1c306a90aee4637eeb68.svg new file mode 100644 index 00000000..e71797ce --- /dev/null +++ b/_images/inheritance-11783204054c71e1697b1c306a90aee4637eeb68.svg @@ -0,0 +1,41 @@ + + +inheritanceb4dede040f + + +DuplicateTestsuiteException + + +DuplicateTestsuiteException + + + + + +UnittestException + + +UnittestException + + + + + +UnittestException->DuplicateTestsuiteException + + + + + +ReportException + +ReportException + + + +ReportException->UnittestException + + + + + \ No newline at end of file diff --git a/_images/inheritance-19a59ec0ec8da3452b56dfe4a4f3ce2c7a0f53d9.svg b/_images/inheritance-19a59ec0ec8da3452b56dfe4a4f3ce2c7a0f53d9.svg new file mode 100644 index 00000000..5da89037 --- /dev/null +++ b/_images/inheritance-19a59ec0ec8da3452b56dfe4a4f3ce2c7a0f53d9.svg @@ -0,0 +1,14 @@ + + +inheritanceca535f21be + + +Package + + +Package + + + + + \ No newline at end of file diff --git a/_images/inheritance-24854db48523fae3e543e68b8b93bba0c777bc94.svg b/_images/inheritance-24854db48523fae3e543e68b8b93bba0c777bc94.svg new file mode 100644 index 00000000..2d122e7f --- /dev/null +++ b/_images/inheritance-24854db48523fae3e543e68b8b93bba0c777bc94.svg @@ -0,0 +1,44 @@ + + +inheritance535239351c + + +Enum + + +Enum + + + + + +Flag + + +Flag + + + + + +Enum->Flag + + + + + +IterationScheme + + +IterationScheme + + + + + +Flag->IterationScheme + + + + + \ No newline at end of file diff --git a/_images/inheritance-2dec61a3c9505fa1b48634218e955e6f20f9fd2b.svg b/_images/inheritance-2dec61a3c9505fa1b48634218e955e6f20f9fd2b.svg new file mode 100644 index 00000000..4f2e5a04 --- /dev/null +++ b/_images/inheritance-2dec61a3c9505fa1b48634218e955e6f20f9fd2b.svg @@ -0,0 +1,44 @@ + + +inheritance315bb6953e + + +Class + + +Class + + + + + +ClassCoverage + + +ClassCoverage + + + + + +Class->ClassCoverage + + + + + +Coverage + + +Coverage + + + + + +Coverage->ClassCoverage + + + + + \ No newline at end of file diff --git a/_images/inheritance-2ec31077562a0402bf748eddda2c8ddd1aecbce5.svg b/_images/inheritance-2ec31077562a0402bf748eddda2c8ddd1aecbce5.svg new file mode 100644 index 00000000..bbae0fd2 --- /dev/null +++ b/_images/inheritance-2ec31077562a0402bf748eddda2c8ddd1aecbce5.svg @@ -0,0 +1,14 @@ + + +inheritance7cf8d33aa3 + + +Class + + +Class + + + + + \ No newline at end of file diff --git a/_images/inheritance-345eb91feb49118bd2eded4ce831ed52684f925f.svg b/_images/inheritance-345eb91feb49118bd2eded4ce831ed52684f925f.svg new file mode 100644 index 00000000..f23b738d --- /dev/null +++ b/_images/inheritance-345eb91feb49118bd2eded4ce831ed52684f925f.svg @@ -0,0 +1,62 @@ + + +inheritance33dcb1e814 + + +DuplicateTestcaseException + + +DuplicateTestcaseException + + + + + +DuplicateTestcaseException->DuplicateTestcaseException + + + + + +UnittestException + + +UnittestException + + + + + +UnittestException->DuplicateTestcaseException + + + + + +JUnitException + + +JUnitException + + + + + +JUnitException->DuplicateTestcaseException + + + + + +ReportException + +ReportException + + + +ReportException->UnittestException + + + + + \ No newline at end of file diff --git a/_images/inheritance-39ea2cb9409c99890fca1136f48c241f5b8ff2e2.svg b/_images/inheritance-39ea2cb9409c99890fca1136f48c241f5b8ff2e2.svg new file mode 100644 index 00000000..59dd88c3 --- /dev/null +++ b/_images/inheritance-39ea2cb9409c99890fca1136f48c241f5b8ff2e2.svg @@ -0,0 +1,29 @@ + + +inheritance3350518557 + + +AggregatedCoverage + + +AggregatedCoverage + + + + + +Coverage + + +Coverage + + + + + +Coverage->AggregatedCoverage + + + + + \ No newline at end of file diff --git a/_images/inheritance-3be8a97439a2918711855fcad03bffaf3096a0e8.svg b/_images/inheritance-3be8a97439a2918711855fcad03bffaf3096a0e8.svg new file mode 100644 index 00000000..08f8aa4f --- /dev/null +++ b/_images/inheritance-3be8a97439a2918711855fcad03bffaf3096a0e8.svg @@ -0,0 +1,44 @@ + + +inheritance0ed8d7c4c8 + + +Enum + + +Enum + + + + + +Flag + + +Flag + + + + + +Enum->Flag + + + + + +TestcaseStatus + + +TestcaseStatus + + + + + +Flag->TestcaseStatus + + + + + \ No newline at end of file diff --git a/_images/inheritance-3e357d31ec916199aad97700180faa910e1e4429.svg b/_images/inheritance-3e357d31ec916199aad97700180faa910e1e4429.svg new file mode 100644 index 00000000..b50a2de4 --- /dev/null +++ b/_images/inheritance-3e357d31ec916199aad97700180faa910e1e4429.svg @@ -0,0 +1,89 @@ + + +inheritancef6255eabf1 + + +Base + + +Base + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +Base->TestsuiteBase + + + + + +Generic + + +Generic + + + + + +Generic->TestsuiteBase + + + + + +Merged + + +Merged + + + + + +MergedTestsuiteSummary + + +MergedTestsuiteSummary + + + + + +Merged->MergedTestsuiteSummary + + + + + +TestsuiteSummary + + +TestsuiteSummary + + + + + +TestsuiteSummary->MergedTestsuiteSummary + + + + + +TestsuiteBase->TestsuiteSummary + + + + + \ No newline at end of file diff --git a/_images/inheritance-3f039477cbfc6416a441f14e90ecf9b7848fd7d4.svg b/_images/inheritance-3f039477cbfc6416a441f14e90ecf9b7848fd7d4.svg new file mode 100644 index 00000000..61985595 --- /dev/null +++ b/_images/inheritance-3f039477cbfc6416a441f14e90ecf9b7848fd7d4.svg @@ -0,0 +1,14 @@ + + +inheritance42359a937f + + +Base + + +Base + + + + + \ No newline at end of file diff --git a/_images/inheritance-4192dbd2e4ead965429d5a58ba44dcf9d00e71cb.svg b/_images/inheritance-4192dbd2e4ead965429d5a58ba44dcf9d00e71cb.svg new file mode 100644 index 00000000..cc58ccac --- /dev/null +++ b/_images/inheritance-4192dbd2e4ead965429d5a58ba44dcf9d00e71cb.svg @@ -0,0 +1,44 @@ + + +inheritance1963c5de16 + + +Base + + +Base + + + + + +BaseWithProperties + + +BaseWithProperties + + + + + +Base->BaseWithProperties + + + + + +Testcase + + +Testcase + + + + + +BaseWithProperties->Testcase + + + + + \ No newline at end of file diff --git a/_images/inheritance-4dd1a326c8f610304a5052cc914136be30d48926.svg b/_images/inheritance-4dd1a326c8f610304a5052cc914136be30d48926.svg new file mode 100644 index 00000000..8d970379 --- /dev/null +++ b/_images/inheritance-4dd1a326c8f610304a5052cc914136be30d48926.svg @@ -0,0 +1,26 @@ + + +inheritance90032ec5c4 + + +DocCoverageException + + +DocCoverageException + + + + + +ReportException + +ReportException + + + +ReportException->DocCoverageException + + + + + \ No newline at end of file diff --git a/_images/inheritance-545f8f302a36bbc5f0f9c040d34bc1cb3ef63aec.svg b/_images/inheritance-545f8f302a36bbc5f0f9c040d34bc1cb3ef63aec.svg new file mode 100644 index 00000000..fa194c3b --- /dev/null +++ b/_images/inheritance-545f8f302a36bbc5f0f9c040d34bc1cb3ef63aec.svg @@ -0,0 +1,44 @@ + + +inheritanceedc3255e8f + + +Base + + +Base + + + + + +BaseWithProperties + + +BaseWithProperties + + + + + +Base->BaseWithProperties + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +BaseWithProperties->TestsuiteBase + + + + + \ No newline at end of file diff --git a/_images/inheritance-57c03c053fd4d7a08f5aa448fa50f8ecaf75d00b.svg b/_images/inheritance-57c03c053fd4d7a08f5aa448fa50f8ecaf75d00b.svg new file mode 100644 index 00000000..6664f850 --- /dev/null +++ b/_images/inheritance-57c03c053fd4d7a08f5aa448fa50f8ecaf75d00b.svg @@ -0,0 +1,59 @@ + + +inheritance1c172a31ab + + +AggregatedCoverage + + +AggregatedCoverage + + + + + +ModuleCoverage + + +ModuleCoverage + + + + + +AggregatedCoverage->ModuleCoverage + + + + + +Coverage + + +Coverage + + + + + +Coverage->AggregatedCoverage + + + + + +Module + + +Module + + + + + +Module->ModuleCoverage + + + + + \ No newline at end of file diff --git a/_images/inheritance-59ff4c1689b8211a11b92a2c3201a6f13c2fe845.svg b/_images/inheritance-59ff4c1689b8211a11b92a2c3201a6f13c2fe845.svg new file mode 100644 index 00000000..8b258ed7 --- /dev/null +++ b/_images/inheritance-59ff4c1689b8211a11b92a2c3201a6f13c2fe845.svg @@ -0,0 +1,62 @@ + + +inheritanceb4dede040f + + +DuplicateTestsuiteException + + +DuplicateTestsuiteException + + + + + +DuplicateTestsuiteException->DuplicateTestsuiteException + + + + + +UnittestException + + +UnittestException + + + + + +UnittestException->DuplicateTestsuiteException + + + + + +JUnitException + + +JUnitException + + + + + +JUnitException->DuplicateTestsuiteException + + + + + +ReportException + +ReportException + + + +ReportException->UnittestException + + + + + \ No newline at end of file diff --git a/_images/inheritance-5e6a607741d61c0cb8deaa3dc2fafecfc5428e37.svg b/_images/inheritance-5e6a607741d61c0cb8deaa3dc2fafecfc5428e37.svg new file mode 100644 index 00000000..20e615bc --- /dev/null +++ b/_images/inheritance-5e6a607741d61c0cb8deaa3dc2fafecfc5428e37.svg @@ -0,0 +1,14 @@ + + +inheritancea7636c1792 + + +JUnitException + + +JUnitException + + + + + \ No newline at end of file diff --git a/_images/inheritance-6e65e5fb29609d54788b45390cb28c49686e17da.svg b/_images/inheritance-6e65e5fb29609d54788b45390cb28c49686e17da.svg new file mode 100644 index 00000000..cce914cb --- /dev/null +++ b/_images/inheritance-6e65e5fb29609d54788b45390cb28c49686e17da.svg @@ -0,0 +1,59 @@ + + +inheritance4f42c1461d + + +Base + + +Base + + + + + +BaseWithProperties + + +BaseWithProperties + + + + + +Base->BaseWithProperties + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +BaseWithProperties->TestsuiteBase + + + + + +TestsuiteSummary + + +TestsuiteSummary + + + + + +TestsuiteBase->TestsuiteSummary + + + + + \ No newline at end of file diff --git a/_images/inheritance-71d7596806a7dad5ceddfddd5a7290df48c4e035.svg b/_images/inheritance-71d7596806a7dad5ceddfddd5a7290df48c4e035.svg new file mode 100644 index 00000000..b5b2ff08 --- /dev/null +++ b/_images/inheritance-71d7596806a7dad5ceddfddd5a7290df48c4e035.svg @@ -0,0 +1,14 @@ + + +inheritance75f65a6bf6 + + +Merged + + +Merged + + + + + \ No newline at end of file diff --git a/_images/inheritance-71dd6d400002de581e585db6acea0177ce5da6c6.svg b/_images/inheritance-71dd6d400002de581e585db6acea0177ce5da6c6.svg new file mode 100644 index 00000000..3473ee5e --- /dev/null +++ b/_images/inheritance-71dd6d400002de581e585db6acea0177ce5da6c6.svg @@ -0,0 +1,80 @@ + + +inheritance0376ade860 + + +Base + + +Base + + + + + +BaseWithProperties + + +BaseWithProperties + + + + + +Base->BaseWithProperties + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +BaseWithProperties->TestsuiteBase + + + + + +Document + + +Document + + + + + +Document->Document + + + + + +TestsuiteSummary + + +TestsuiteSummary + + + + + +TestsuiteSummary->Document + + + + + +TestsuiteBase->TestsuiteSummary + + + + + \ No newline at end of file diff --git a/_images/inheritance-777ab3b0aa707c4b853581c23fd0d3d7b722cb75.svg b/_images/inheritance-777ab3b0aa707c4b853581c23fd0d3d7b722cb75.svg new file mode 100644 index 00000000..c40359b0 --- /dev/null +++ b/_images/inheritance-777ab3b0aa707c4b853581c23fd0d3d7b722cb75.svg @@ -0,0 +1,26 @@ + + +inheritance87e4808aab + + +ReportException + +ReportException + + + +UnittestException + + +UnittestException + + + + + +ReportException->UnittestException + + + + + \ No newline at end of file diff --git a/_images/inheritance-7ad3cb9d428d82d0d0a59b88cada23cc67da85dc.svg b/_images/inheritance-7ad3cb9d428d82d0d0a59b88cada23cc67da85dc.svg new file mode 100644 index 00000000..afa7d0c8 --- /dev/null +++ b/_images/inheritance-7ad3cb9d428d82d0d0a59b88cada23cc67da85dc.svg @@ -0,0 +1,59 @@ + + +inheritance42f105934e + + +AggregatedCoverage + + +AggregatedCoverage + + + + + +PackageCoverage + + +PackageCoverage + + + + + +AggregatedCoverage->PackageCoverage + + + + + +Coverage + + +Coverage + + + + + +Coverage->AggregatedCoverage + + + + + +Package + + +Package + + + + + +Package->PackageCoverage + + + + + \ No newline at end of file diff --git a/_images/inheritance-7b34508f42d80eda8d453ce511c81aa138119148.svg b/_images/inheritance-7b34508f42d80eda8d453ce511c81aa138119148.svg new file mode 100644 index 00000000..930cb39d --- /dev/null +++ b/_images/inheritance-7b34508f42d80eda8d453ce511c81aa138119148.svg @@ -0,0 +1,62 @@ + + +inheritanceb90494d938 + + +AlreadyInHierarchyException + + +AlreadyInHierarchyException + + + + + +AlreadyInHierarchyException->AlreadyInHierarchyException + + + + + +UnittestException + + +UnittestException + + + + + +UnittestException->AlreadyInHierarchyException + + + + + +JUnitException + + +JUnitException + + + + + +JUnitException->AlreadyInHierarchyException + + + + + +ReportException + +ReportException + + + +ReportException->UnittestException + + + + + \ No newline at end of file diff --git a/_images/inheritance-8c8b4cb58a37b24cb5e9fbf4b348c59590eb50f4.svg b/_images/inheritance-8c8b4cb58a37b24cb5e9fbf4b348c59590eb50f4.svg new file mode 100644 index 00000000..88de6bf1 --- /dev/null +++ b/_images/inheritance-8c8b4cb58a37b24cb5e9fbf4b348c59590eb50f4.svg @@ -0,0 +1,14 @@ + + +inheritancedb22aa25f7 + + +Requirement + + +Requirement + + + + + \ No newline at end of file diff --git a/_images/inheritance-8f25a630dfac11147347efa317f719a8c2def5b1.svg b/_images/inheritance-8f25a630dfac11147347efa317f719a8c2def5b1.svg new file mode 100644 index 00000000..cc799fbe --- /dev/null +++ b/_images/inheritance-8f25a630dfac11147347efa317f719a8c2def5b1.svg @@ -0,0 +1,59 @@ + + +inheritance16a412fec3 + + +Enum + + +Enum + + + + + +ReprEnum + + +ReprEnum + + + + + +Enum->ReprEnum + + + + + +IntEnum + + +IntEnum + + + + + +TestsuiteKind + + +TestsuiteKind + + + + + +IntEnum->TestsuiteKind + + + + + +ReprEnum->IntEnum + + + + + \ No newline at end of file diff --git a/_images/inheritance-8f65e97bca207f02161d49c281d96104ab1db865.svg b/_images/inheritance-8f65e97bca207f02161d49c281d96104ab1db865.svg new file mode 100644 index 00000000..bb077547 --- /dev/null +++ b/_images/inheritance-8f65e97bca207f02161d49c281d96104ab1db865.svg @@ -0,0 +1,41 @@ + + +inheritanceb90494d938 + + +AlreadyInHierarchyException + + +AlreadyInHierarchyException + + + + + +UnittestException + + +UnittestException + + + + + +UnittestException->AlreadyInHierarchyException + + + + + +ReportException + +ReportException + + + +ReportException->UnittestException + + + + + \ No newline at end of file diff --git a/_images/inheritance-a895bd6e804c7fe370c254b5d72a95ca7aaebece.svg b/_images/inheritance-a895bd6e804c7fe370c254b5d72a95ca7aaebece.svg new file mode 100644 index 00000000..1ab137f5 --- /dev/null +++ b/_images/inheritance-a895bd6e804c7fe370c254b5d72a95ca7aaebece.svg @@ -0,0 +1,29 @@ + + +inheritance1963c5de16 + + +Base + + +Base + + + + + +Testcase + + +Testcase + + + + + +Base->Testcase + + + + + \ No newline at end of file diff --git a/_images/inheritance-a8dfe22e9f490a1bb3fa42bee9b0bcbd27bc42b2.svg b/_images/inheritance-a8dfe22e9f490a1bb3fa42bee9b0bcbd27bc42b2.svg new file mode 100644 index 00000000..8451df76 --- /dev/null +++ b/_images/inheritance-a8dfe22e9f490a1bb3fa42bee9b0bcbd27bc42b2.svg @@ -0,0 +1,44 @@ + + +inheritancebe247186c1 + + +Enum + + +Enum + + + + + +Flag + + +Flag + + + + + +Enum->Flag + + + + + +JUnitReaderMode + + +JUnitReaderMode + + + + + +Flag->JUnitReaderMode + + + + + \ No newline at end of file diff --git a/_images/inheritance-aec343891fa57b94cee395fd529dd99fea1aeec6.svg b/_images/inheritance-aec343891fa57b94cee395fd529dd99fea1aeec6.svg new file mode 100644 index 00000000..01a268d7 --- /dev/null +++ b/_images/inheritance-aec343891fa57b94cee395fd529dd99fea1aeec6.svg @@ -0,0 +1,59 @@ + + +inheritance84c535a038 + + +Base + + +Base + + + + + +BaseWithProperties + + +BaseWithProperties + + + + + +Base->BaseWithProperties + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +BaseWithProperties->TestsuiteBase + + + + + +Testsuite + + +Testsuite + + + + + +TestsuiteBase->Testsuite + + + + + \ No newline at end of file diff --git a/_images/inheritance-af4248956fb207ac20eefa14b1836ec49d2d64ba.svg b/_images/inheritance-af4248956fb207ac20eefa14b1836ec49d2d64ba.svg new file mode 100644 index 00000000..fd33ad96 --- /dev/null +++ b/_images/inheritance-af4248956fb207ac20eefa14b1836ec49d2d64ba.svg @@ -0,0 +1,14 @@ + + +inheritanceece7bd6d57 + + +Module + + +Module + + + + + \ No newline at end of file diff --git a/_images/inheritance-af8327496740613cf7c82780f6cc9b019fe02661.svg b/_images/inheritance-af8327496740613cf7c82780f6cc9b019fe02661.svg new file mode 100644 index 00000000..0c393ecb --- /dev/null +++ b/_images/inheritance-af8327496740613cf7c82780f6cc9b019fe02661.svg @@ -0,0 +1,29 @@ + + +inheritancefa0107365b + + +Base + + +Base + + + + + +Testclass + + +Testclass + + + + + +Base->Testclass + + + + + \ No newline at end of file diff --git a/_images/inheritance-c3e4b02e4877d40ec48411a502a67396641cca13.svg b/_images/inheritance-c3e4b02e4877d40ec48411a502a67396641cca13.svg new file mode 100644 index 00000000..60ab56a7 --- /dev/null +++ b/_images/inheritance-c3e4b02e4877d40ec48411a502a67396641cca13.svg @@ -0,0 +1,41 @@ + + +inheritance7b347f307c + + +DocCoverageException + + +DocCoverageException + + + + + +DocStrCoverageError + + +DocStrCoverageError + + + + + +DocCoverageException->DocStrCoverageError + + + + + +ReportException + +ReportException + + + +ReportException->DocCoverageException + + + + + \ No newline at end of file diff --git a/_images/inheritance-ccdab6829d69af5a7130c234a11301d140bb372b.svg b/_images/inheritance-ccdab6829d69af5a7130c234a11301d140bb372b.svg new file mode 100644 index 00000000..754510e5 --- /dev/null +++ b/_images/inheritance-ccdab6829d69af5a7130c234a11301d140bb372b.svg @@ -0,0 +1,44 @@ + + +inheritance0238781dc4 + + +Enum + + +Enum + + + + + +Flag + + +Flag + + + + + +Enum->Flag + + + + + +TestsuiteStatus + + +TestsuiteStatus + + + + + +Flag->TestsuiteStatus + + + + + \ No newline at end of file diff --git a/_images/inheritance-cdc2e78afcb98fa9070909bfffa7ffb7b84eb330.svg b/_images/inheritance-cdc2e78afcb98fa9070909bfffa7ffb7b84eb330.svg new file mode 100644 index 00000000..2521cc5e --- /dev/null +++ b/_images/inheritance-cdc2e78afcb98fa9070909bfffa7ffb7b84eb330.svg @@ -0,0 +1,59 @@ + + +inheritance4f42c1461d + + +Base + + +Base + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +Base->TestsuiteBase + + + + + +Generic + + +Generic + + + + + +Generic->TestsuiteBase + + + + + +TestsuiteSummary + + +TestsuiteSummary + + + + + +TestsuiteBase->TestsuiteSummary + + + + + \ No newline at end of file diff --git a/_images/inheritance-cf564cdc12eaa70c87161c69ef604bf693b06bd0.svg b/_images/inheritance-cf564cdc12eaa70c87161c69ef604bf693b06bd0.svg new file mode 100644 index 00000000..5b14fbae --- /dev/null +++ b/_images/inheritance-cf564cdc12eaa70c87161c69ef604bf693b06bd0.svg @@ -0,0 +1,47 @@ + + +inheritance87e4808aab + + +JUnitException + + +JUnitException + + + + + +UnittestException + + +UnittestException + + + + + +JUnitException->UnittestException + + + + + +ReportException + +ReportException + + + +ReportException->UnittestException + + + + + +UnittestException->UnittestException + + + + + \ No newline at end of file diff --git a/_images/inheritance-d1aedd9bf15a3e1605b39a3257c203ff2d59b50c.svg b/_images/inheritance-d1aedd9bf15a3e1605b39a3257c203ff2d59b50c.svg new file mode 100644 index 00000000..22bc9d3c --- /dev/null +++ b/_images/inheritance-d1aedd9bf15a3e1605b39a3257c203ff2d59b50c.svg @@ -0,0 +1,44 @@ + + +inheritanceedc3255e8f + + +Base + + +Base + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +Base->TestsuiteBase + + + + + +Generic + + +Generic + + + + + +Generic->TestsuiteBase + + + + + \ No newline at end of file diff --git a/_images/inheritance-d2b38394b674bc1a04b51d5f7b2a8e52f02e6ad7.svg b/_images/inheritance-d2b38394b674bc1a04b51d5f7b2a8e52f02e6ad7.svg new file mode 100644 index 00000000..6f1bd58a --- /dev/null +++ b/_images/inheritance-d2b38394b674bc1a04b51d5f7b2a8e52f02e6ad7.svg @@ -0,0 +1,29 @@ + + +inheritance4e718c0707 + + +Base + + +Base + + + + + +BaseWithProperties + + +BaseWithProperties + + + + + +Base->BaseWithProperties + + + + + \ No newline at end of file diff --git a/_images/inheritance-d364c0e05f21786aa4775bf5660b89f6f748fce5.svg b/_images/inheritance-d364c0e05f21786aa4775bf5660b89f6f748fce5.svg new file mode 100644 index 00000000..fca57004 --- /dev/null +++ b/_images/inheritance-d364c0e05f21786aa4775bf5660b89f6f748fce5.svg @@ -0,0 +1,89 @@ + + +inheritancef9a7b09b95 + + +Base + + +Base + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +Base->TestsuiteBase + + + + + +Generic + + +Generic + + + + + +Generic->TestsuiteBase + + + + + +Merged + + +Merged + + + + + +MergedTestsuite + + +MergedTestsuite + + + + + +Merged->MergedTestsuite + + + + + +Testsuite + + +Testsuite + + + + + +Testsuite->MergedTestsuite + + + + + +TestsuiteBase->Testsuite + + + + + \ No newline at end of file diff --git a/_images/inheritance-d6c5b6e47694e2fe40c6b69c3cf132534da0368b.svg b/_images/inheritance-d6c5b6e47694e2fe40c6b69c3cf132534da0368b.svg new file mode 100644 index 00000000..11942deb --- /dev/null +++ b/_images/inheritance-d6c5b6e47694e2fe40c6b69c3cf132534da0368b.svg @@ -0,0 +1,59 @@ + + +inheritance84c535a038 + + +Base + + +Base + + + + + +TestsuiteBase + + +TestsuiteBase + + + + + +Base->TestsuiteBase + + + + + +Generic + + +Generic + + + + + +Generic->TestsuiteBase + + + + + +Testsuite + + +Testsuite + + + + + +TestsuiteBase->Testsuite + + + + + \ No newline at end of file diff --git a/_images/inheritance-d796feaaeb095be08fe9ad1934e5c074a523ac99.svg b/_images/inheritance-d796feaaeb095be08fe9ad1934e5c074a523ac99.svg new file mode 100644 index 00000000..64f57216 --- /dev/null +++ b/_images/inheritance-d796feaaeb095be08fe9ad1934e5c074a523ac99.svg @@ -0,0 +1,14 @@ + + +inheritance030f0c49ab + + +Coverage + + +Coverage + + + + + \ No newline at end of file diff --git a/_images/inheritance-d99f9596f729098d0c0a9b709c283e4be5d5f450.svg b/_images/inheritance-d99f9596f729098d0c0a9b709c283e4be5d5f450.svg new file mode 100644 index 00000000..4c720cb1 --- /dev/null +++ b/_images/inheritance-d99f9596f729098d0c0a9b709c283e4be5d5f450.svg @@ -0,0 +1,59 @@ + + +inheritance8bc0148cc6 + + +Base + + +Base + + + + + +Testcase + + +Testcase + + + + + +Base->Testcase + + + + + +Merged + + +Merged + + + + + +MergedTestcase + + +MergedTestcase + + + + + +Merged->MergedTestcase + + + + + +Testcase->MergedTestcase + + + + + \ No newline at end of file diff --git a/_images/inheritance-e7a28fd6fac0e17ce923d2d28e762b4e157223ea.svg b/_images/inheritance-e7a28fd6fac0e17ce923d2d28e762b4e157223ea.svg new file mode 100644 index 00000000..ef3fe2cb --- /dev/null +++ b/_images/inheritance-e7a28fd6fac0e17ce923d2d28e762b4e157223ea.svg @@ -0,0 +1,14 @@ + + +inheritance42359a937f + + +Base + + +Base + + + + + \ No newline at end of file diff --git a/_images/inheritance-ef9d9c0b0b4193f3a8350657b122f1067ad6d7b8.svg b/_images/inheritance-ef9d9c0b0b4193f3a8350657b122f1067ad6d7b8.svg new file mode 100644 index 00000000..3e86a032 --- /dev/null +++ b/_images/inheritance-ef9d9c0b0b4193f3a8350657b122f1067ad6d7b8.svg @@ -0,0 +1,41 @@ + + +inheritance33dcb1e814 + + +DuplicateTestcaseException + + +DuplicateTestcaseException + + + + + +UnittestException + + +UnittestException + + + + + +UnittestException->DuplicateTestcaseException + + + + + +ReportException + +ReportException + + + +ReportException->UnittestException + + + + + \ No newline at end of file diff --git a/_images/inheritance-f34d9eda93b4c2b98fa2a70e4657115d23cf7764.svg b/_images/inheritance-f34d9eda93b4c2b98fa2a70e4657115d23cf7764.svg new file mode 100644 index 00000000..c8f5452f --- /dev/null +++ b/_images/inheritance-f34d9eda93b4c2b98fa2a70e4657115d23cf7764.svg @@ -0,0 +1,14 @@ + + +inheritance241abf643f + + +RequirementsFile + + +RequirementsFile + + + + + \ No newline at end of file diff --git a/_images/inheritance-f74f2494d980ef8e08cbe921dd275e51dd6c0749.svg b/_images/inheritance-f74f2494d980ef8e08cbe921dd275e51dd6c0749.svg new file mode 100644 index 00000000..26a90a5a --- /dev/null +++ b/_images/inheritance-f74f2494d980ef8e08cbe921dd275e51dd6c0749.svg @@ -0,0 +1,44 @@ + + +inheritanced81a0faa0b + + +CoverageState + + +CoverageState + + + + + +Flag + + +Flag + + + + + +Flag->CoverageState + + + + + +Enum + + +Enum + + + + + +Enum->Flag + + + + + \ No newline at end of file diff --git a/_images/inheritance-fa50c4e555470df2e11596d5246162c6fa012390.svg b/_images/inheritance-fa50c4e555470df2e11596d5246162c6fa012390.svg new file mode 100644 index 00000000..3587dbcb --- /dev/null +++ b/_images/inheritance-fa50c4e555470df2e11596d5246162c6fa012390.svg @@ -0,0 +1,14 @@ + + +inheritance0376ade860 + + +Document + + +Document + + + + + \ No newline at end of file diff --git a/_images/logo.svg b/_images/logo.svg new file mode 100644 index 00000000..6cdf6406 --- /dev/null +++ b/_images/logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 00000000..6306a929 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,165 @@ + + + + + + + + Overview: module code — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyEDAA/Reports/DocumentationCoverage.html b/_modules/pyEDAA/Reports/DocumentationCoverage.html new file mode 100644 index 00000000..f243a20c --- /dev/null +++ b/_modules/pyEDAA/Reports/DocumentationCoverage.html @@ -0,0 +1,307 @@ + + + + + + + + pyEDAA.Reports.DocumentationCoverage — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyEDAA.Reports.DocumentationCoverage

+# ==================================================================================================================== #
+#              _____ ____    _        _      ____                       _                                              #
+#  _ __  _   _| ____|  _ \  / \      / \    |  _ \ ___ _ __   ___  _ __| |_ ___                                        #
+# | '_ \| | | |  _| | | | |/ _ \    / _ \   | |_) / _ \ '_ \ / _ \| '__| __/ __|                                       #
+# | |_) | |_| | |___| |_| / ___ \  / ___ \ _|  _ <  __/ |_) | (_) | |  | |_\__ \                                       #
+# | .__/ \__, |_____|____/_/   \_\/_/   \_(_)_| \_\___| .__/ \___/|_|   \__|___/                                       #
+# |_|    |___/                                        |_|                                                              #
+# ==================================================================================================================== #
+# Authors:                                                                                                             #
+#   Patrick Lehmann                                                                                                    #
+#                                                                                                                      #
+# License:                                                                                                             #
+# ==================================================================================================================== #
+# Copyright 2021-2024 Electronic Design Automation Abstraction (EDA²)                                                  #
+#                                                                                                                      #
+# 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.                                                                                       #
+#                                                                                                                      #
+# SPDX-License-Identifier: Apache-2.0                                                                                  #
+# ==================================================================================================================== #
+#
+"""Abstraction of code documentation coverage data model."""
+from enum                  import Flag
+from typing                import Optional as Nullable
+
+from pyTooling.Decorators  import export
+from pyTooling.MetaClasses import ExtendedType
+
+from pyEDAA.Reports        import ReportException
+
+
+
+[docs] +@export +class DocCoverageException(ReportException): + pass
+ + + +
+[docs] +@export +class CoverageState(Flag): + Unknown = 0 + Excluded = 1 + Ignored = 2 + Empty = 4 + Covered = 8 + + Weak = 16 + Incomplete = 32 + Inherited = 64 + Detailed = 128 + + Undocumented = 256 + Documented = 512 + + Parameters = 1024 + ReturnValue = 2048 + Exceptions = 8192 + Types = 16384
+ + +# unrequiredButDocumented +# wrongly documented + + +
+[docs] +@export +class Base(metaclass=ExtendedType, slots=True): + _parent: Nullable["Base"] + _name: str + _status: CoverageState + +
+[docs] + def __init__(self, name: str, parent: Nullable["Base"] = None): + if name is None: + raise ValueError(f"Parameter 'name' must not be None.") + + self._parent = parent + self._name = name + self._status = CoverageState.Unknown
+ + + @property + def Parent(self) -> Nullable["Base"]: + return self._parent + + @property + def Name(self) -> str: + return self._name + + @property + def Status(self) -> CoverageState: + return self._status
+ + + +
+[docs] +@export +class _Type(Base): + pass
+ + + +
+[docs] +@export +class Class(_Type): + pass
+ + + +
+[docs] +@export +class _Unit(Base): + pass
+ + + +
+[docs] +@export +class Module(_Unit): + pass
+ + + +
+[docs] +@export +class Package(_Unit): + pass
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyEDAA/Reports/DocumentationCoverage/Python.html b/_modules/pyEDAA/Reports/DocumentationCoverage/Python.html new file mode 100644 index 00000000..f581c41d --- /dev/null +++ b/_modules/pyEDAA/Reports/DocumentationCoverage/Python.html @@ -0,0 +1,733 @@ + + + + + + + + pyEDAA.Reports.DocumentationCoverage.Python — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyEDAA.Reports.DocumentationCoverage.Python

+# ==================================================================================================================== #
+#              _____ ____    _        _      ____                       _                                              #
+#  _ __  _   _| ____|  _ \  / \      / \    |  _ \ ___ _ __   ___  _ __| |_ ___                                        #
+# | '_ \| | | |  _| | | | |/ _ \    / _ \   | |_) / _ \ '_ \ / _ \| '__| __/ __|                                       #
+# | |_) | |_| | |___| |_| / ___ \  / ___ \ _|  _ <  __/ |_) | (_) | |  | |_\__ \                                       #
+# | .__/ \__, |_____|____/_/   \_\/_/   \_(_)_| \_\___| .__/ \___/|_|   \__|___/                                       #
+# |_|    |___/                                        |_|                                                              #
+# ==================================================================================================================== #
+# Authors:                                                                                                             #
+#   Patrick Lehmann                                                                                                    #
+#                                                                                                                      #
+# License:                                                                                                             #
+# ==================================================================================================================== #
+# Copyright 2024-2024 Electronic Design Automation Abstraction (EDA²)                                                  #
+# Copyright 2023-2023 Patrick Lehmann - Bötzingen, Germany                                                             #
+#                                                                                                                      #
+# 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.                                                                                       #
+#                                                                                                                      #
+# SPDX-License-Identifier: Apache-2.0                                                                                  #
+# ==================================================================================================================== #
+#
+"""
+**Abstract code documentation coverage data model for Python code.**
+"""
+from pathlib                              import Path
+from typing                               import Optional as Nullable, Iterable, Dict, Union, Tuple, List
+
+from pyTooling.Decorators                 import export, readonly
+from pyTooling.MetaClasses                import ExtendedType
+
+from pyEDAA.Reports.DocumentationCoverage import Class, Module, Package, CoverageState, DocCoverageException
+
+
+
+[docs] +@export +class Coverage(metaclass=ExtendedType, mixin=True): + """ + This base-class for :class:`ClassCoverage` and :class:`AggregatedCoverage` represents a basic set of documentation coverage metrics. + + Besides the *total* number of coverable items, it distinguishes items as *excluded*, *ignored*, and *expected*. |br| + Expected items are further distinguished into *covered* and *uncovered* items. |br| + If no item is expected, then *coverage* is always 100 |%|. + + All coverable items + total = excluded + ignored + expected + + All expected items + expected = covered + uncovered + + Coverage [0.00..1.00] + coverage = covered / expected + """ + _total: int + _excluded: int + _ignored: int + _expected: int + _covered: int + _uncovered: int + + _coverage: float + +
+[docs] + def __init__(self) -> None: + self._total = 0 + self._excluded = 0 + self._ignored = 0 + self._expected = 0 + self._covered = 0 + self._uncovered = 0 + + self._coverage = -1.0
+ + + @readonly + def Total(self) -> int: + return self._total + + @readonly + def Excluded(self) -> int: + return self._excluded + + @readonly + def Ignored(self) -> int: + return self._ignored + + @readonly + def Expected(self) -> int: + return self._expected + + @readonly + def Covered(self) -> int: + return self._covered + + @readonly + def Uncovered(self) -> int: + return self._uncovered + + @readonly + def Coverage(self) -> float: + return self._coverage + + def CalculateCoverage(self) -> None: + self._uncovered = self._expected - self._covered + if self._expected != 0: + self._coverage = self._covered / self._expected + else: + self._coverage = 1.0 + + def _CountCoverage(self, iterator: Iterable[CoverageState]) -> Tuple[int, int, int, int, int]: + total = 0 + excluded = 0 + ignored = 0 + expected = 0 + covered = 0 + for coverageState in iterator: + if coverageState is CoverageState.Unknown: + raise Exception(f"") + + total += 1 + + if CoverageState.Excluded in coverageState: + excluded += 1 + elif CoverageState.Ignored in coverageState: + ignored += 1 + + expected += 1 + if CoverageState.Covered in coverageState: + covered += 1 + + return total, excluded, ignored, expected, covered
+ + + +
+[docs] +@export +class AggregatedCoverage(Coverage, mixin=True): + """ + This base-class for :class:`ModuleCoverage` and :class:`PackageCoverage` represents an extended set of documentation coverage metrics, especially with aggregated metrics. + + As inherited from :class:`~Coverage`, it provides the *total* number of coverable items, which are distinguished into + *excluded*, *ignored*, and *expected* items. |br| + Expected items are further distinguished into *covered* and *uncovered* items. |br| + If no item is expected, then *coverage* and *aggregated coverage* are always 100 |%|. + + In addition, all previously mentioned metrics are collected as *aggregated...*, too. |br| + + All coverable items + total = excluded + ignored + expected + + All expected items + expected = covered + uncovered + + Coverage [0.00..1.00] + coverage = covered / expected + """ + _file: Path + + _aggregatedTotal: int + _aggregatedExcluded: int + _aggregatedIgnored: int + _aggregatedExpected: int + _aggregatedCovered: int + _aggregatedUncovered: int + + _aggregatedCoverage: float + +
+[docs] + def __init__(self, file: Path) -> None: + super().__init__() + self._file = file
+ + + @readonly + def File(self) -> Path: + return self._file + + @readonly + def AggregatedTotal(self) -> int: + return self._aggregatedTotal + + @readonly + def AggregatedExcluded(self) -> int: + return self._aggregatedExcluded + + @readonly + def AggregatedIgnored(self) -> int: + return self._aggregatedIgnored + + @readonly + def AggregatedExpected(self) -> int: + return self._aggregatedExpected + + @readonly + def AggregatedCovered(self) -> int: + return self._aggregatedCovered + + @readonly + def AggregatedUncovered(self) -> int: + return self._aggregatedUncovered + + @readonly + def AggregatedCoverage(self) -> float: + return self._aggregatedCoverage + + def Aggregate(self) -> None: + assert self._aggregatedUncovered == self._aggregatedExpected - self._aggregatedCovered + + if self._aggregatedExpected != 0: + self._aggregatedCoverage = self._aggregatedCovered / self._aggregatedExpected + else: + self._aggregatedCoverage = 1.0
+ + + +
+[docs] +@export +class ClassCoverage(Class, Coverage): + """ + This class represents the class documentation coverage for Python classes. + """ + _fields: Dict[str, CoverageState] + _methods: Dict[str, CoverageState] + _classes: Dict[str, "ClassCoverage"] + +
+[docs] + def __init__(self, name: str, parent: Union["PackageCoverage", "ClassCoverage", None] = None) -> None: + super().__init__(name, parent) + Coverage.__init__(self) + + if parent is not None: + parent._classes[name] = self + + self._fields = {} + self._methods = {} + self._classes = {}
+ + + @readonly + def Fields(self) -> Dict[str, CoverageState]: + return self._fields + + @readonly + def Methods(self) -> Dict[str, CoverageState]: + return self._methods + + @readonly + def Classes(self) -> Dict[str, "ClassCoverage"]: + return self._classes + + def CalculateCoverage(self) -> None: + for cls in self._classes.values(): + cls.CalculateCoverage() + + self._total, self._excluded, self._ignored, self._expected, self._covered = \ + self._CountCoverage(zip( + self._fields.values(), + self._methods.values() + )) + + super().CalculateCoverage() + +
+[docs] + def __str__(self) -> str: + return f"<ClassCoverage - tot:{self._total}, ex:{self._excluded}, ig:{self._ignored}, exp:{self._expected}, cov:{self._covered}, un:{self._uncovered} => {self._coverage:.1%}>"
+
+ + + +
+[docs] +@export +class ModuleCoverage(Module, AggregatedCoverage): + """ + This class represents the module documentation coverage for Python modules. + """ + _variables: Dict[str, CoverageState] + _functions: Dict[str, CoverageState] + _classes: Dict[str, ClassCoverage] + +
+[docs] + def __init__(self, name: str, file: Path, parent: Nullable["PackageCoverage"] = None) -> None: + super().__init__(name, parent) + AggregatedCoverage.__init__(self, file) + + if parent is not None: + parent._modules[name] = self + + self._file = file + self._variables = {} + self._functions = {} + self._classes = {}
+ + + @readonly + def Variables(self) -> Dict[str, CoverageState]: + return self._variables + + @readonly + def Functions(self) -> Dict[str, CoverageState]: + return self._functions + + @readonly + def Classes(self) -> Dict[str, ClassCoverage]: + return self._classes + + def CalculateCoverage(self) -> None: + for cls in self._classes.values(): + cls.CalculateCoverage() + + self._total, self._excluded, self._ignored, self._expected, self._covered = \ + self._CountCoverage(zip( + self._variables.values(), + self._functions.values() + )) + + super().CalculateCoverage() + + def Aggregate(self) -> None: + self._aggregatedTotal = self._total + self._aggregatedExcluded = self._excluded + self._aggregatedIgnored = self._ignored + self._aggregatedExpected = self._expected + self._aggregatedCovered = self._covered + self._aggregatedUncovered = self._uncovered + + for cls in self._classes.values(): + self._aggregatedTotal += cls._total + self._aggregatedExcluded += cls._excluded + self._aggregatedIgnored += cls._ignored + self._aggregatedExpected += cls._expected + self._aggregatedCovered += cls._covered + self._aggregatedUncovered += cls._uncovered + + super().Aggregate() + +
+[docs] + def __str__(self) -> str: + return f"<ModuleCoverage - tot:{self._total}|{self._aggregatedTotal}, ex:{self._excluded}|{self._aggregatedExcluded}, ig:{self._ignored}|{self._aggregatedIgnored}, exp:{self._expected}|{self._aggregatedExpected}, cov:{self._covered}|{self._aggregatedCovered}, un:{self._uncovered}|{self._aggregatedUncovered} => {self._coverage:.1%}|{self._aggregatedCoverage:.1%}>"
+
+ + + +
+[docs] +@export +class PackageCoverage(Package, AggregatedCoverage): + """ + This class represents the package documentation coverage for Python packages. + """ + _fileCount: int + _variables: Dict[str, CoverageState] + _functions: Dict[str, CoverageState] + _classes: Dict[str, ClassCoverage] + _modules: Dict[str, ModuleCoverage] + _packages: Dict[str, "PackageCoverage"] + +
+[docs] + def __init__(self, name: str, file: Path, parent: Nullable["PackageCoverage"] = None) -> None: + super().__init__(name, parent) + AggregatedCoverage.__init__(self, file) + + if parent is not None: + parent._packages[name] = self + + self._file = file + self._fileCount = 1 + self._variables = {} + self._functions = {} + self._classes = {} + self._modules = {} + self._packages = {}
+ + + @readonly + def FileCount(self) -> int: + return self._fileCount + + @readonly + def Variables(self) -> Dict[str, CoverageState]: + return self._variables + + @readonly + def Functions(self) -> Dict[str, CoverageState]: + return self._functions + + @readonly + def Classes(self) -> Dict[str, ClassCoverage]: + return self._classes + + @readonly + def Modules(self) -> Dict[str, ModuleCoverage]: + return self._modules + + @readonly + def Packages(self) -> Dict[str, "PackageCoverage"]: + return self._packages + + def __getitem__(self, key: str) -> Union["PackageCoverage", ModuleCoverage]: + try: + return self._modules[key] + except KeyError: + return self._packages[key] + + def CalculateCoverage(self) -> None: + for cls in self._classes.values(): + cls.CalculateCoverage() + + for mod in self._modules.values(): + mod.CalculateCoverage() + + for pkg in self._packages.values(): + pkg.CalculateCoverage() + + self._total, self._excluded, self._ignored, self._expected, self._covered = \ + self._CountCoverage(zip( + self._variables.values(), + self._functions.values() + )) + + super().CalculateCoverage() + + def Aggregate(self) -> None: + self._fileCount = len(self._modules) + 1 + self._aggregatedTotal = self._total + self._aggregatedExcluded = self._excluded + self._aggregatedIgnored = self._ignored + self._aggregatedExpected = self._expected + self._aggregatedCovered = self._covered + self._aggregatedUncovered = self._uncovered + + for pkg in self._packages.values(): + pkg.Aggregate() + self._fileCount += pkg._fileCount + self._aggregatedTotal += pkg._total + self._aggregatedExcluded += pkg._excluded + self._aggregatedIgnored += pkg._ignored + self._aggregatedExpected += pkg._expected + self._aggregatedCovered += pkg._covered + self._aggregatedUncovered += pkg._uncovered + + for mod in self._modules.values(): + mod.Aggregate() + self._aggregatedTotal += mod._total + self._aggregatedExcluded += mod._excluded + self._aggregatedIgnored += mod._ignored + self._aggregatedExpected += mod._expected + self._aggregatedCovered += mod._covered + self._aggregatedUncovered += mod._uncovered + + super().Aggregate() + +
+[docs] + def __str__(self) -> str: + return f"<PackageCoverage - tot:{self._total}|{self._aggregatedTotal}, ex:{self._excluded}|{self._aggregatedExcluded}, ig:{self._ignored}|{self._aggregatedIgnored}, exp:{self._expected}|{self._aggregatedExpected}, cov:{self._covered}|{self._aggregatedCovered}, un:{self._uncovered}|{self._aggregatedUncovered} => {self._coverage:.1%}|{self._aggregatedCoverage:.1%}>"
+
+ + + +
+[docs] +@export +class DocStrCoverageError(DocCoverageException): + pass
+ + + +
+[docs] +@export +class DocStrCoverage(metaclass=ExtendedType): + """ + A wrapper class for the docstr_coverage package and it's analyzer producing a documentation coverage model. + """ + from docstr_coverage import ResultCollection + + _packageName: str + _searchDirectory: Path + _moduleFiles: List[Path] + _coverageReport: ResultCollection + +
+[docs] + def __init__(self, packageName: str, directory: Path) -> None: + if not directory.exists(): + raise DocStrCoverageError(f"Package source directory '{directory}' does not exist.") from FileNotFoundError(f"Directory '{directory}' does not exist.") + + self._searchDirectory = directory + self._packageName = packageName + self._moduleFiles = [file for file in directory.glob("**/*.py")]
+ + + @readonly + def SearchDirectories(self) -> Path: + return self._searchDirectory + + @readonly + def PackageName(self) -> str: + return self._packageName + + @readonly + def ModuleFiles(self) -> List[Path]: + return self._moduleFiles + + @readonly + def CoverageReport(self) -> ResultCollection: + return self._coverageReport + + def Analyze(self) -> ResultCollection: + from docstr_coverage import analyze, ResultCollection + + self._coverageReport: ResultCollection = analyze(self._moduleFiles, show_progress=False) + return self._coverageReport + + def Convert(self) -> PackageCoverage: + from docstr_coverage.result_collection import FileCount + + rootPackageCoverage = PackageCoverage(self._packageName, self._searchDirectory / "__init__.py") + + for key, value in self._coverageReport.files(): + path: Path = key.relative_to(self._searchDirectory) + perFileResult: FileCount = value.count_aggregate() + + moduleName = path.stem + modulePath = path.parent.parts + + currentCoverageObject: AggregatedCoverage = rootPackageCoverage + for packageName in modulePath: + try: + currentCoverageObject = currentCoverageObject[packageName] + except KeyError: + currentCoverageObject = PackageCoverage(packageName, path, currentCoverageObject) + + if moduleName != "__init__": + currentCoverageObject = ModuleCoverage(moduleName, path, currentCoverageObject) + + currentCoverageObject._expected = perFileResult.needed + currentCoverageObject._covered = perFileResult.found + currentCoverageObject._uncovered = perFileResult.missing + + if currentCoverageObject._expected != 0: + currentCoverageObject._coverage = currentCoverageObject._covered / currentCoverageObject._expected + else: + currentCoverageObject._coverage = 1.0 + + if currentCoverageObject._uncovered != currentCoverageObject._expected - currentCoverageObject._covered: + currentCoverageObject._coverage = -2.0 + + return rootPackageCoverage + + del ResultCollection
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyEDAA/Reports/Requirement/Python.html b/_modules/pyEDAA/Reports/Requirement/Python.html new file mode 100644 index 00000000..06312f77 --- /dev/null +++ b/_modules/pyEDAA/Reports/Requirement/Python.html @@ -0,0 +1,246 @@ + + + + + + + + pyEDAA.Reports.Requirement.Python — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + + +
  • +
  • +
+
+
+
+
+ +

Source code for pyEDAA.Reports.Requirement.Python

+# ==================================================================================================================== #
+#              _____ ____    _        _      ____                       _                                              #
+#  _ __  _   _| ____|  _ \  / \      / \    |  _ \ ___ _ __   ___  _ __| |_ ___                                        #
+# | '_ \| | | |  _| | | | |/ _ \    / _ \   | |_) / _ \ '_ \ / _ \| '__| __/ __|                                       #
+# | |_) | |_| | |___| |_| / ___ \  / ___ \ _|  _ <  __/ |_) | (_) | |  | |_\__ \                                       #
+# | .__/ \__, |_____|____/_/   \_\/_/   \_(_)_| \_\___| .__/ \___/|_|   \__|___/                                       #
+# |_|    |___/                                        |_|                                                              #
+# ==================================================================================================================== #
+# Authors:                                                                                                             #
+#   Patrick Lehmann                                                                                                    #
+#                                                                                                                      #
+# License:                                                                                                             #
+# ==================================================================================================================== #
+# Copyright 2021-2024 Electronic Design Automation Abstraction (EDA²)                                                  #
+#                                                                                                                      #
+# 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.                                                                                       #
+#                                                                                                                      #
+# SPDX-License-Identifier: Apache-2.0                                                                                  #
+# ==================================================================================================================== #
+#
+from pathlib import Path
+from re      import compile as re_compile
+
+from pyTooling.Decorators import export
+
+
+
+[docs] +@export +class Requirement: + _package: str + + EXTRA_PATTERN = re_compile(r"""^\s*extra\s*==\s*(?P<quote>['"])(?P<key>\w+)(?P=quote)?$""") + + # python_version + # platform_python_implementation + # platform_system + # python_full_version + # sys_platform + +
+[docs] + def __init__(self, rule: str) -> None: + extraKey = None + for rulePart in (part.strip() for part in rule.split(";")): + match = self.EXTRA_PATTERN.match(rulePart) + if match: + extraKey = match["key"] + continue
+
+ + + + +
+[docs] +@export +class RequirementsFile: + _path: Path + +
+[docs] + def __init__(self, path: Path, parse: bool = False) -> None: + self._path = path + + if parse: + self.Parse()
+ + + def Parse(self) -> None: + with self._path.open("r", encoding="utf-8") as file: + lines = file.readline() + + for line in lines: + pass
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyEDAA/Reports/Unittesting.html b/_modules/pyEDAA/Reports/Unittesting.html new file mode 100644 index 00000000..c0ffcb61 --- /dev/null +++ b/_modules/pyEDAA/Reports/Unittesting.html @@ -0,0 +1,2257 @@ + + + + + + + + pyEDAA.Reports.Unittesting — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyEDAA.Reports.Unittesting

+# ==================================================================================================================== #
+#              _____ ____    _        _      ____                       _                                              #
+#  _ __  _   _| ____|  _ \  / \      / \    |  _ \ ___ _ __   ___  _ __| |_ ___                                        #
+# | '_ \| | | |  _| | | | |/ _ \    / _ \   | |_) / _ \ '_ \ / _ \| '__| __/ __|                                       #
+# | |_) | |_| | |___| |_| / ___ \  / ___ \ _|  _ <  __/ |_) | (_) | |  | |_\__ \                                       #
+# | .__/ \__, |_____|____/_/   \_\/_/   \_(_)_| \_\___| .__/ \___/|_|   \__|___/                                       #
+# |_|    |___/                                        |_|                                                              #
+# ==================================================================================================================== #
+# Authors:                                                                                                             #
+#   Patrick Lehmann                                                                                                    #
+#                                                                                                                      #
+# License:                                                                                                             #
+# ==================================================================================================================== #
+# Copyright 2024-2024 Electronic Design Automation Abstraction (EDA²)                                                  #
+#                                                                                                                      #
+# 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.                                                                                       #
+#                                                                                                                      #
+# SPDX-License-Identifier: Apache-2.0                                                                                  #
+# ==================================================================================================================== #
+#
+"""
+The pyEDAA.Reports.Unittesting package implements a hierarchy of test entities. These are test cases, test suites and a
+test summary provided as a class hierarchy. Test cases are the leaf elements in the hierarchy and abstract an
+individual test run. Test suites are used to group multiple test cases or other test suites. The root element is a test
+summary. When such a summary is stored in a file format like Ant + JUnit4 XML, a file format specific document is
+derived from a summary class.
+
+**Data Model**
+
+.. mermaid::
+
+	 graph TD;
+		 doc[Document]
+		 sum[Summary]
+		 ts1[Testsuite]
+		 ts2[Testsuite]
+		 ts21[Testsuite]
+		 tc11[Testcase]
+		 tc12[Testcase]
+		 tc13[Testcase]
+		 tc21[Testcase]
+		 tc22[Testcase]
+		 tc211[Testcase]
+		 tc212[Testcase]
+		 tc213[Testcase]
+
+		 doc:::root -.-> sum:::summary
+		 sum --> ts1:::suite
+		 sum --> ts2:::suite
+		 ts2 --> ts21:::suite
+		 ts1 --> tc11:::case
+		 ts1 --> tc12:::case
+		 ts1 --> tc13:::case
+		 ts2 --> tc21:::case
+		 ts2 --> tc22:::case
+		 ts21 --> tc211:::case
+		 ts21 --> tc212:::case
+		 ts21 --> tc213:::case
+
+		 classDef root fill:#4dc3ff
+		 classDef summary fill:#80d4ff
+		 classDef suite fill:#b3e6ff
+		 classDef case fill:#eeccff
+"""
+from datetime              import timedelta, datetime
+from enum                  import Flag, IntEnum
+from pathlib               import Path
+from sys                   import version_info
+from typing                import Optional as Nullable, Dict, Iterable, Any, Tuple, Generator, Union, List, Generic, TypeVar, Mapping
+
+from pyTooling.Common      import getFullyQualifiedName
+from pyTooling.Decorators  import export, readonly
+from pyTooling.MetaClasses import ExtendedType, abstractmethod
+from pyTooling.Tree        import Node
+
+from pyEDAA.Reports        import ReportException
+
+
+
+[docs] +@export +class UnittestException(ReportException): + """Base-exception for all unit test related exceptions."""
+ + + +
+[docs] +@export +class AlreadyInHierarchyException(UnittestException): + """ + A unit test exception raised if the element is already part of a hierarchy. + + This exception is caused by an inconsistent data model. Elements added to the hierarchy should be part of the same + hierarchy should occur only once in the hierarchy. + + .. hint:: + + This is usually caused by a non-None parent reference. + """
+ + + +
+[docs] +@export +class DuplicateTestsuiteException(UnittestException): + """ + A unit test exception raised on duplicate test suites (by name). + + This exception is raised, if a child test suite with same name already exist in the test suite. + + .. hint:: + + Test suite names need to be unique per parent element (test suite or test summary). + """
+ + + +
+[docs] +@export +class DuplicateTestcaseException(UnittestException): + """ + A unit test exception raised on duplicate test cases (by name). + + This exception is raised, if a child test case with same name already exist in the test suite. + + .. hint:: + + Test case names need to be unique per parent element (test suite). + """
+ + + +
+[docs] +@export +class TestcaseStatus(Flag): + """A flag enumeration describing the status of a test case.""" + Unknown = 0 #: Testcase status is uninitialized and therefore unknown. + Excluded = 1 #: Testcase was permanently excluded / disabled + Skipped = 2 #: Testcase was temporarily skipped (e.g. based on a condition) + Weak = 4 #: No assertions were recorded. + Passed = 8 #: A passed testcase, because all assertions were successful. + Failed = 16 #: A failed testcase due to at least one failed assertion. + + Mask = Excluded | Skipped | Weak | Passed | Failed + + Inverted = 128 #: To mark inverted results + UnexpectedPassed = Failed | Inverted + ExpectedFailed = Passed | Inverted + + Warned = 1024 #: Runtime warning + Errored = 2048 #: Runtime error (mostly caught exceptions) + Aborted = 4096 #: Uncaught runtime exception + + SetupError = 8192 #: Preparation / compilation error + TearDownError = 16384 #: Cleanup error / resource release error + Inconsistent = 32768 #: Dataset is inconsistent + + Flags = Warned | Errored | Aborted | SetupError | TearDownError | Inconsistent + + # TODO: timed out ? + + def __matmul__(self, other: "TestcaseStatus") -> "TestcaseStatus": + s = self & self.Mask + o = other & self.Mask + if s is self.Excluded: + resolved = self.Excluded if o is self.Excluded else self.Unknown + elif s is self.Skipped: + resolved = self.Unknown if (o is self.Unknown) or (o is self.Excluded) else o + elif s is self.Weak: + resolved = self.Weak if o is self.Weak else self.Unknown + elif s is self.Passed: + resolved = self.Passed if (o is self.Skipped) or (o is self.Passed) else self.Unknown + elif s is self.Failed: + resolved = self.Failed if (o is self.Skipped) or (o is self.Failed) else self.Unknown + else: + resolved = self.Unknown + + resolved |= (self & self.Flags) | (other & self.Flags) + return resolved
+ + + +
+[docs] +@export +class TestsuiteStatus(Flag): + """A flag enumeration describing the status of a test suite.""" + Unknown = 0 + Excluded = 1 #: Testcase was permanently excluded / disabled + Skipped = 2 #: Testcase was temporarily skipped (e.g. based on a condition) + Empty = 4 #: No tests in suite + Passed = 8 #: Passed testcase, because all assertions succeeded + Failed = 16 #: Failed testcase due to failing assertions + + Mask = Excluded | Skipped | Empty | Passed | Failed + + Inverted = 128 #: To mark inverted results + UnexpectedPassed = Failed | Inverted + ExpectedFailed = Passed | Inverted + + Warned = 1024 #: Runtime warning + Errored = 2048 #: Runtime error (mostly caught exceptions) + Aborted = 4096 #: Uncaught runtime exception + + SetupError = 8192 #: Preparation / compilation error + TearDownError = 16384 #: Cleanup error / resource release error + + Flags = Warned | Errored | Aborted | SetupError | TearDownError
+ + + +
+[docs] +@export +class TestsuiteKind(IntEnum): + """Enumeration describing the kind of test suite.""" + Root = 0 #: Root element of the hierarchy. + Logical = 1 #: Represents a logical unit. + Namespace = 2 #: Represents a namespace. + Package = 3 #: Represents a package. + Module = 4 #: Represents a module. + Class = 5 #: Represents a class.
+ + + +
+[docs] +@export +class IterationScheme(Flag): + """ + A flag enumeration for selecting the test suite iteration scheme. + + When a test entity hierarchy is (recursively) iterated, this iteration scheme describes how to iterate the hierarchy + and what elements to return as a result. + """ + Unknown = 0 #: Neutral element. + IncludeSelf = 1 #: Also include the element itself. + IncludeTestsuites = 2 #: Include test suites into the result. + IncludeTestcases = 4 #: Include test cases into the result. + + Recursive = 8 #: Iterate recursively. + + PreOrder = 16 #: Iterate in pre-order (top-down: current node, then child element left-to-right). + PostOrder = 32 #: Iterate in pre-order (bottom-up: child element left-to-right, then current node). + + Default = IncludeTestsuites | Recursive | IncludeTestcases | PreOrder #: Recursively iterate all test entities in pre-order. + TestsuiteDefault = IncludeTestsuites | Recursive | PreOrder #: Recursively iterate only test suites in pre-order. + TestcaseDefault = IncludeTestcases | Recursive | PreOrder #: Recursively iterate only test cases in pre-order.
+ + + +TestsuiteType = TypeVar("TestsuiteType", bound="Testsuite") +TestcaseAggregateReturnType = Tuple[int, int, int, timedelta] +TestsuiteAggregateReturnType = Tuple[int, int, int, int, int, int, int, int, int, int, int, timedelta] + + +
+[docs] +@export +class Base(metaclass=ExtendedType, slots=True): + """ + Base-class for all test entities (test cases, test suites, ...). + + It provides a reference to the parent test entity, so bidirectional referencing can be used in the test entity + hierarchy. + + Every test entity has a name to identity it. It's also used in the parent's child element dictionaries to identify the + child. |br| + E.g. it's used as a test case name in the dictionary of test cases in a test suite. + + Every test entity has fields for time tracking. If known, a start time and a test duration can be set. For more + details, a setup duration and teardown duration can be added. All durations are summed up in a total duration field. + + As tests can have warnings and errors or even fail, these messages are counted and aggregated in the test entity + hierarchy. + + Every test entity offers an internal dictionary for annotations. |br| + This feature is for example used by Ant + JUnit4's XML property fields. + """ + + _parent: Nullable["TestsuiteBase"] + _name: str + + _startTime: Nullable[datetime] + _setupDuration: Nullable[timedelta] + _testDuration: Nullable[timedelta] + _teardownDuration: Nullable[timedelta] + _totalDuration: Nullable[timedelta] + + _warningCount: int + _errorCount: int + _fatalCount: int + + _dict: Dict[str, Any] + +
+[docs] + def __init__( + self, + name: str, + startTime: Nullable[datetime] = None, + setupDuration: Nullable[timedelta] = None, + testDuration: Nullable[timedelta] = None, + teardownDuration: Nullable[timedelta] = None, + totalDuration: Nullable[timedelta] = None, + warningCount: int = 0, + errorCount: int = 0, + fatalCount: int = 0, + keyValuePairs: Nullable[Mapping[str, Any]] = None, + parent: Nullable["TestsuiteBase"] = None + ): + """ + Initializes the fields of the base-class. + + :param name: Name of the test entity. + :param startTime: Time when the test entity was started. + :param setupDuration: Duration it took to set up the entity. + :param testDuration: Duration of the entity's test run. + :param teardownDuration: Duration it took to tear down the entity. + :param totalDuration: Total duration of the entity's execution (setup + test + teardown). + :param warningCount: Count of encountered warnings. + :param errorCount: Count of encountered errors. + :param fatalCount: Count of encountered fatal errors. + :param keyValuePairs: Mapping of key-value pairs to initialize the test entity with. + :param parent: Reference to the parent test entity. + :raises TypeError: If parameter 'parent' is not a TestsuiteBase. + :raises ValueError: If parameter 'name' is None. + :raises TypeError: If parameter 'name' is not a string. + :raises ValueError: If parameter 'name' is empty. + :raises TypeError: If parameter 'testDuration' is not a timedelta. + :raises TypeError: If parameter 'setupDuration' is not a timedelta. + :raises TypeError: If parameter 'teardownDuration' is not a timedelta. + :raises TypeError: If parameter 'totalDuration' is not a timedelta. + :raises TypeError: If parameter 'warningCount' is not an integer. + :raises TypeError: If parameter 'errorCount' is not an integer. + :raises TypeError: If parameter 'fatalCount' is not an integer. + :raises TypeError: If parameter 'keyValuePairs' is not a Mapping. + :raises ValueError: If parameter 'totalDuration' is not consistent. + """ + + if parent is not None and not isinstance(parent, TestsuiteBase): + ex = TypeError(f"Parameter 'parent' is not of type 'TestsuiteBase'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") + raise ex + + if name is None: + raise ValueError(f"Parameter 'name' is None.") + elif not isinstance(name, str): + ex = TypeError(f"Parameter 'name' is not of type 'str'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") + raise ex + elif name.strip() == "": + raise ValueError(f"Parameter 'name' is empty.") + + self._parent = parent + self._name = name + + if testDuration is not None and not isinstance(testDuration, timedelta): + ex = TypeError(f"Parameter 'testDuration' is not of type 'timedelta'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testDuration)}'.") + raise ex + + if setupDuration is not None and not isinstance(setupDuration, timedelta): + ex = TypeError(f"Parameter 'setupDuration' is not of type 'timedelta'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(setupDuration)}'.") + raise ex + + if teardownDuration is not None and not isinstance(teardownDuration, timedelta): + ex = TypeError(f"Parameter 'teardownDuration' is not of type 'timedelta'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(teardownDuration)}'.") + raise ex + + if totalDuration is not None and not isinstance(totalDuration, timedelta): + ex = TypeError(f"Parameter 'totalDuration' is not of type 'timedelta'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(totalDuration)}'.") + raise ex + + if testDuration is not None: + if setupDuration is not None: + if teardownDuration is not None: + if totalDuration is not None: + if totalDuration < (setupDuration + testDuration + teardownDuration): + raise ValueError(f"Parameter 'totalDuration' can not be less than the sum of setup, test and teardown durations.") + else: # no total + totalDuration = setupDuration + testDuration + teardownDuration + # no teardown + elif totalDuration is not None: + if totalDuration < (setupDuration + testDuration): + raise ValueError(f"Parameter 'totalDuration' can not be less than the sum of setup and test durations.") + # no teardown, no total + else: + totalDuration = setupDuration + testDuration + # no setup + elif teardownDuration is not None: + if totalDuration is not None: + if totalDuration < (testDuration + teardownDuration): + raise ValueError(f"Parameter 'totalDuration' can not be less than the sum of test and teardown durations.") + else: # no setup, no total + totalDuration = testDuration + teardownDuration + # no setup, no teardown + elif totalDuration is not None: + if totalDuration < testDuration: + raise ValueError(f"Parameter 'totalDuration' can not be less than test durations.") + else: # no setup, no teardown, no total + totalDuration = testDuration + # no test + elif totalDuration is not None: + testDuration = totalDuration + if setupDuration is not None: + testDuration -= setupDuration + if teardownDuration is not None: + testDuration -= teardownDuration + + self._startTime = startTime + self._setupDuration = setupDuration + self._testDuration = testDuration + self._teardownDuration = teardownDuration + self._totalDuration = totalDuration + + if not isinstance(warningCount, int): + ex = TypeError(f"Parameter 'warningCount' is not of type 'int'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(warningCount)}'.") + raise ex + + if not isinstance(errorCount, int): + ex = TypeError(f"Parameter 'errorCount' is not of type 'int'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(errorCount)}'.") + raise ex + + if not isinstance(fatalCount, int): + ex = TypeError(f"Parameter 'fatalCount' is not of type 'int'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(fatalCount)}'.") + raise ex + + self._warningCount = warningCount + self._errorCount = errorCount + self._fatalCount = fatalCount + + if keyValuePairs is not None and not isinstance(keyValuePairs, Mapping): + ex = TypeError(f"Parameter 'keyValuePairs' is not a mapping.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(keyValuePairs)}'.") + raise ex + + self._dict = {} if keyValuePairs is None else {k: v for k, v in keyValuePairs}
+ + + # QUESTION: allow Parent as setter? + @readonly + def Parent(self) -> Nullable["TestsuiteBase"]: + """ + Read-only property returning the reference to the parent test entity. + + :return: Reference to the parent entity. + """ + return self._parent + + @readonly + def Name(self) -> str: + """ + Read-only property returning the test entity's name. + + :return: + """ + return self._name + + @readonly + def StartTime(self) -> Nullable[datetime]: + """ + Read-only property returning the time when the test entity was started. + + :return: Time when the test entity was started. + """ + return self._startTime + + @readonly + def SetupDuration(self) -> Nullable[timedelta]: + """ + Read-only property returning the duration of the test entity's setup. + + :return: Duration it took to set up the entity. + """ + return self._setupDuration + + @readonly + def TestDuration(self) -> Nullable[timedelta]: + """ + Read-only property returning the duration of a test entities run. + + This duration is excluding setup and teardown durations. In case setup and/or teardown durations are unknown or not + distinguishable, assign setup and teardown durations with zero. + + :return: Duration of the entity's test run. + """ + return self._testDuration + + @readonly + def TeardownDuration(self) -> Nullable[timedelta]: + """ + Read-only property returning the duration of the test entity's teardown. + + :return: Duration it took to tear down the entity. + """ + return self._teardownDuration + + @readonly + def TotalDuration(self) -> Nullable[timedelta]: + """ + Read-only property returning the total duration of a test entity run. + + this duration includes setup and teardown durations. + + :return: Total duration of the entity's execution (setup + test + teardown) + """ + return self._totalDuration + + @readonly + def WarningCount(self) -> int: + """ + Read-only property returning the number of encountered warnings. + + :return: Count of encountered warnings. + """ + return self._warningCount + + @readonly + def ErrorCount(self) -> int: + """ + Read-only property returning the number of encountered errors. + + :return: Count of encountered errors. + """ + return self._errorCount + + @readonly + def FatalCount(self) -> int: + """ + Read-only property returning the number of encountered fatal errors. + + :return: Count of encountered fatal errors. + """ + return self._fatalCount + +
+[docs] + def __len__(self) -> int: + """ + Returns the number of annotated key-value pairs. + + :return: Number of annotated key-value pairs. + """ + return len(self._dict)
+ + +
+[docs] + def __getitem__(self, key: str) -> Any: + """ + Access a key-value pair by key. + + :param key: Name if the key-value pair. + :return: Value of the accessed key. + """ + return self._dict[key]
+ + +
+[docs] + def __setitem__(self, key: str, value: Any) -> None: + """ + Set the value of a key-value pair by key. + + If the pair doesn't exist yet, it's created. + + :param key: Key of the key-value pair. + :param value: Value of the key-value pair. + """ + self._dict[key] = value
+ + +
+[docs] + def __delitem__(self, key: str) -> None: + """ + Delete a key-value pair by key. + + :param key: Name if the key-value pair. + """ + del self._dict[key]
+ + +
+[docs] + def __contains__(self, key: str) -> bool: + """ + Returns True, if a key-value pairs was annotated by this key. + + :param key: Name of the key-value pair. + :return: True, if the pair was annotated. + """ + return key in self._dict
+ + +
+[docs] + def __iter__(self) -> Generator[Tuple[str, Any], None, None]: + """ + Iterate all annotated key-value pairs. + + :return: A generator of key-value pair tuples (key, value). + """ + yield from self._dict.items()
+ + +
+[docs] + @abstractmethod + def Aggregate(self, strict: bool = True): + """ + Aggregate all test entities in the hierarchy. + + :return: + """
+ + +
+[docs] + @abstractmethod + def __str__(self) -> str: + """ + Formats the test entity as human-readable incl. some statistics. + """
+
+ + + +
+[docs] +@export +class Testcase(Base): + """ + A testcase is the leaf-entity in the test entity hierarchy representing an individual test run. + + Test cases are grouped by test suites in the test entity hierarchy. The root of the hierarchy is a test summary. + + Every test case has an overall status like unknown, skipped, failed or passed. + + In addition to all features from its base-class, test cases provide additional statistics for passed and failed + assertions (checks) as well as a sum thereof. + """ + + _status: TestcaseStatus + _assertionCount: Nullable[int] + _failedAssertionCount: Nullable[int] + _passedAssertionCount: Nullable[int] + +
+[docs] + def __init__( + self, + name: str, + startTime: Nullable[datetime] = None, + setupDuration: Nullable[timedelta] = None, + testDuration: Nullable[timedelta] = None, + teardownDuration: Nullable[timedelta] = None, + totalDuration: Nullable[timedelta] = None, + status: TestcaseStatus = TestcaseStatus.Unknown, + assertionCount: Nullable[int] = None, + failedAssertionCount: Nullable[int] = None, + passedAssertionCount: Nullable[int] = None, + warningCount: int = 0, + errorCount: int = 0, + fatalCount: int = 0, + keyValuePairs: Nullable[Mapping[str, Any]] = None, + parent: Nullable["Testsuite"] = None + ): + """ + Initializes the fields of a test case. + + :param name: Name of the test entity. + :param startTime: Time when the test entity was started. + :param setupDuration: Duration it took to set up the entity. + :param testDuration: Duration of the entity's test run. + :param teardownDuration: Duration it took to tear down the entity. + :param totalDuration: Total duration of the entity's execution (setup + test + teardown) + :param status: Status of the test case. + :param assertionCount: Number of assertions within the test. + :param failedAssertionCount: Number of failed assertions within the test. + :param passedAssertionCount: Number of passed assertions within the test. + :param warningCount: Count of encountered warnings. + :param errorCount: Count of encountered errors. + :param fatalCount: Count of encountered fatal errors. + :param keyValuePairs: Mapping of key-value pairs to initialize the test case. + :param parent: Reference to the parent test suite. + :raises TypeError: If parameter 'parent' is not a Testsuite. + :raises ValueError: If parameter 'assertionCount' is not consistent. + """ + + if parent is not None: + if not isinstance(parent, Testsuite): + ex = TypeError(f"Parameter 'parent' is not of type 'Testsuite'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") + raise ex + + parent._testcases[name] = self + + super().__init__( + name, + startTime, + setupDuration, + testDuration, + teardownDuration, + totalDuration, + warningCount, + errorCount, + fatalCount, + keyValuePairs, + parent + ) + + if not isinstance(status, TestcaseStatus): + ex = TypeError(f"Parameter 'status' is not of type 'TestcaseStatus'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(status)}'.") + raise ex + + self._status = status + + if assertionCount is not None and not isinstance(assertionCount, int): + ex = TypeError(f"Parameter 'assertionCount' is not of type 'int'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(assertionCount)}'.") + raise ex + + if failedAssertionCount is not None and not isinstance(failedAssertionCount, int): + ex = TypeError(f"Parameter 'failedAssertionCount' is not of type 'int'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(failedAssertionCount)}'.") + raise ex + + if passedAssertionCount is not None and not isinstance(passedAssertionCount, int): + ex = TypeError(f"Parameter 'passedAssertionCount' is not of type 'int'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(passedAssertionCount)}'.") + raise ex + + self._assertionCount = assertionCount + if assertionCount is not None: + if failedAssertionCount is not None: + self._failedAssertionCount = failedAssertionCount + + if passedAssertionCount is not None: + if passedAssertionCount + failedAssertionCount != assertionCount: + raise ValueError(f"passed assertion count ({passedAssertionCount}) + failed assertion count ({failedAssertionCount} != assertion count ({assertionCount}") + + self._passedAssertionCount = passedAssertionCount + else: + self._passedAssertionCount = assertionCount - failedAssertionCount + elif passedAssertionCount is not None: + self._passedAssertionCount = passedAssertionCount + self._failedAssertionCount = assertionCount - passedAssertionCount + else: + raise ValueError(f"Neither passed assertion count nor failed assertion count are provided.") + elif failedAssertionCount is not None: + self._failedAssertionCount = failedAssertionCount + + if passedAssertionCount is not None: + self._passedAssertionCount = passedAssertionCount + self._assertionCount = passedAssertionCount + failedAssertionCount + else: + raise ValueError(f"Passed assertion count is mandatory, if failed assertion count is provided instead of assertion count.") + elif passedAssertionCount is not None: + raise ValueError(f"Assertion count or failed assertion count is mandatory, if passed assertion count is provided.") + else: + self._passedAssertionCount = None + self._failedAssertionCount = None
+ + + @readonly + def Status(self) -> TestcaseStatus: + """ + Read-only property returning the status of the test case. + + :return: The test case's status. + """ + return self._status + + @readonly + def AssertionCount(self) -> int: + """ + Read-only property returning the number of assertions (checks) in a test case. + + :return: Number of assertions. + """ + if self._assertionCount is None: + return 0 + return self._assertionCount + + @readonly + def FailedAssertionCount(self) -> int: + """ + Read-only property returning the number of failed assertions (failed checks) in a test case. + + :return: Number of assertions. + """ + return self._failedAssertionCount + + @readonly + def PassedAssertionCount(self) -> int: + """ + Read-only property returning the number of passed assertions (successful checks) in a test case. + + :return: Number of passed assertions. + """ + return self._passedAssertionCount + + def Copy(self) -> "Testcase": + return self.__class__( + self._name, + self._startTime, + self._setupDuration, + self._testDuration, + self._teardownDuration, + self._totalDuration, + self._status, + self._warningCount, + self._errorCount, + self._fatalCount, + ) + +
+[docs] + def Aggregate(self, strict: bool = True) -> TestcaseAggregateReturnType: + if self._status is TestcaseStatus.Unknown: + if self._assertionCount is None: + self._status = TestcaseStatus.Passed + elif self._assertionCount == 0: + self._status = TestcaseStatus.Weak + elif self._failedAssertionCount == 0: + self._status = TestcaseStatus.Passed + else: + self._status = TestcaseStatus.Failed + + if self._warningCount > 0: + self._status |= TestcaseStatus.Warned + + if self._errorCount > 0: + self._status |= TestcaseStatus.Errored + + if self._fatalCount > 0: + self._status |= TestcaseStatus.Aborted + + if strict: + self._status = self._status & ~TestcaseStatus.Passed | TestcaseStatus.Failed + + # TODO: check for setup errors + # TODO: check for teardown errors + + totalDuration = timedelta() if self._totalDuration is None else self._totalDuration + + return self._warningCount, self._errorCount, self._fatalCount, totalDuration
+ + +
+[docs] + def __str__(self) -> str: + """ + Formats the test case as human-readable incl. statistics. + + :pycode:`f"<Testcase {}: {} - assert/pass/fail:{}/{}/{} - warn/error/fatal:{}/{}/{} - setup/test/teardown:{}/{}/{}>"` + + :return: Human-readable summary of a test case object. + """ + return ( + f"<Testcase {self._name}: {self._status.name} -" + f" assert/pass/fail:{self._assertionCount}/{self._passedAssertionCount}/{self._failedAssertionCount} -" + f" warn/error/fatal:{self._warningCount}/{self._errorCount}/{self._fatalCount} -" + f" setup/test/teardown:{self._setupDuration:.3f}/{self._testDuration:.3f}/{self._teardownDuration:.3f}>" + )
+
+ + + +
+[docs] +@export +class TestsuiteBase(Base, Generic[TestsuiteType]): + """ + Base-class for all test suites and for test summaries. + + A test suite is a mid-level grouping element in the test entity hierarchy, whereas the test summary is the root + element in that hierarchy. While a test suite groups other test suites and test cases, a test summary can only group + test suites. Thus, a test summary contains no test cases. + """ + + _kind: TestsuiteKind + _status: TestsuiteStatus + _testsuites: Dict[str, TestsuiteType] + + _tests: int + _inconsistent: int + _excluded: int + _skipped: int + _errored: int + _weak: int + _failed: int + _passed: int + +
+[docs] + def __init__( + self, + name: str, + kind: TestsuiteKind = TestsuiteKind.Logical, + startTime: Nullable[datetime] = None, + setupDuration: Nullable[timedelta] = None, + testDuration: Nullable[timedelta] = None, + teardownDuration: Nullable[timedelta] = None, + totalDuration: Nullable[timedelta] = None, + status: TestsuiteStatus = TestsuiteStatus.Unknown, + warningCount: int = 0, + errorCount: int = 0, + fatalCount: int = 0, + testsuites: Nullable[Iterable[TestsuiteType]] = None, + keyValuePairs: Nullable[Mapping[str, Any]] = None, + parent: Nullable["Testsuite"] = None + ): + """ + Initializes the based-class fields of a test suite or test summary. + + :param name: Name of the test entity. + :param kind: Kind of the test entity. + :param startTime: Time when the test entity was started. + :param setupDuration: Duration it took to set up the entity. + :param testDuration: Duration of all tests listed in the test entity. + :param teardownDuration: Duration it took to tear down the entity. + :param totalDuration: Total duration of the entity's execution (setup + test + teardown) + :param status: Overall status of the test entity. + :param warningCount: Count of encountered warnings incl. warnings from sub-elements. + :param errorCount: Count of encountered errors incl. errors from sub-elements. + :param fatalCount: Count of encountered fatal errors incl. fatal errors from sub-elements. + :param testsuites: List of test suites to initialize the test entity with. + :param keyValuePairs: Mapping of key-value pairs to initialize the test entity with. + :param parent: Reference to the parent test entity. + :raises TypeError: If parameter 'parent' is not a TestsuiteBase. + :raises TypeError: If parameter 'testsuites' is not iterable. + :raises TypeError: If element in parameter 'testsuites' is not a Testsuite. + :raises AlreadyInHierarchyException: If a test suite in parameter 'testsuites' is already part of a test entity hierarchy. + :raises DuplicateTestsuiteException: If a test suite in parameter 'testsuites' is already listed (by name) in the list of test suites. + """ + if parent is not None: + if not isinstance(parent, TestsuiteBase): + ex = TypeError(f"Parameter 'parent' is not of type 'TestsuiteBase'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") + raise ex + + parent._testsuites[name] = self + + super().__init__( + name, + startTime, + setupDuration, + testDuration, + teardownDuration, + totalDuration, + warningCount, + errorCount, + fatalCount, + keyValuePairs, + parent + ) + + self._kind = kind + self._status = status + + self._testsuites = {} + if testsuites is not None: + if not isinstance(testsuites, Iterable): + ex = TypeError(f"Parameter 'testsuites' is not iterable.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testsuites)}'.") + raise ex + + for testsuite in testsuites: + if not isinstance(testsuite, Testsuite): + ex = TypeError(f"Element of parameter 'testsuites' is not of type 'Testsuite'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.") + raise ex + + if testsuite._parent is not None: + raise AlreadyInHierarchyException(f"Testsuite '{testsuite._name}' is already part of a testsuite hierarchy.") + + if testsuite._name in self._testsuites: + raise DuplicateTestsuiteException(f"Testsuite already contains a testsuite with same name '{testsuite._name}'.") + + testsuite._parent = self + self._testsuites[testsuite._name] = testsuite + + self._status = TestsuiteStatus.Unknown + self._tests = 0 + self._inconsistent = 0 + self._excluded = 0 + self._skipped = 0 + self._errored = 0 + self._weak = 0 + self._failed = 0 + self._passed = 0
+ + + @readonly + def Kind(self) -> TestsuiteKind: + """ + Read-only property returning the kind of the test suite. + + Test suites are used to group test cases. This grouping can be due to language/framework specifics like tests + grouped by a module file or namespace. Others might be just logically grouped without any relation to a programming + language construct. + + Test summaries always return kind ``Root``. + + :return: Kind of the test suite. + """ + return self._kind + + @readonly + def Status(self) -> TestsuiteStatus: + """ + Read-only property returning the aggregated overall status of the test suite. + + :return: Overall status of the test suite. + """ + return self._status + + @readonly + def Testsuites(self) -> Dict[str, TestsuiteType]: + """ + Read-only property returning a reference to the internal dictionary of test suites. + + :return: Reference to the dictionary of test suite. + """ + return self._testsuites + + @readonly + def TestsuiteCount(self) -> int: + """ + Read-only property returning the number of all test suites in the test suite hierarchy. + + :return: Number of test suites. + """ + return 1 + sum(testsuite.TestsuiteCount for testsuite in self._testsuites.values()) + + @readonly + def TestcaseCount(self) -> int: + """ + Read-only property returning the number of all test cases in the test entity hierarchy. + + :return: Number of test cases. + """ + return sum(testsuite.TestcaseCount for testsuite in self._testsuites.values()) + + @readonly + def AssertionCount(self) -> int: + """ + Read-only property returning the number of all assertions in all test cases in the test entity hierarchy. + + :return: Number of assertions in all test cases. + """ + return sum(ts.AssertionCount for ts in self._testsuites.values()) + + @readonly + def FailedAssertionCount(self) -> int: + """ + Read-only property returning the number of all failed assertions in all test cases in the test entity hierarchy. + + :return: Number of failed assertions in all test cases. + """ + raise NotImplementedError() + # return self._assertionCount - (self._warningCount + self._errorCount + self._fatalCount) + + @readonly + def PassedAssertionCount(self) -> int: + """ + Read-only property returning the number of all passed assertions in all test cases in the test entity hierarchy. + + :return: Number of passed assertions in all test cases. + """ + raise NotImplementedError() + # return self._assertionCount - (self._warningCount + self._errorCount + self._fatalCount) + + @readonly + def Tests(self) -> int: + return self._tests + + @readonly + def Inconsistent(self) -> int: + """ + Read-only property returning the number of inconsistent tests in the test suite hierarchy. + + :return: Number of inconsistent tests. + """ + return self._inconsistent + + @readonly + def Excluded(self) -> int: + """ + Read-only property returning the number of excluded tests in the test suite hierarchy. + + :return: Number of excluded tests. + """ + return self._excluded + + @readonly + def Skipped(self) -> int: + """ + Read-only property returning the number of skipped tests in the test suite hierarchy. + + :return: Number of skipped tests. + """ + return self._skipped + + @readonly + def Errored(self) -> int: + """ + Read-only property returning the number of tests with errors in the test suite hierarchy. + + :return: Number of errored tests. + """ + return self._errored + + @readonly + def Weak(self) -> int: + """ + Read-only property returning the number of weak tests in the test suite hierarchy. + + :return: Number of weak tests. + """ + return self._weak + + @readonly + def Failed(self) -> int: + """ + Read-only property returning the number of failed tests in the test suite hierarchy. + + :return: Number of failed tests. + """ + return self._failed + + @readonly + def Passed(self) -> int: + """ + Read-only property returning the number of passed tests in the test suite hierarchy. + + :return: Number of passed tests. + """ + return self._passed + + @readonly + def WarningCount(self) -> int: + raise NotImplementedError() + # return self._warningCount + + @readonly + def ErrorCount(self) -> int: + raise NotImplementedError() + # return self._errorCount + + @readonly + def FatalCount(self) -> int: + raise NotImplementedError() + # return self._fatalCount + +
+[docs] + def Aggregate(self, strict: bool = True) -> TestsuiteAggregateReturnType: + tests = 0 + inconsistent = 0 + excluded = 0 + skipped = 0 + errored = 0 + weak = 0 + failed = 0 + passed = 0 + + warningCount = 0 + errorCount = 0 + fatalCount = 0 + + totalDuration = timedelta() + + for testsuite in self._testsuites.values(): + t, i, ex, s, e, w, f, p, wc, ec, fc, td = testsuite.Aggregate(strict) + tests += t + inconsistent += i + excluded += ex + skipped += s + errored += e + weak += w + failed += f + passed += p + + warningCount += wc + errorCount += ec + fatalCount += fc + + totalDuration += td + + return tests, inconsistent, excluded, skipped, errored, weak, failed, passed, warningCount, errorCount, fatalCount, totalDuration
+ + +
+[docs] + def AddTestsuite(self, testsuite: TestsuiteType) -> None: + """ + Add a test suite to the list of test suites. + + :param testsuite: The test suite to add. + :raises ValueError: If parameter 'testsuite' is None. + :raises TypeError: If parameter 'testsuite' is not a Testsuite. + :raises AlreadyInHierarchyException: If parameter 'testsuite' is already part of a test entity hierarchy. + :raises DuplicateTestcaseException: If parameter 'testsuite' is already listed (by name) in the list of test suites. + """ + if testsuite is None: + raise ValueError("Parameter 'testsuite' is None.") + elif not isinstance(testsuite, Testsuite): + ex = TypeError(f"Parameter 'testsuite' is not of type 'Testsuite'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testsuite)}'.") + raise ex + + if testsuite._parent is not None: + raise AlreadyInHierarchyException(f"Testsuite '{testsuite._name}' is already part of a testsuite hierarchy.") + + if testsuite._name in self._testsuites: + raise DuplicateTestsuiteException(f"Testsuite already contains a testsuite with same name '{testsuite._name}'.") + + testsuite._parent = self + self._testsuites[testsuite._name] = testsuite
+ + +
+[docs] + def AddTestsuites(self, testsuites: Iterable[TestsuiteType]) -> None: + """ + Add a list of test suites to the list of test suites. + + :param testsuites: List of test suites to add. + :raises ValueError: If parameter 'testsuites' is None. + :raises TypeError: If parameter 'testsuites' is not iterable. + """ + if testsuites is None: + raise ValueError("Parameter 'testsuites' is None.") + elif not isinstance(testsuites, Iterable): + ex = TypeError(f"Parameter 'testsuites' is not iterable.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testsuites)}'.") + raise ex + + for testsuite in testsuites: + self.AddTestsuite(testsuite)
+ + + @abstractmethod + def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]: + pass + + def IterateTestsuites(self, scheme: IterationScheme = IterationScheme.TestsuiteDefault) -> Generator[TestsuiteType, None, None]: + return self.Iterate(scheme) + + def IterateTestcases(self, scheme: IterationScheme = IterationScheme.TestcaseDefault) -> Generator[Testcase, None, None]: + return self.Iterate(scheme) + + def ToTree(self) -> Node: + rootNode = Node(value=self._name) + + def convertTestcase(testcase: Testcase, parentNode: Node) -> None: + _ = Node(value=testcase._name, parent=parentNode) + + def convertTestsuite(testsuite: Testsuite, parentNode: Node) -> None: + testsuiteNode = Node(value=testsuite._name, parent=parentNode) + + for ts in testsuite._testsuites.values(): + convertTestsuite(ts, testsuiteNode) + + for tc in testsuite._testcases.values(): + convertTestcase(tc, testsuiteNode) + + for testsuite in self._testsuites.values(): + convertTestsuite(testsuite, rootNode) + + return rootNode
+ + + +
+[docs] +@export +class Testsuite(TestsuiteBase[TestsuiteType]): + """ + A testsuite is a mid-level element in the test entity hierarchy representing a group of tests. + + Test suites contain test cases and optionally other test suites. Test suites can be grouped by test suites to form a + hierarchy of test entities. The root of the hierarchy is a test summary. + """ + + _testcases: Dict[str, "Testcase"] + +
+[docs] + def __init__( + self, + name: str, + kind: TestsuiteKind = TestsuiteKind.Logical, + startTime: Nullable[datetime] = None, + setupDuration: Nullable[timedelta] = None, + testDuration: Nullable[timedelta] = None, + teardownDuration: Nullable[timedelta] = None, + totalDuration: Nullable[timedelta] = None, + status: TestsuiteStatus = TestsuiteStatus.Unknown, + warningCount: int = 0, + errorCount: int = 0, + fatalCount: int = 0, + testsuites: Nullable[Iterable[TestsuiteType]] = None, + testcases: Nullable[Iterable["Testcase"]] = None, + keyValuePairs: Nullable[Mapping[str, Any]] = None, + parent: Nullable[TestsuiteType] = None + ): + """ + Initializes the fields of a test suite. + + :param name: Name of the test suite. + :param kind: Kind of the test suite. + :param startTime: Time when the test suite was started. + :param setupDuration: Duration it took to set up the test suite. + :param testDuration: Duration of all tests listed in the test suite. + :param teardownDuration: Duration it took to tear down the test suite. + :param totalDuration: Total duration of the entity's execution (setup + test + teardown) + :param status: Overall status of the test suite. + :param warningCount: Count of encountered warnings incl. warnings from sub-elements. + :param errorCount: Count of encountered errors incl. errors from sub-elements. + :param fatalCount: Count of encountered fatal errors incl. fatal errors from sub-elements. + :param testsuites: List of test suites to initialize the test suite with. + :param testcases: List of test cases to initialize the test suite with. + :param keyValuePairs: Mapping of key-value pairs to initialize the test suite with. + :param parent: Reference to the parent test entity. + :raises TypeError: If parameter 'testcases' is not iterable. + :raises TypeError: If element in parameter 'testcases' is not a Testcase. + :raises AlreadyInHierarchyException: If a test case in parameter 'testcases' is already part of a test entity hierarchy. + :raises DuplicateTestcaseException: If a test case in parameter 'testcases' is already listed (by name) in the list of test cases. + """ + super().__init__( + name, + kind, + startTime, + setupDuration, + testDuration, + teardownDuration, + totalDuration, + status, + warningCount, + errorCount, + fatalCount, + testsuites, + keyValuePairs, + parent + ) + + # self._testDuration = testDuration + + self._testcases = {} + if testcases is not None: + if not isinstance(testcases, Iterable): + ex = TypeError(f"Parameter 'testcases' is not iterable.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testcases)}'.") + raise ex + + for testcase in testcases: + if not isinstance(testcase, Testcase): + ex = TypeError(f"Element of parameter 'testcases' is not of type 'Testcase'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testcase)}'.") + raise ex + + if testcase._parent is not None: + raise AlreadyInHierarchyException(f"Testcase '{testcase._name}' is already part of a testsuite hierarchy.") + + if testcase._name in self._testcases: + raise DuplicateTestcaseException(f"Testsuite already contains a testcase with same name '{testcase._name}'.") + + testcase._parent = self + self._testcases[testcase._name] = testcase
+ + + @readonly + def Testcases(self) -> Dict[str, "Testcase"]: + """ + Read-only property returning a reference to the internal dictionary of test cases. + + :return: Reference to the dictionary of test cases. + """ + return self._testcases + + @readonly + def TestcaseCount(self) -> int: + """ + Read-only property returning the number of all test cases in the test entity hierarchy. + + :return: Number of test cases. + """ + return super().TestcaseCount + len(self._testcases) + + @readonly + def AssertionCount(self) -> int: + return super().AssertionCount + sum(tc.AssertionCount for tc in self._testcases.values()) + + def Copy(self) -> "Testsuite": + return self.__class__( + self._name, + self._startTime, + self._setupDuration, + self._teardownDuration, + self._totalDuration, + self._status, + self._warningCount, + self._errorCount, + self._fatalCount + ) + +
+[docs] + def Aggregate(self, strict: bool = True) -> TestsuiteAggregateReturnType: + tests, inconsistent, excluded, skipped, errored, weak, failed, passed, warningCount, errorCount, fatalCount, totalDuration = super().Aggregate() + + for testcase in self._testcases.values(): + wc, ec, fc, td = testcase.Aggregate(strict) + + tests += 1 + + warningCount += wc + errorCount += ec + fatalCount += fc + + totalDuration += td + + status = testcase._status + if status is TestcaseStatus.Unknown: + raise UnittestException(f"Found testcase '{testcase._name}' with state 'Unknown'.") + elif TestcaseStatus.Inconsistent in status: + inconsistent += 1 + elif status is TestcaseStatus.Excluded: + excluded += 1 + elif status is TestcaseStatus.Skipped: + skipped += 1 + elif status is TestcaseStatus.Errored: + errored += 1 + elif status is TestcaseStatus.Weak: + weak += 1 + elif status is TestcaseStatus.Passed: + passed += 1 + elif status is TestcaseStatus.Failed: + failed += 1 + elif status & TestcaseStatus.Mask is not TestcaseStatus.Unknown: + raise UnittestException(f"Found testcase '{testcase._name}' with unsupported state '{status}'.") + else: + raise UnittestException(f"Internal error for testcase '{testcase._name}', field '_status' is '{status}'.") + + self._tests = tests + self._inconsistent = inconsistent + self._excluded = excluded + self._skipped = skipped + self._errored = errored + self._weak = weak + self._failed = failed + self._passed = passed + + self._warningCount = warningCount + self._errorCount = errorCount + self._fatalCount = fatalCount + + if self._totalDuration is None: + self._totalDuration = totalDuration + + if errored > 0: + self._status = TestsuiteStatus.Errored + elif failed > 0: + self._status = TestsuiteStatus.Failed + elif tests == 0: + self._status = TestsuiteStatus.Empty + elif tests - skipped == passed: + self._status = TestsuiteStatus.Passed + elif tests == skipped: + self._status = TestsuiteStatus.Skipped + else: + self._status = TestsuiteStatus.Unknown + + return tests, inconsistent, excluded, skipped, errored, weak, failed, passed, warningCount, errorCount, fatalCount, totalDuration
+ + +
+[docs] + def AddTestcase(self, testcase: "Testcase") -> None: + """ + Add a test case to the list of test cases. + + :param testcase: The test case to add. + :raises ValueError: If parameter 'testcase' is None. + :raises TypeError: If parameter 'testcase' is not a Testcase. + :raises AlreadyInHierarchyException: If parameter 'testcase' is already part of a test entity hierarchy. + :raises DuplicateTestcaseException: If parameter 'testcase' is already listed (by name) in the list of test cases. + """ + if testcase is None: + raise ValueError("Parameter 'testcase' is None.") + elif not isinstance(testcase, Testcase): + ex = TypeError(f"Parameter 'testcase' is not of type 'Testcase'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testcase)}'.") + raise ex + + if testcase._parent is not None: + raise ValueError(f"Testcase '{testcase._name}' is already part of a testsuite hierarchy.") + + if testcase._name in self._testcases: + raise DuplicateTestcaseException(f"Testsuite already contains a testcase with same name '{testcase._name}'.") + + testcase._parent = self + self._testcases[testcase._name] = testcase
+ + +
+[docs] + def AddTestcases(self, testcases: Iterable["Testcase"]) -> None: + """ + Add a list of test cases to the list of test cases. + + :param testcases: List of test cases to add. + :raises ValueError: If parameter 'testcases' is None. + :raises TypeError: If parameter 'testcases' is not iterable. + """ + if testcases is None: + raise ValueError("Parameter 'testcases' is None.") + elif not isinstance(testcases, Iterable): + ex = TypeError(f"Parameter 'testcases' is not iterable.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(testcases)}'.") + raise ex + + for testcase in testcases: + self.AddTestcase(testcase)
+ + + def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]: + assert IterationScheme.PreOrder | IterationScheme.PostOrder not in scheme + + if IterationScheme.PreOrder in scheme: + if IterationScheme.IncludeSelf | IterationScheme.IncludeTestsuites in scheme: + yield self + + if IterationScheme.IncludeTestcases in scheme: + for testcase in self._testcases.values(): + yield testcase + + for testsuite in self._testsuites.values(): + yield from testsuite.Iterate(scheme | IterationScheme.IncludeSelf) + + if IterationScheme.PostOrder in scheme: + if IterationScheme.IncludeTestcases in scheme: + for testcase in self._testcases.values(): + yield testcase + + if IterationScheme.IncludeSelf | IterationScheme.IncludeTestsuites in scheme: + yield self + +
+[docs] + def __str__(self) -> str: + return ( + f"<Testsuite {self._name}: {self._status.name} -" + # f" assert/pass/fail:{self._assertionCount}/{self._passedAssertionCount}/{self._failedAssertionCount} -" + f" warn/error/fatal:{self._warningCount}/{self._errorCount}/{self._fatalCount}>" + )
+
+ + + +
+[docs] +@export +class TestsuiteSummary(TestsuiteBase[TestsuiteType]): + """ + A testsuite summary is the root element in the test entity hierarchy representing a summary of all test suites and cases. + + The testsuite summary contains test suites, which in turn can contain test suites and test cases. + """ + +
+[docs] + def __init__( + self, + name: str, + startTime: Nullable[datetime] = None, + setupDuration: Nullable[timedelta] = None, + testDuration: Nullable[timedelta] = None, + teardownDuration: Nullable[timedelta] = None, + totalDuration: Nullable[timedelta] = None, + status: TestsuiteStatus = TestsuiteStatus.Unknown, + warningCount: int = 0, + errorCount: int = 0, + fatalCount: int = 0, + testsuites: Nullable[Iterable[TestsuiteType]] = None, + keyValuePairs: Nullable[Mapping[str, Any]] = None, + parent: Nullable[TestsuiteType] = None + ): + """ + Initializes the fields of a test summary. + + :param name: Name of the test summary. + :param startTime: Time when the test summary was started. + :param setupDuration: Duration it took to set up the test summary. + :param testDuration: Duration of all tests listed in the test summary. + :param teardownDuration: Duration it took to tear down the test summary. + :param totalDuration: Total duration of the entity's execution (setup + test + teardown) + :param status: Overall status of the test summary. + :param warningCount: Count of encountered warnings incl. warnings from sub-elements. + :param errorCount: Count of encountered errors incl. errors from sub-elements. + :param fatalCount: Count of encountered fatal errors incl. fatal errors from sub-elements. + :param testsuites: List of test suites to initialize the test summary with. + :param keyValuePairs: Mapping of key-value pairs to initialize the test summary with. + :param parent: Reference to the parent test summary. + """ + super().__init__( + name, + TestsuiteKind.Root, + startTime, + setupDuration, + testDuration, + teardownDuration, + totalDuration, + status, + warningCount, + errorCount, + fatalCount, + testsuites, + keyValuePairs, + parent + )
+ + +
+[docs] + def Aggregate(self, strict: bool = True) -> TestsuiteAggregateReturnType: + tests, inconsistent, excluded, skipped, errored, weak, failed, passed, warningCount, errorCount, fatalCount, totalDuration = super().Aggregate(strict) + + self._tests = tests + self._inconsistent = inconsistent + self._excluded = excluded + self._skipped = skipped + self._errored = errored + self._weak = weak + self._failed = failed + self._passed = passed + + self._warningCount = warningCount + self._errorCount = errorCount + self._fatalCount = fatalCount + + if self._totalDuration is None: + self._totalDuration = totalDuration + + if errored > 0: + self._status = TestsuiteStatus.Errored + elif failed > 0: + self._status = TestsuiteStatus.Failed + elif tests == 0: + self._status = TestsuiteStatus.Empty + elif tests - skipped == passed: + self._status = TestsuiteStatus.Passed + elif tests == skipped: + self._status = TestsuiteStatus.Skipped + elif tests == excluded: + self._status = TestsuiteStatus.Excluded + else: + self._status = TestsuiteStatus.Unknown + + return tests, inconsistent, excluded, skipped, errored, weak, failed, passed, warningCount, errorCount, fatalCount, totalDuration
+ + + def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]: + if IterationScheme.IncludeSelf | IterationScheme.IncludeTestsuites | IterationScheme.PreOrder in scheme: + yield self + + for testsuite in self._testsuites.values(): + yield from testsuite.IterateTestsuites(scheme | IterationScheme.IncludeSelf) + + if IterationScheme.IncludeSelf | IterationScheme.IncludeTestsuites | IterationScheme.PostOrder in scheme: + yield self + +
+[docs] + def __str__(self) -> str: + return ( + f"<TestsuiteSummary {self._name}: {self._status.name} -" + # f" assert/pass/fail:{self._assertionCount}/{self._passedAssertionCount}/{self._failedAssertionCount} -" + f" warn/error/fatal:{self._warningCount}/{self._errorCount}/{self._fatalCount}>" + )
+
+ + + +
+[docs] +@export +class Document(metaclass=ExtendedType, mixin=True): + """A mixin-class representing a unit test summary document (file).""" + + _path: Path + + _analysisDuration: float #: TODO: replace by Timer; should be timedelta? + _modelConversion: float #: TODO: replace by Timer; should be timedelta? + +
+[docs] + def __init__(self, reportFile: Path, analyzeAndConvert: bool = False): + self._path = reportFile + + self._analysisDuration = -1.0 + self._modelConversion = -1.0 + + if analyzeAndConvert: + self.Analyze() + self.Convert()
+ + + @readonly + def Path(self) -> Path: + """ + Read-only property returning the path to the file of this document. + + :return: The document's path to the file. + """ + return self._path + + @readonly + def AnalysisDuration(self) -> timedelta: + """ + Read-only property returning analysis duration. + + .. note:: + + This includes usually the duration to validate and parse the file format, but it excludes the time to convert the + content to the test entity hierarchy. + + :return: Duration to analyze the document. + """ + return timedelta(seconds=self._analysisDuration) + + @readonly + def ModelConversionDuration(self) -> timedelta: + """ + Read-only property returning conversion duration. + + .. note:: + + This includes usually the duration to convert the document's content to the test entity hierarchy. It might also + include the duration to (re-)aggregate all states and statistics in the hierarchy. + + :return: Duration to convert the document. + """ + return timedelta(seconds=self._modelConversion) + +
+[docs] + @abstractmethod + def Analyze(self) -> None: + """Analyze and validate the document's content."""
+ + + # @abstractmethod + # def Write(self, path: Nullable[Path] = None, overwrite: bool = False): + # pass + +
+[docs] + @abstractmethod + def Convert(self): + """Convert the document's content to an instance of the test entity hierarchy."""
+
+ + + +
+[docs] +@export +class Merged(metaclass=ExtendedType, mixin=True): + """A mixin-class representing a merged test entity.""" + + _mergedCount: int + +
+[docs] + def __init__(self, mergedCount: int = 1): + self._mergedCount = mergedCount
+ + + @readonly + def MergedCount(self) -> int: + return self._mergedCount
+ + + +
+[docs] +@export +class Combined(metaclass=ExtendedType, mixin=True): + _combinedCount: int + +
+[docs] + def __init__(self, combinedCound: int = 1): + self._combinedCount = combinedCound
+ + + @readonly + def CombinedCount(self) -> int: + return self._combinedCount
+ + + +
+[docs] +@export +class MergedTestcase(Testcase, Merged): + _mergedTestcases: List[Testcase] + +
+[docs] + def __init__( + self, + testcase: Testcase, + parent: Nullable["Testsuite"] = None + ): + if testcase is None: + raise ValueError(f"Parameter 'testcase' is None.") + + super().__init__( + testcase._name, + testcase._startTime, + testcase._setupDuration, testcase._testDuration, testcase._teardownDuration, testcase._totalDuration, + TestcaseStatus.Unknown, + testcase._assertionCount, testcase._failedAssertionCount, testcase._passedAssertionCount, + testcase._warningCount, testcase._errorCount, testcase._fatalCount, + parent + ) + Merged.__init__(self) + + self._mergedTestcases = [testcase]
+ + + @readonly + def Status(self) -> TestcaseStatus: + if self._status is TestcaseStatus.Unknown: + status = self._mergedTestcases[0]._status + for mtc in self._mergedTestcases[1:]: + status @= mtc._status + + self._status = status + + return self._status + + @readonly + def SummedAssertionCount(self) -> int: + return sum(tc._assertionCount for tc in self._mergedTestcases) + + @readonly + def SummedPassedAssertionCount(self) -> int: + return sum(tc._passedAssertionCount for tc in self._mergedTestcases) + + @readonly + def SummedFailedAssertionCount(self) -> int: + return sum(tc._failedAssertionCount for tc in self._mergedTestcases) + +
+[docs] + def Aggregate(self, strict: bool = True) -> TestcaseAggregateReturnType: + firstMTC = self._mergedTestcases[0] + + status = firstMTC._status + warningCount = firstMTC._warningCount + errorCount = firstMTC._errorCount + fatalCount = firstMTC._fatalCount + totalDuration = firstMTC._totalDuration + + for mtc in self._mergedTestcases[1:]: + status @= mtc._status + warningCount += mtc._warningCount + errorCount += mtc._errorCount + fatalCount += mtc._fatalCount + + self._status = status + + return warningCount, errorCount, fatalCount, totalDuration
+ + + def Merge(self, tc: Testcase) -> None: + self._mergedCount += 1 + + self._mergedTestcases.append(tc) + + self._warningCount += tc._warningCount + self._errorCount += tc._errorCount + self._fatalCount += tc._fatalCount + + def ToTestcase(self) -> Testcase: + return Testcase( + self._name, + self._startTime, + self._setupDuration, + self._testDuration, + self._teardownDuration, + self._totalDuration, + self._status, + self._assertionCount, + self._failedAssertionCount, + self._passedAssertionCount, + self._warningCount, + self._errorCount, + self._fatalCount + )
+ + + +
+[docs] +@export +class MergedTestsuite(Testsuite, Merged): +
+[docs] + def __init__( + self, + testsuite: Testsuite, + addTestsuites: bool = False, + addTestcases: bool = False, + parent: Nullable["Testsuite"] = None + ): + if testsuite is None: + raise ValueError(f"Parameter 'testsuite' is None.") + + super().__init__( + testsuite._name, + testsuite._kind, + testsuite._startTime, + testsuite._setupDuration, testsuite._testDuration, testsuite._teardownDuration, testsuite._totalDuration, + TestsuiteStatus.Unknown, + testsuite._warningCount, testsuite._errorCount, testsuite._fatalCount, + parent + ) + Merged.__init__(self) + + if addTestsuites: + for ts in testsuite._testsuites.values(): + mergedTestsuite = MergedTestsuite(ts, addTestsuites, addTestcases) + self.AddTestsuite(mergedTestsuite) + + if addTestcases: + for tc in testsuite._testcases.values(): + mergedTestcase = MergedTestcase(tc) + self.AddTestcase(mergedTestcase)
+ + + def Merge(self, testsuite: Testsuite) -> None: + self._mergedCount += 1 + + for ts in testsuite._testsuites.values(): + if ts._name in self._testsuites: + self._testsuites[ts._name].Merge(ts) + else: + mergedTestsuite = MergedTestsuite(ts, addTestsuites=True, addTestcases=True) + self.AddTestsuite(mergedTestsuite) + + for tc in testsuite._testcases.values(): + if tc._name in self._testcases: + self._testcases[tc._name].Merge(tc) + else: + mergedTestcase = MergedTestcase(tc) + self.AddTestcase(mergedTestcase) + + def ToTestsuite(self) -> Testsuite: + testsuite = Testsuite( + self._name, + self._kind, + self._startTime, + self._setupDuration, + self._testDuration, + self._teardownDuration, + self._totalDuration, + self._status, + self._warningCount, + self._errorCount, + self._fatalCount, + testsuites=(ts.ToTestsuite() for ts in self._testsuites.values()), + testcases=(tc.ToTestcase() for tc in self._testcases.values()) + ) + + testsuite._tests = self._tests + testsuite._excluded = self._excluded + testsuite._inconsistent = self._inconsistent + testsuite._skipped = self._skipped + testsuite._errored = self._errored + testsuite._weak = self._weak + testsuite._failed = self._failed + testsuite._passed = self._passed + + return testsuite
+ + + +
+[docs] +@export +class MergedTestsuiteSummary(TestsuiteSummary, Merged): + _mergedFiles: Dict[Path, TestsuiteSummary] + +
+[docs] + def __init__(self, name: str) -> None: + super().__init__(name) + Merged.__init__(self, mergedCount=0) + + self._mergedFiles = {}
+ + + def Merge(self, testsuiteSummary: TestsuiteSummary) -> None: + # if summary.File in self._mergedFiles: + # raise + + # FIXME: a summary is not necessarily a file + self._mergedCount += 1 + self._mergedFiles[testsuiteSummary._name] = testsuiteSummary + + for testsuite in testsuiteSummary._testsuites.values(): + if testsuite._name in self._testsuites: + self._testsuites[testsuite._name].Merge(testsuite) + else: + mergedTestsuite = MergedTestsuite(testsuite, addTestsuites=True, addTestcases=True) + self.AddTestsuite(mergedTestsuite) + + def ToTestsuiteSummary(self) -> TestsuiteSummary: + testsuiteSummary = TestsuiteSummary( + self._name, + self._startTime, + self._setupDuration, + self._testDuration, + self._teardownDuration, + self._totalDuration, + self._status, + self._warningCount, + self._errorCount, + self._fatalCount, + testsuites=(ts.ToTestsuite() for ts in self._testsuites.values()) + ) + + testsuiteSummary._tests = self._tests + testsuiteSummary._excluded = self._excluded + testsuiteSummary._inconsistent = self._inconsistent + testsuiteSummary._skipped = self._skipped + testsuiteSummary._errored = self._errored + testsuiteSummary._weak = self._weak + testsuiteSummary._failed = self._failed + testsuiteSummary._passed = self._passed + + return testsuiteSummary
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_modules/pyEDAA/Reports/Unittesting/JUnit.html b/_modules/pyEDAA/Reports/Unittesting/JUnit.html new file mode 100644 index 00000000..868c645f --- /dev/null +++ b/_modules/pyEDAA/Reports/Unittesting/JUnit.html @@ -0,0 +1,2019 @@ + + + + + + + + pyEDAA.Reports.Unittesting.JUnit — pyEDAA.Reports 0.14.1 documentation + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for pyEDAA.Reports.Unittesting.JUnit

+# ==================================================================================================================== #
+#              _____ ____    _        _      ____                       _                                              #
+#  _ __  _   _| ____|  _ \  / \      / \    |  _ \ ___ _ __   ___  _ __| |_ ___                                        #
+# | '_ \| | | |  _| | | | |/ _ \    / _ \   | |_) / _ \ '_ \ / _ \| '__| __/ __|                                       #
+# | |_) | |_| | |___| |_| / ___ \  / ___ \ _|  _ <  __/ |_) | (_) | |  | |_\__ \                                       #
+# | .__/ \__, |_____|____/_/   \_\/_/   \_(_)_| \_\___| .__/ \___/|_|   \__|___/                                       #
+# |_|    |___/                                        |_|                                                              #
+# ==================================================================================================================== #
+# Authors:                                                                                                             #
+#   Patrick Lehmann                                                                                                    #
+#                                                                                                                      #
+# License:                                                                                                             #
+# ==================================================================================================================== #
+# Copyright 2024-2024 Electronic Design Automation Abstraction (EDA²)                                                  #
+# Copyright 2023-2023 Patrick Lehmann - Bötzingen, Germany                                                             #
+#                                                                                                                      #
+# 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.                                                                                       #
+#                                                                                                                      #
+# SPDX-License-Identifier: Apache-2.0                                                                                  #
+# ==================================================================================================================== #
+#
+"""
+The pyEDAA.Reports.Unittesting.JUnit package implements a hierarchy of test entities for the JUnit unit testing summary
+file format (XML format). This test entity hierarchy is not derived from :class:`pyEDAA.Reports.Unittesting`, because it
+doesn't match the unified data model. Nonetheless, both data models can be converted to each other. In addition, derived
+data models are provided for the many dialects of that XML file format. See the list modules in this package for the
+implemented dialects.
+
+The test entity hierarchy consists of test cases, test classes, test suites and a test summary. Test cases are the leaf
+elements in the hierarchy and represent an individual test run. Next, test classes group test cases, because the
+original Ant + JUnit format groups test cases (Java methods) in a Java class. Next, test suites are used to group
+multiple test classes. Finally, the root element is a test summary. When such a summary is stored in a file format like
+Ant + JUnit4 XML, a file format specific document is derived from a summary class.
+
+**Data Model**
+
+.. mermaid::
+
+	 graph TD;
+		 doc[Document]
+		 sum[Summary]
+		 ts1[Testsuite]
+		 ts11[Testsuite]
+		 ts2[Testsuite]
+
+		 tc111[Testclass]
+		 tc112[Testclass]
+		 tc23[Testclass]
+
+		 tc1111[Testcase]
+		 tc1112[Testcase]
+		 tc1113[Testcase]
+		 tc1121[Testcase]
+		 tc1122[Testcase]
+		 tc231[Testcase]
+		 tc232[Testcase]
+		 tc233[Testcase]
+
+		 doc:::root -.-> sum:::summary
+		 sum --> ts1:::suite
+		 sum ---> ts2:::suite
+		 ts1 --> ts11:::suite
+
+		 ts11 --> tc111:::cls
+		 ts11 --> tc112:::cls
+		 ts2  --> tc23:::cls
+
+		 tc111 --> tc1111:::case
+		 tc111 --> tc1112:::case
+		 tc111 --> tc1113:::case
+		 tc112 --> tc1121:::case
+		 tc112 --> tc1122:::case
+		 tc23 --> tc231:::case
+		 tc23 --> tc232:::case
+		 tc23 --> tc233:::case
+
+		 classDef root fill:#4dc3ff
+		 classDef summary fill:#80d4ff
+		 classDef suite fill:#b3e6ff
+		 classDef cls fill:#ff9966
+		 classDef case fill:#eeccff
+"""
+from datetime        import datetime, timedelta
+from enum            import Flag
+from pathlib         import Path
+from sys             import version_info
+from time            import perf_counter_ns
+from typing          import Optional as Nullable, Iterable, Dict, Any, Generator, Tuple, Union, TypeVar, Type, ClassVar
+
+from lxml.etree                 import XMLParser, parse, XMLSchema, ElementTree, Element, SubElement, tostring
+from lxml.etree                 import XMLSyntaxError, _ElementTree, _Element, _Comment, XMLSchemaParseError
+from pyTooling.Common           import getFullyQualifiedName, getResourceFile
+from pyTooling.Decorators       import export, readonly
+from pyTooling.Exceptions       import ToolingException
+from pyTooling.MetaClasses      import ExtendedType, mustoverride, abstractmethod
+from pyTooling.Tree             import Node
+
+from pyEDAA.Reports             import Resources
+from pyEDAA.Reports.Unittesting import UnittestException, AlreadyInHierarchyException, DuplicateTestsuiteException, DuplicateTestcaseException
+from pyEDAA.Reports.Unittesting import TestcaseStatus, TestsuiteStatus, TestsuiteKind, IterationScheme
+from pyEDAA.Reports.Unittesting import Document as ut_Document, TestsuiteSummary as ut_TestsuiteSummary
+from pyEDAA.Reports.Unittesting import Testsuite as ut_Testsuite, Testcase as ut_Testcase
+
+
+
+[docs] +@export +class JUnitException: + """An exception-mixin for JUnit format specific exceptions."""
+ + + +
+[docs] +@export +class UnittestException(UnittestException, JUnitException): + pass
+ + + +
+[docs] +@export +class AlreadyInHierarchyException(AlreadyInHierarchyException, JUnitException): + """ + A unit test exception raised if the element is already part of a hierarchy. + + This exception is caused by an inconsistent data model. Elements added to the hierarchy should be part of the same + hierarchy should occur only once in the hierarchy. + + .. hint:: + + This is usually caused by a non-None parent reference. + """
+ + + +
+[docs] +@export +class DuplicateTestsuiteException(DuplicateTestsuiteException, JUnitException): + """ + A unit test exception raised on duplicate test suites (by name). + + This exception is raised, if a child test suite with same name already exist in the test suite. + + .. hint:: + + Test suite names need to be unique per parent element (test suite or test summary). + """
+ + + +
+[docs] +@export +class DuplicateTestcaseException(DuplicateTestcaseException, JUnitException): + """ + A unit test exception raised on duplicate test cases (by name). + + This exception is raised, if a child test case with same name already exist in the test suite. + + .. hint:: + + Test case names need to be unique per parent element (test suite). + """
+ + + +
+[docs] +@export +class JUnitReaderMode(Flag): + Default = 0 #: Default behavior + DecoupleTestsuiteHierarchyAndTestcaseClassName = 1 #: Undocumented
+ + + +TestsuiteType = TypeVar("TestsuiteType", bound="Testsuite") +TestcaseAggregateReturnType = Tuple[int, int, int] +TestsuiteAggregateReturnType = Tuple[int, int, int, int, int] + + +
+[docs] +@export +class Base(metaclass=ExtendedType, slots=True): + """ + Base-class for all test entities (test cases, test classes, test suites, ...). + + It provides a reference to the parent test entity, so bidirectional referencing can be used in the test entity + hierarchy. + + Every test entity has a name to identity it. It's also used in the parent's child element dictionaries to identify the + child. |br| + E.g. it's used as a test case name in the dictionary of test cases in a test class. + """ + + _parent: Nullable["Testsuite"] + _name: str + +
+[docs] + def __init__(self, name: str, parent: Nullable["Testsuite"] = None): + """ + Initializes the fields of the base-class. + + :param name: Name of the test entity. + :param parent: Reference to the parent test entity. + :raises ValueError: If parameter 'name' is None. + :raises TypeError: If parameter 'name' is not a string. + :raises ValueError: If parameter 'name' is empty. + """ + if name is None: + raise ValueError(f"Parameter 'name' is None.") + elif not isinstance(name, str): + ex = TypeError(f"Parameter 'name' is not of type 'str'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(name)}'.") + raise ex + elif name.strip() == "": + raise ValueError(f"Parameter 'name' is empty.") + + self._parent = parent + self._name = name
+ + + @readonly + def Parent(self) -> Nullable["Testsuite"]: + """ + Read-only property returning the reference to the parent test entity. + + :return: Reference to the parent entity. + """ + return self._parent + + # QUESTION: allow Parent as setter? + + @readonly + def Name(self) -> str: + """ + Read-only property returning the test entity's name. + + :return: + """ + return self._name
+ + + +
+[docs] +@export +class BaseWithProperties(Base): + """ + Base-class for all test entities supporting properties (test cases, test suites, ...). + + Every test entity has fields for the test duration and number of executed assertions. + + Every test entity offers an internal dictionary for properties. + """ + + _duration: Nullable[timedelta] + _assertionCount: Nullable[int] + _properties: Dict[str, Any] + +
+[docs] + def __init__( + self, + name: str, + duration: Nullable[timedelta] = None, + assertionCount: Nullable[int] = None, + parent: Nullable["Testsuite"] = None + ): + """ + Initializes the fields of the base-class. + + :param name: Name of the test entity. + :param duration: Duration of the entity's execution. + :param assertionCount: Number of assertions within the test. + :param parent: Reference to the parent test entity. + :raises TypeError: If parameter 'duration' is not a timedelta. + :raises TypeError: If parameter 'assertionCount' is not an integer. + """ + super().__init__(name, parent) + + if duration is not None and not isinstance(duration, timedelta): + ex = TypeError(f"Parameter 'duration' is not of type 'timedelta'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(duration)}'.") + raise ex + + if assertionCount is not None and not isinstance(assertionCount, int): + ex = TypeError(f"Parameter 'assertionCount' is not of type 'int'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(assertionCount)}'.") + raise ex + + self._duration = duration + self._assertionCount = assertionCount + + self._properties = {}
+ + + @readonly + def Duration(self) -> timedelta: + """ + Read-only property returning the duration of a test entity run. + + .. note:: + + The JUnit format doesn't distinguish setup, run and teardown durations. + + :return: Duration of the entity's execution. + """ + return self._duration + + @readonly + @abstractmethod + def AssertionCount(self) -> int: + """ + Read-only property returning the number of assertions (checks) in a test case. + + .. note:: + + The JUnit format doesn't distinguish passed and failed assertions. + + :return: Number of assertions. + """ + +
+[docs] + def __len__(self) -> int: + """ + Returns the number of annotated properties. + + Syntax: :pycode:`length = len(obj)` + + :return: Number of annotated properties. + """ + return len(self._properties)
+ + +
+[docs] + def __getitem__(self, name: str) -> Any: + """ + Access a property by name. + + Syntax: :pycode:`value = obj[name]` + + :param name: Name if the property. + :return: Value of the accessed property. + """ + return self._properties[name]
+ + +
+[docs] + def __setitem__(self, name: str, value: Any) -> None: + """ + Set the value of a property by name. + + If the property doesn't exist yet, it's created. + + Syntax: :pycode:`obj[name] = value` + + :param name: Name of the property. + :param value: Value of the property. + """ + self._properties[name] = value
+ + +
+[docs] + def __delitem__(self, name: str) -> None: + """ + Delete a property by name. + + Syntax: :pycode:`del obj[name]` + + :param name: Name if the property. + """ + del self._properties[name]
+ + +
+[docs] + def __contains__(self, name: str) -> bool: + """ + Returns True, if a property was annotated by this name. + + Syntax: :pycode:`name in obj` + + :param name: Name of the property. + :return: True, if the property was annotated. + """ + return name in self._properties
+ + +
+[docs] + def __iter__(self) -> Generator[Tuple[str, Any], None, None]: + """ + Iterate all annotated properties. + + Syntax: :pycode:`for name, value in obj:` + + :return: A generator of property tuples (name, value). + """ + yield from self._properties.items()
+
+ + + +
+[docs] +@export +class Testcase(BaseWithProperties): + """ + A testcase is the leaf-entity in the test entity hierarchy representing an individual test run. + + Test cases are grouped by test classes in the test entity hierarchy. These are again grouped by test suites. The root + of the hierarchy is a test summary. + + Every test case has an overall status like unknown, skipped, failed or passed. + """ + + _status: TestcaseStatus + +
+[docs] + def __init__( + self, + name: str, + duration: Nullable[timedelta] = None, + status: TestcaseStatus = TestcaseStatus.Unknown, + assertionCount: Nullable[int] = None, + parent: Nullable["Testclass"] = None + ): + """ + Initializes the fields of a test case. + + :param name: Name of the test entity. + :param duration: Duration of the entity's execution. + :param status: Status of the test case. + :param assertionCount: Number of assertions within the test. + :param parent: Reference to the parent test class. + :raises TypeError: If parameter 'parent' is not a Testsuite. + :raises ValueError: If parameter 'assertionCount' is not consistent. + """ + if parent is not None: + if not isinstance(parent, Testclass): + ex = TypeError(f"Parameter 'parent' is not of type 'Testclass'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") + raise ex + + parent._testcases[name] = self + + super().__init__(name, duration, assertionCount, parent) + + if not isinstance(status, TestcaseStatus): + ex = TypeError(f"Parameter 'status' is not of type 'TestcaseStatus'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(status)}'.") + raise ex + + self._status = status
+ + + @readonly + def Classname(self) -> str: + """ + Read-only property returning the class name of the test case. + + :return: The test case's class name. + + .. note:: + + In the JUnit format, a test case is uniquely identified by a tuple of class name and test case name. This + structure has been decomposed by this data model into 2 leaf-levels in the test entity hierarchy. Thus, the class + name is represented by its own level and instances of test classes. + """ + if self._parent is None: + raise UnittestException("Standalone Testcase instance is not linked to a Testclass.") + return self._parent._name + + @readonly + def Status(self) -> TestcaseStatus: + """ + Read-only property returning the status of the test case. + + :return: The test case's status. + """ + return self._status + + @readonly + def AssertionCount(self) -> int: + """ + Read-only property returning the number of assertions (checks) in a test case. + + .. note:: + + The JUnit format doesn't distinguish passed and failed assertions. + + :return: Number of assertions. + """ + if self._assertionCount is None: + return 0 + return self._assertionCount + + def Copy(self) -> "Testcase": + return self.__class__( + self._name, + self._duration, + self._status, + self._assertionCount + ) + + def Aggregate(self) -> None: + if self._status is TestcaseStatus.Unknown: + if self._assertionCount is None: + self._status = TestcaseStatus.Passed + elif self._assertionCount == 0: + self._status = TestcaseStatus.Weak + else: + self._status = TestcaseStatus.Failed + + # TODO: check for setup errors + # TODO: check for teardown errors + +
+[docs] + @classmethod + def FromTestcase(cls, testcase: ut_Testcase) -> "Testcase": + """ + Convert a test case of the unified test entity data model to the JUnit specific data model's test case object. + + :param testcase: Test case from unified data model. + :return: Test case from JUnit specific data model. + """ + return cls( + testcase._name, + duration=testcase._testDuration, + status= testcase._status, + assertionCount=testcase._assertionCount + )
+ + + def ToTestcase(self) -> ut_Testcase: + return ut_Testcase( + self._name, + testDuration=self._duration, + status=self._status, + assertionCount=self._assertionCount, + # TODO: as only assertions are recorded by JUnit files, all are marked as passed + passedAssertionCount=self._assertionCount + ) + + def ToTree(self) -> Node: + node = Node(value=self._name) + node["status"] = self._status + node["assertionCount"] = self._assertionCount + node["duration"] = self._duration + + return node + +
+[docs] + def __str__(self) -> str: + moduleName = self.__module__.split(".")[-1] + className = self.__class__.__name__ + return ( + f"<{moduleName}{className} {self._name}: {self._status.name} - asserts:{self._assertionCount}>" + )
+
+ + + +
+[docs] +@export +class TestsuiteBase(BaseWithProperties): + """ + Base-class for all test suites and for test summaries. + + A test suite is a mid-level grouping element in the test entity hierarchy, whereas the test summary is the root + element in that hierarchy. While a test suite groups test classes, a test summary can only group test suites. Thus, a + test summary contains no test classes and test cases. + """ + + _startTime: Nullable[datetime] + _status: TestsuiteStatus + + _tests: int + _skipped: int + _errored: int + _failed: int + _passed: int + +
+[docs] + def __init__( + self, + name: str, + startTime: Nullable[datetime] = None, + duration: Nullable[timedelta] = None, + status: TestsuiteStatus = TestsuiteStatus.Unknown, + parent: Nullable["Testsuite"] = None + ): + """ + Initializes the based-class fields of a test suite or test summary. + + :param name: Name of the test entity. + :param startTime: Time when the test entity was started. + :param duration: Duration of the entity's execution. + :param status: Overall status of the test entity. + :param parent: Reference to the parent test entity. + :raises TypeError: If parameter 'parent' is not a TestsuiteBase. + """ + if parent is not None: + if not isinstance(parent, TestsuiteBase): + ex = TypeError(f"Parameter 'parent' is not of type 'TestsuiteBase'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") + raise ex + + parent._testsuites[name] = self + + super().__init__(name, duration, None, parent) + + self._startTime = startTime + self._status = status + self._tests = 0 + self._skipped = 0 + self._errored = 0 + self._failed = 0 + self._passed = 0
+ + + @readonly + def StartTime(self) -> Nullable[datetime]: + return self._startTime + + @readonly + def Status(self) -> TestsuiteStatus: + return self._status + + @readonly + @mustoverride + def TestcaseCount(self) -> int: + pass + + @readonly + def Tests(self) -> int: + return self.TestcaseCount + + @readonly + def Skipped(self) -> int: + return self._skipped + + @readonly + def Errored(self) -> int: + return self._errored + + @readonly + def Failed(self) -> int: + return self._failed + + @readonly + def Passed(self) -> int: + return self._passed + + def Aggregate(self) -> TestsuiteAggregateReturnType: + tests = 0 + skipped = 0 + errored = 0 + failed = 0 + passed = 0 + + # for testsuite in self._testsuites.values(): + # t, s, e, w, f, p = testsuite.Aggregate() + # tests += t + # skipped += s + # errored += e + # weak += w + # failed += f + # passed += p + + return tests, skipped, errored, failed, passed + + @mustoverride + def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]: + pass
+ + + +
+[docs] +@export +class Testclass(Base): + """ + A test class is a low-level element in the test entity hierarchy representing a group of tests. + + Test classes contain test cases and are grouped by a test suites. + """ + + _testcases: Dict[str, "Testcase"] + +
+[docs] + def __init__( + self, + classname: str, + testcases: Nullable[Iterable["Testcase"]] = None, + parent: Nullable["Testsuite"] = None + ): + """ + Initializes the fields of the test class. + + :param classname: Classname of the test entity. + :param parent: Reference to the parent test suite. + :raises ValueError: If parameter 'classname' is None. + :raises TypeError: If parameter 'classname' is not a string. + :raises ValueError: If parameter 'classname' is empty. + """ + if parent is not None: + if not isinstance(parent, Testsuite): + ex = TypeError(f"Parameter 'parent' is not of type 'Testsuite'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") + raise ex + + parent._testclasses[classname] = self + + super().__init__(classname, parent) + + self._testcases = {} + if testcases is not None: + for testcase in testcases: + if testcase._parent is not None: + raise AlreadyInHierarchyException(f"Testcase '{testcase._name}' is already part of a testsuite hierarchy.") + + if testcase._name in self._testcases: + raise DuplicateTestcaseException(f"Class already contains a testcase with same name '{testcase._name}'.") + + testcase._parent = self + self._testcases[testcase._name] = testcase
+ + + @readonly + def Classname(self) -> str: + """ + Read-only property returning the name of the test class. + + :return: The test class' name. + """ + return self._name + + @readonly + def Testcases(self) -> Dict[str, "Testcase"]: + """ + Read-only property returning a reference to the internal dictionary of test cases. + + :return: Reference to the dictionary of test cases. + """ + return self._testcases + + @readonly + def TestcaseCount(self) -> int: + """ + Read-only property returning the number of all test cases in the test entity hierarchy. + + :return: Number of test cases. + """ + return len(self._testcases) + + @readonly + def AssertionCount(self) -> int: + return sum(tc.AssertionCount for tc in self._testcases.values()) + + def AddTestcase(self, testcase: "Testcase") -> None: + if testcase._parent is not None: + raise ValueError(f"Testcase '{testcase._name}' is already part of a testsuite hierarchy.") + + if testcase._name in self._testcases: + raise DuplicateTestcaseException(f"Class already contains a testcase with same name '{testcase._name}'.") + + testcase._parent = self + self._testcases[testcase._name] = testcase + + def AddTestcases(self, testcases: Iterable["Testcase"]) -> None: + for testcase in testcases: + self.AddTestcase(testcase) + + def ToTestsuite(self) -> ut_Testsuite: + return ut_Testsuite( + self._name, + TestsuiteKind.Class, + # startTime=self._startTime, + # totalDuration=self._duration, + # status=self._status, + testcases=(tc.ToTestcase() for tc in self._testcases.values()) + ) + + def ToTree(self) -> Node: + node = Node( + value=self._name, + children=(tc.ToTree() for tc in self._testcases.values()) + ) + + return node + +
+[docs] + def __str__(self) -> str: + moduleName = self.__module__.split(".")[-1] + className = self.__class__.__name__ + return ( + f"<{moduleName}{className} {self._name}: {len(self._testcases)}>" + )
+
+ + + +
+[docs] +@export +class Testsuite(TestsuiteBase): + """ + A testsuite is a mid-level element in the test entity hierarchy representing a logical group of tests. + + Test suites contain test classes and are grouped by a test summary, which is the root of the hierarchy. + """ + + _hostname: str + _testclasses: Dict[str, "Testclass"] + +
+[docs] + def __init__( + self, + name: str, + hostname: Nullable[str] = None, + startTime: Nullable[datetime] = None, + duration: Nullable[timedelta] = None, + status: TestsuiteStatus = TestsuiteStatus.Unknown, + testclasses: Nullable[Iterable["Testclass"]] = None, + parent: Nullable["TestsuiteSummary"] = None + ): + """ + Initializes the fields of a test suite. + + :param name: Name of the test suite. + :param startTime: Time when the test suite was started. + :param duration: duration of the entity's execution. + :param status: Overall status of the test suite. + :param parent: Reference to the parent test summary. + :raises TypeError: If parameter 'testcases' is not iterable. + :raises TypeError: If element in parameter 'testcases' is not a Testcase. + :raises AlreadyInHierarchyException: If a test case in parameter 'testcases' is already part of a test entity hierarchy. + :raises DuplicateTestcaseException: If a test case in parameter 'testcases' is already listed (by name) in the list of test cases. + """ + if parent is not None: + if not isinstance(parent, TestsuiteSummary): + ex = TypeError(f"Parameter 'parent' is not of type 'TestsuiteSummary'.") + if version_info >= (3, 11): # pragma: no cover + ex.add_note(f"Got type '{getFullyQualifiedName(parent)}'.") + raise ex + + parent._testsuites[name] = self + + super().__init__(name, startTime, duration, status, parent) + + self._hostname = hostname + + self._testclasses = {} + if testclasses is not None: + for testclass in testclasses: + if testclass._parent is not None: + raise ValueError(f"Class '{testclass._name}' is already part of a testsuite hierarchy.") + + if testclass._name in self._testclasses: + raise DuplicateTestcaseException(f"Testsuite already contains a class with same name '{testclass._name}'.") + + testclass._parent = self + self._testclasses[testclass._name] = testclass
+ + + @readonly + def Hostname(self) -> Nullable[str]: + return self._hostname + + @readonly + def Testclasses(self) -> Dict[str, "Testclass"]: + return self._testclasses + + @readonly + def TestclassCount(self) -> int: + return len(self._testclasses) + + # @readonly + # def Testcases(self) -> Dict[str, "Testcase"]: + # return self._classes + + @readonly + def TestcaseCount(self) -> int: + return sum(cls.TestcaseCount for cls in self._testclasses.values()) + + @readonly + def AssertionCount(self) -> int: + return sum(cls.AssertionCount for cls in self._testclasses.values()) + + def AddTestclass(self, testclass: "Testclass") -> None: + if testclass._parent is not None: + raise ValueError(f"Class '{testclass._name}' is already part of a testsuite hierarchy.") + + if testclass._name in self._testclasses: + raise DuplicateTestcaseException(f"Testsuite already contains a class with same name '{testclass._name}'.") + + testclass._parent = self + self._testclasses[testclass._name] = testclass + + def AddTestclasses(self, testclasses: Iterable["Testclass"]) -> None: + for testcase in testclasses: + self.AddTestclass(testcase) + + # def IterateTestsuites(self, scheme: IterationScheme = IterationScheme.TestsuiteDefault) -> Generator[TestsuiteType, None, None]: + # return self.Iterate(scheme) + + def IterateTestcases(self, scheme: IterationScheme = IterationScheme.TestcaseDefault) -> Generator[Testcase, None, None]: + return self.Iterate(scheme) + + def Copy(self) -> "Testsuite": + return self.__class__( + self._name, + self._hostname, + self._startTime, + self._duration, + self._status + ) + + def Aggregate(self, strict: bool = True) -> TestsuiteAggregateReturnType: + tests, skipped, errored, failed, passed = super().Aggregate() + + for testclass in self._testclasses.values(): + _ = testclass.Aggregate(strict) + + tests += 1 + + status = testclass._status + if status is TestcaseStatus.Unknown: + raise UnittestException(f"Found testclass '{testclass._name}' with state 'Unknown'.") + elif status is TestcaseStatus.Skipped: + skipped += 1 + elif status is TestcaseStatus.Errored: + errored += 1 + elif status is TestcaseStatus.Passed: + passed += 1 + elif status is TestcaseStatus.Failed: + failed += 1 + elif status & TestcaseStatus.Mask is not TestcaseStatus.Unknown: + raise UnittestException(f"Found testclass '{testclass._name}' with unsupported state '{status}'.") + else: + raise UnittestException(f"Internal error for testclass '{testclass._name}', field '_status' is '{status}'.") + + self._tests = tests + self._skipped = skipped + self._errored = errored + self._failed = failed + self._passed = passed + + if errored > 0: + self._status = TestsuiteStatus.Errored + elif failed > 0: + self._status = TestsuiteStatus.Failed + elif tests == 0: + self._status = TestsuiteStatus.Empty + elif tests - skipped == passed: + self._status = TestsuiteStatus.Passed + elif tests == skipped: + self._status = TestsuiteStatus.Skipped + else: + self._status = TestsuiteStatus.Unknown + + return tests, skipped, errored, failed, passed + +
+[docs] + def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]: + """ + Iterate the test suite and its child elements according to the iteration scheme. + + If no scheme is given, use the default scheme. + + :param scheme: Scheme how to iterate the test suite and its child elements. + :returns: A generator for iterating the results filtered and in the order defined by the iteration scheme. + """ + assert IterationScheme.PreOrder | IterationScheme.PostOrder not in scheme + + if IterationScheme.PreOrder in scheme: + if IterationScheme.IncludeSelf | IterationScheme.IncludeTestsuites in scheme: + yield self + + if IterationScheme.IncludeTestcases in scheme: + for testcase in self._testclasses.values(): + yield testcase + + for testclass in self._testclasses.values(): + yield from testclass.Iterate(scheme | IterationScheme.IncludeSelf) + + if IterationScheme.PostOrder in scheme: + if IterationScheme.IncludeTestcases in scheme: + for testcase in self._testclasses.values(): + yield testcase + + if IterationScheme.IncludeSelf | IterationScheme.IncludeTestsuites in scheme: + yield self
+ + +
+[docs] + @classmethod + def FromTestsuite(cls, testsuite: ut_Testsuite) -> "Testsuite": + """ + Convert a test suite of the unified test entity data model to the JUnit specific data model's test suite object. + + :param testsuite: Test suite from unified data model. + :return: Test suite from JUnit specific data model. + """ + juTestsuite = cls( + testsuite._name, + startTime=testsuite._startTime, + duration=testsuite._totalDuration, + status= testsuite._status, + ) + + juTestsuite._tests = testsuite._tests + juTestsuite._skipped = testsuite._skipped + juTestsuite._errored = testsuite._errored + juTestsuite._failed = testsuite._failed + juTestsuite._passed = testsuite._passed + + for tc in testsuite.IterateTestcases(): + ts = tc._parent + if ts is None: + raise UnittestException(f"Testcase '{tc._name}' is not part of a hierarchy.") + + classname = ts._name + ts = ts._parent + while ts is not None and ts._kind > TestsuiteKind.Logical: + classname = f"{ts._name}.{classname}" + ts = ts._parent + + if classname in juTestsuite._testclasses: + juClass = juTestsuite._testclasses[classname] + else: + juClass = Testclass(classname, parent=juTestsuite) + + juClass.AddTestcase(Testcase.FromTestcase(tc)) + + return juTestsuite
+ + + def ToTestsuite(self) -> ut_Testsuite: + testsuite = ut_Testsuite( + self._name, + TestsuiteKind.Logical, + startTime=self._startTime, + totalDuration=self._duration, + status=self._status, + ) + + for testclass in self._testclasses.values(): + suite = testsuite + classpath = testclass._name.split(".") + for element in classpath: + if element in suite._testsuites: + suite = suite._testsuites[element] + else: + suite = ut_Testsuite(element, kind=TestsuiteKind.Package, parent=suite) + + suite._kind = TestsuiteKind.Class + if suite._parent is not testsuite: + suite._parent._kind = TestsuiteKind.Module + + suite.AddTestcases(tc.ToTestcase() for tc in testclass._testcases.values()) + + return testsuite + + def ToTree(self) -> Node: + node = Node( + value=self._name, + children=(cls.ToTree() for cls in self._testclasses.values()) + ) + node["startTime"] = self._startTime + node["duration"] = self._duration + + return node + +
+[docs] + def __str__(self) -> str: + moduleName = self.__module__.split(".")[-1] + className = self.__class__.__name__ + return ( + f"<{moduleName}{className} {self._name}: {self._status.name} - tests:{self._tests}>" + )
+
+ + + +
+[docs] +@export +class TestsuiteSummary(TestsuiteBase): + _testsuites: Dict[str, Testsuite] + +
+[docs] + def __init__( + self, + name: str, + startTime: Nullable[datetime] = None, + duration: Nullable[timedelta] = None, + status: TestsuiteStatus = TestsuiteStatus.Unknown, + testsuites: Nullable[Iterable[Testsuite]] = None + ): + super().__init__(name, startTime, duration, status, None) + + self._testsuites = {} + if testsuites is not None: + for testsuite in testsuites: + if testsuite._parent is not None: + raise ValueError(f"Testsuite '{testsuite._name}' is already part of a testsuite hierarchy.") + + if testsuite._name in self._testsuites: + raise DuplicateTestsuiteException(f"Testsuite already contains a testsuite with same name '{testsuite._name}'.") + + testsuite._parent = self + self._testsuites[testsuite._name] = testsuite
+ + + @readonly + def Testsuites(self) -> Dict[str, Testsuite]: + return self._testsuites + + @readonly + def TestcaseCount(self) -> int: + return sum(ts.TestcaseCount for ts in self._testsuites.values()) + + @readonly + def TestsuiteCount(self) -> int: + return len(self._testsuites) + + @readonly + def AssertionCount(self) -> int: + return sum(ts.AssertionCount for ts in self._testsuites.values()) + + def AddTestsuite(self, testsuite: Testsuite) -> None: + if testsuite._parent is not None: + raise ValueError(f"Testsuite '{testsuite._name}' is already part of a testsuite hierarchy.") + + if testsuite._name in self._testsuites: + raise DuplicateTestsuiteException(f"Testsuite already contains a testsuite with same name '{testsuite._name}'.") + + testsuite._parent = self + self._testsuites[testsuite._name] = testsuite + + def AddTestsuites(self, testsuites: Iterable[Testsuite]) -> None: + for testsuite in testsuites: + self.AddTestsuite(testsuite) + + def Aggregate(self) -> TestsuiteAggregateReturnType: + tests, skipped, errored, failed, passed = super().Aggregate() + + self._tests = tests + self._skipped = skipped + self._errored = errored + self._failed = failed + self._passed = passed + + if errored > 0: + self._status = TestsuiteStatus.Errored + elif failed > 0: + self._status = TestsuiteStatus.Failed + elif tests == 0: + self._status = TestsuiteStatus.Empty + elif tests - skipped == passed: + self._status = TestsuiteStatus.Passed + elif tests == skipped: + self._status = TestsuiteStatus.Skipped + else: + self._status = TestsuiteStatus.Unknown + + return tests, skipped, errored, failed, passed + +
+[docs] + def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[Testsuite, Testcase], None, None]: + """ + Iterate the test suite summary and its child elements according to the iteration scheme. + + If no scheme is given, use the default scheme. + + :param scheme: Scheme how to iterate the test suite summary and its child elements. + :returns: A generator for iterating the results filtered and in the order defined by the iteration scheme. + """ + if IterationScheme.IncludeSelf | IterationScheme.IncludeTestsuites | IterationScheme.PreOrder in scheme: + yield self + + for testsuite in self._testsuites.values(): + yield from testsuite.IterateTestsuites(scheme | IterationScheme.IncludeSelf) + + if IterationScheme.IncludeSelf | IterationScheme.IncludeTestsuites | IterationScheme.PostOrder in scheme: + yield self
+ + +
+[docs] + @classmethod + def FromTestsuiteSummary(cls, testsuiteSummary: ut_TestsuiteSummary) -> "TestsuiteSummary": + """ + Convert a test suite summary of the unified test entity data model to the JUnit specific data model's test suite. + + :param testsuiteSummary: Test suite summary from unified data model. + :return: Test suite summary from JUnit specific data model. + """ + return cls( + testsuiteSummary._name, + startTime=testsuiteSummary._startTime, + duration=testsuiteSummary._totalDuration, + status=testsuiteSummary._status, + testsuites=(ut_Testsuite.FromTestsuite(testsuite) for testsuite in testsuiteSummary._testsuites.values()) + )
+ + +
+[docs] + def ToTestsuiteSummary(self) -> ut_TestsuiteSummary: + """ + Convert this test suite summary a new test suite summary of the unified data model. + + All fields are copied to the new instance. Child elements like test suites are copied recursively. + + :return: A test suite summary of the unified test entity data model. + """ + return ut_TestsuiteSummary( + self._name, + startTime=self._startTime, + totalDuration=self._duration, + status=self._status, + testsuites=(testsuite.ToTestsuite() for testsuite in self._testsuites.values()) + )
+ + + def ToTree(self) -> Node: + node = Node( + value=self._name, + children=(ts.ToTree() for ts in self._testsuites.values()) + ) + node["startTime"] = self._startTime + node["duration"] = self._duration + + return node + +
+[docs] + def __str__(self) -> str: + moduleName = self.__module__.split(".")[-1] + className = self.__class__.__name__ + return ( + f"<{moduleName}{className} {self._name}: {self._status.name} - tests:{self._tests}>" + )
+
+ + + +
+[docs] +@export +class Document(TestsuiteSummary, ut_Document): + _TESTCASE: ClassVar[Type[Testcase]] = Testcase + _TESTCLASS: ClassVar[Type[Testclass]] = Testclass + _TESTSUITE: ClassVar[Type[Testsuite]] = Testsuite + + _readerMode: JUnitReaderMode + _xmlDocument: Nullable[_ElementTree] + +
+[docs] + def __init__(self, xmlReportFile: Path, analyzeAndConvert: bool = False, readerMode: JUnitReaderMode = JUnitReaderMode.Default): + super().__init__("Unprocessed JUnit XML file") + + self._readerMode = readerMode + self._xmlDocument = None + + ut_Document.__init__(self, xmlReportFile, analyzeAndConvert)
+ + +
+[docs] + @classmethod + def FromTestsuiteSummary(cls, xmlReportFile: Path, testsuiteSummary: ut_TestsuiteSummary): + doc = cls(xmlReportFile) + doc._name = testsuiteSummary._name + doc._startTime = testsuiteSummary._startTime + doc._duration = testsuiteSummary._totalDuration + doc._status = testsuiteSummary._status + doc._tests = testsuiteSummary._tests + doc._skipped = testsuiteSummary._skipped + doc._errored = testsuiteSummary._errored + doc._failed = testsuiteSummary._failed + doc._passed = testsuiteSummary._passed + + doc.AddTestsuites(Testsuite.FromTestsuite(testsuite) for testsuite in testsuiteSummary._testsuites.values()) + + return doc
+ + +
+[docs] + def Analyze(self) -> None: + """ + Analyze the XML file, parse the content into an XML data structure and validate the data structure using an XML + schema. + + .. hint:: + + The time spend for analysis will be made available via property :data:`AnalysisDuration`. + + The used XML schema definition is generic to support "any" dialect. + """ + xmlSchemaFile = "Any-JUnit.xsd" + self._Analyze(xmlSchemaFile)
+ + + def _Analyze(self, xmlSchemaFile: str) -> None: + if not self._path.exists(): + raise UnittestException(f"JUnit XML file '{self._path}' does not exist.") \ + from FileNotFoundError(f"File '{self._path}' not found.") + + startAnalysis = perf_counter_ns() + try: + xmlSchemaResourceFile = getResourceFile(Resources, xmlSchemaFile) + except ToolingException as ex: + raise UnittestException(f"Couldn't locate XML Schema '{xmlSchemaFile}' in package resources.") from ex + + try: + schemaParser = XMLParser(ns_clean=True) + schemaRoot = parse(xmlSchemaResourceFile, schemaParser) + except XMLSyntaxError as ex: + raise UnittestException(f"XML Syntax Error while parsing XML Schema '{xmlSchemaFile}'.") from ex + + try: + junitSchema = XMLSchema(schemaRoot) + except XMLSchemaParseError as ex: + raise UnittestException(f"Error while parsing XML Schema '{xmlSchemaFile}'.") + + try: + junitParser = XMLParser(schema=junitSchema, ns_clean=True) + junitDocument = parse(self._path, parser=junitParser) + + self._xmlDocument = junitDocument + except XMLSyntaxError as ex: + if version_info >= (3, 11): # pragma: no cover + for logEntry in junitParser.error_log: + ex.add_note(str(logEntry)) + raise UnittestException(f"XML syntax or validation error for '{self._path}' using XSD schema '{xmlSchemaResourceFile}'.") from ex + except Exception as ex: + raise UnittestException(f"Couldn't open '{self._path}'.") from ex + + endAnalysis = perf_counter_ns() + self._analysisDuration = (endAnalysis - startAnalysis) / 1e9 + +
+[docs] + def Write(self, path: Nullable[Path] = None, overwrite: bool = False, regenerate: bool = False) -> None: + """ + Write the data model as XML into a file adhering to the Any JUnit dialect. + + :param path: Optional path to the XMl file, if internal path shouldn't be used. + :param overwrite: If true, overwrite an existing file. + :param regenerate: If true, regenerate the XML structure from data model. + :raises UnittestException: If the file cannot be overwritten. + :raises UnittestException: If the internal XML data structure wasn't generated. + :raises UnittestException: If the file cannot be opened or written. + """ + if path is None: + path = self._path + + if not overwrite and path.exists(): + raise UnittestException(f"JUnit XML file '{path}' can not be overwritten.") \ + from FileExistsError(f"File '{path}' already exists.") + + if regenerate: + self.Generate(overwrite=True) + + if self._xmlDocument is None: + ex = UnittestException(f"Internal XML document tree is empty and needs to be generated before write is possible.") + ex.add_note(f"Call 'JUnitDocument.Generate()' or 'JUnitDocument.Write(..., regenerate=True)'.") + raise ex + + try: + with path.open("wb") as file: + file.write(tostring(self._xmlDocument, encoding="utf-8", xml_declaration=True, pretty_print=True)) + except Exception as ex: + raise UnittestException(f"JUnit XML file '{path}' can not be written.") from ex
+ + +
+[docs] + def Convert(self) -> None: + """ + Convert the parsed and validated XML data structure into a JUnit test entity hierarchy. + + This method converts the root element. + + .. hint:: + + The time spend for model conversion will be made available via property :data:`ModelConversionDuration`. + + :raises UnittestException: If XML was not read and parsed before. + """ + if self._xmlDocument is None: + ex = UnittestException(f"JUnit XML file '{self._path}' needs to be read and analyzed by an XML parser.") + ex.add_note(f"Call 'JUnitDocument.Analyze()' or create the document using 'JUnitDocument(path, parse=True)'.") + raise ex + + startConversion = perf_counter_ns() + rootElement: _Element = self._xmlDocument.getroot() + + self._name = self._ConvertName(rootElement, optional=True) + self._startTime = self._ConvertTimestamp(rootElement, optional=True) + self._duration = self._ConvertTime(rootElement, optional=True) + + if False: # self._readerMode is JUnitReaderMode. + self._tests = self._ConvertTests(testsuitesNode) + self._skipped = self._ConvertSkipped(testsuitesNode) + self._errored = self._ConvertErrors(testsuitesNode) + self._failed = self._ConvertFailures(testsuitesNode) + self._assertionCount = self._ConvertAssertions(testsuitesNode) + + for rootNode in rootElement.iterchildren(tag="testsuite"): # type: _Element + self._ConvertTestsuite(self, rootNode) + + if True: # self._readerMode is JUnitReaderMode. + self.Aggregate() + + endConversation = perf_counter_ns() + self._modelConversion = (endConversation - startConversion) / 1e9
+ + +
+[docs] + def _ConvertName(self, element: _Element, default: str = "root", optional: bool = True) -> str: + """ + Convert the ``name`` attribute from an XML element node to a string. + + :param element: The XML element node with a ``name`` attribute. + :param default: The default value, if no ``name`` attribute was found. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``name`` attribute's content if found, otherwise the given default value. + :raises UnittestException: If optional is false and no ``name`` attribute exists on the given element node. + """ + if "name" in element.attrib: + return element.attrib["name"] + elif not optional: + raise UnittestException(f"Required parameter 'name' not found in tag '{element.tag}'.") + else: + return default
+ + +
+[docs] + def _ConvertTimestamp(self, element: _Element, optional: bool = True) -> Nullable[datetime]: + """ + Convert the ``timestamp`` attribute from an XML element node to a datetime. + + :param element: The XML element node with a ``timestamp`` attribute. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``timestamp`` attribute's content if found, otherwise ``None``. + :raises UnittestException: If optional is false and no ``timestamp`` attribute exists on the given element node. + """ + if "timestamp" in element.attrib: + timestamp = element.attrib["timestamp"] + return datetime.fromisoformat(timestamp) + elif not optional: + raise UnittestException(f"Required parameter 'timestamp' not found in tag '{element.tag}'.") + else: + return None
+ + +
+[docs] + def _ConvertTime(self, element: _Element, optional: bool = True) -> Nullable[timedelta]: + """ + Convert the ``time`` attribute from an XML element node to a timedelta. + + :param element: The XML element node with a ``time`` attribute. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``time`` attribute's content if found, otherwise ``None``. + :raises UnittestException: If optional is false and no ``time`` attribute exists on the given element node. + """ + if "time" in element.attrib: + time = element.attrib["time"] + return timedelta(seconds=float(time)) + elif not optional: + raise UnittestException(f"Required parameter 'time' not found in tag '{element.tag}'.") + else: + return None
+ + +
+[docs] + def _ConvertHostname(self, element: _Element, default: str = "localhost", optional: bool = True) -> str: + """ + Convert the ``hostname`` attribute from an XML element node to a string. + + :param element: The XML element node with a ``hostname`` attribute. + :param default: The default value, if no ``hostname`` attribute was found. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``hostname`` attribute's content if found, otherwise the given default value. + :raises UnittestException: If optional is false and no ``hostname`` attribute exists on the given element node. + """ + if "hostname" in element.attrib: + return element.attrib["hostname"] + elif not optional: + raise UnittestException(f"Required parameter 'hostname' not found in tag '{element.tag}'.") + else: + return default
+ + +
+[docs] + def _ConvertClassname(self, element: _Element) -> str: + """ + Convert the ``classname`` attribute from an XML element node to a string. + + :param element: The XML element node with a ``classname`` attribute. + :return: The ``classname`` attribute's content. + :raises UnittestException: If no ``classname`` attribute exists on the given element node. + """ + if "classname" in element.attrib: + return element.attrib["classname"] + else: + raise UnittestException(f"Required parameter 'classname' not found in tag '{element.tag}'.")
+ + +
+[docs] + def _ConvertTests(self, element: _Element, default: Nullable[int] = None, optional: bool = True) -> Nullable[int]: + """ + Convert the ``tests`` attribute from an XML element node to an integer. + + :param element: The XML element node with a ``tests`` attribute. + :param default: The default value, if no ``tests`` attribute was found. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``tests`` attribute's content if found, otherwise the given default value. + :raises UnittestException: If optional is false and no ``tests`` attribute exists on the given element node. + """ + if "tests" in element.attrib: + return int(element.attrib["tests"]) + elif not optional: + raise UnittestException(f"Required parameter 'tests' not found in tag '{element.tag}'.") + else: + return default
+ + +
+[docs] + def _ConvertSkipped(self, element: _Element, default: Nullable[int] = None, optional: bool = True) -> Nullable[int]: + """ + Convert the ``skipped`` attribute from an XML element node to an integer. + + :param element: The XML element node with a ``skipped`` attribute. + :param default: The default value, if no ``skipped`` attribute was found. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``skipped`` attribute's content if found, otherwise the given default value. + :raises UnittestException: If optional is false and no ``skipped`` attribute exists on the given element node. + """ + if "skipped" in element.attrib: + return int(element.attrib["skipped"]) + elif not optional: + raise UnittestException(f"Required parameter 'skipped' not found in tag '{element.tag}'.") + else: + return default
+ + +
+[docs] + def _ConvertErrors(self, element: _Element, default: Nullable[int] = None, optional: bool = True) -> Nullable[int]: + """ + Convert the ``errors`` attribute from an XML element node to an integer. + + :param element: The XML element node with a ``errors`` attribute. + :param default: The default value, if no ``errors`` attribute was found. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``errors`` attribute's content if found, otherwise the given default value. + :raises UnittestException: If optional is false and no ``errors`` attribute exists on the given element node. + """ + if "errors" in element.attrib: + return int(element.attrib["errors"]) + elif not optional: + raise UnittestException(f"Required parameter 'errors' not found in tag '{element.tag}'.") + else: + return default
+ + +
+[docs] + def _ConvertFailures(self, element: _Element, default: Nullable[int] = None, optional: bool = True) -> Nullable[int]: + """ + Convert the ``failures`` attribute from an XML element node to an integer. + + :param element: The XML element node with a ``failures`` attribute. + :param default: The default value, if no ``failures`` attribute was found. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``failures`` attribute's content if found, otherwise the given default value. + :raises UnittestException: If optional is false and no ``failures`` attribute exists on the given element node. + """ + if "failures" in element.attrib: + return int(element.attrib["failures"]) + elif not optional: + raise UnittestException(f"Required parameter 'failures' not found in tag '{element.tag}'.") + else: + return default
+ + +
+[docs] + def _ConvertAssertions(self, element: _Element, default: Nullable[int] = None, optional: bool = True) -> Nullable[int]: + """ + Convert the ``assertions`` attribute from an XML element node to an integer. + + :param element: The XML element node with a ``assertions`` attribute. + :param default: The default value, if no ``assertions`` attribute was found. + :param optional: If false, an exception is raised for the missing attribute. + :return: The ``assertions`` attribute's content if found, otherwise the given default value. + :raises UnittestException: If optional is false and no ``assertions`` attribute exists on the given element node. + """ + if "assertions" in element.attrib: + return int(element.attrib["assertions"]) + elif not optional: + raise UnittestException(f"Required parameter 'assertions' not found in tag '{element.tag}'.") + else: + return default
+ + +
+[docs] + def _ConvertTestsuite(self, parent: TestsuiteSummary, testsuitesNode: _Element) -> None: + """ + Convert the XML data structure of a ``<testsuite>`` to a test suite. + + This method uses private helper methods provided by the base-class. + + :param parent: The test suite summary as a parent element in the test entity hierarchy. + :param testsuitesNode: The current XML element node representing a test suite. + """ + newTestsuite = self._TESTSUITE( + self._ConvertName(testsuitesNode, optional=False), + self._ConvertHostname(testsuitesNode, optional=True), + self._ConvertTimestamp(testsuitesNode, optional=True), + self._ConvertTime(testsuitesNode, optional=True), + parent=parent + ) + + if False: # self._readerMode is JUnitReaderMode. + self._tests = self._ConvertTests(testsuitesNode) + self._skipped = self._ConvertSkipped(testsuitesNode) + self._errored = self._ConvertErrors(testsuitesNode) + self._failed = self._ConvertFailures(testsuitesNode) + self._assertionCount = self._ConvertAssertions(testsuitesNode) + + self._ConvertTestsuiteChildren(testsuitesNode, newTestsuite)
+ + + def _ConvertTestsuiteChildren(self, testsuitesNode: _Element, newTestsuite: Testsuite) -> None: + for node in testsuitesNode.iterchildren(): # type: _Element + # if node.tag == "testsuite": + # self._ConvertTestsuite(newTestsuite, node) + # el + if node.tag == "testcase": + self._ConvertTestcase(newTestsuite, node) + +
+[docs] + def _ConvertTestcase(self, parent: Testsuite, testcaseNode: _Element) -> None: + """ + Convert the XML data structure of a ``<testcase>`` to a test case. + + This method uses private helper methods provided by the base-class. + + :param parent: The test suite as a parent element in the test entity hierarchy. + :param testcaseNode: The current XML element node representing a test case. + """ + className = self._ConvertClassname(testcaseNode) + testclass = self._FindOrCreateTestclass(parent, className) + + newTestcase = self._TESTCASE( + self._ConvertName(testcaseNode, optional=False), + self._ConvertTime(testcaseNode, optional=False), + assertionCount=self._ConvertAssertions(testcaseNode), + parent=testclass + ) + + self._ConvertTestcaseChildren(testcaseNode, newTestcase)
+ + + def _FindOrCreateTestclass(self, parent: Testsuite, className: str) -> Testclass: + if className in parent._testclasses: + return parent._testclasses[className] + else: + return self._TESTCLASS(className, parent=parent) + + def _ConvertTestcaseChildren(self, testcaseNode: _Element, newTestcase: Testcase) -> None: + for node in testcaseNode.iterchildren(): # type: _Element + if isinstance(node, _Comment): + pass + elif isinstance(node, _Element): + if node.tag == "skipped": + newTestcase._status = TestcaseStatus.Skipped + elif node.tag == "failure": + newTestcase._status = TestcaseStatus.Failed + elif node.tag == "error": + newTestcase._status = TestcaseStatus.Errored + elif node.tag == "system-out": + pass + elif node.tag == "system-err": + pass + elif node.tag == "properties": + pass + else: + raise UnittestException(f"Unknown element '{node.tag}' in junit file.") + else: + pass + + if newTestcase._status is TestcaseStatus.Unknown: + newTestcase._status = TestcaseStatus.Passed + +
+[docs] + def Generate(self, overwrite: bool = False) -> None: + """ + Generate the internal XML data structure from test suites and test cases. + + This method generates the XML root element (``<testsuites>``) and recursively calls other generated methods. + + :param overwrite: Overwrite the internal XML data structure. + :raises UnittestException: If overwrite is false and the internal XML data structure is not empty. + """ + if not overwrite and self._xmlDocument is not None: + raise UnittestException(f"Internal XML document is populated with data.") + + rootElement = Element("testsuites") + rootElement.attrib["name"] = self._name + if self._startTime is not None: + rootElement.attrib["timestamp"] = f"{self._startTime.isoformat()}" + if self._duration is not None: + rootElement.attrib["time"] = f"{self._duration.total_seconds():.6f}" + rootElement.attrib["tests"] = str(self._tests) + rootElement.attrib["failures"] = str(self._failed) + rootElement.attrib["errors"] = str(self._errored) + rootElement.attrib["skipped"] = str(self._skipped) + # if self._assertionCount is not None: + # rootElement.attrib["assertions"] = f"{self._assertionCount}" + + self._xmlDocument = ElementTree(rootElement) + + for testsuite in self._testsuites.values(): + self._GenerateTestsuite(testsuite, rootElement)
+ + +
+[docs] + def _GenerateTestsuite(self, testsuite: Testsuite, parentElement: _Element) -> None: + """ + Generate the internal XML data structure for a test suite. + + This method generates the XML element (``<testsuite>``) and recursively calls other generated methods. + + :param testsuite: The test suite to convert to an XML data structures. + :param parentElement: The parent XML data structure element, this data structure part will be added to. + :return: + """ + testsuiteElement = SubElement(parentElement, "testsuite") + testsuiteElement.attrib["name"] = testsuite._name + if testsuite._startTime is not None: + testsuiteElement.attrib["timestamp"] = f"{testsuite._startTime.isoformat()}" + if testsuite._duration is not None: + testsuiteElement.attrib["time"] = f"{testsuite._duration.total_seconds():.6f}" + testsuiteElement.attrib["tests"] = str(testsuite._tests) + testsuiteElement.attrib["failures"] = str(testsuite._failed) + testsuiteElement.attrib["errors"] = str(testsuite._errored) + testsuiteElement.attrib["skipped"] = str(testsuite._skipped) + # if testsuite._assertionCount is not None: + # testsuiteElement.attrib["assertions"] = f"{testsuite._assertionCount}" + if testsuite._hostname is not None: + testsuiteElement.attrib["hostname"] = testsuite._hostname + + for testclass in testsuite._testclasses.values(): + for tc in testclass._testcases.values(): + self._GenerateTestcase(tc, testsuiteElement)
+ + +
+[docs] + def _GenerateTestcase(self, testcase: Testcase, parentElement: _Element) -> None: + """ + Generate the internal XML data structure for a test case. + + This method generates the XML element (``<testcase>``) and recursively calls other generated methods. + + :param testcase: The test case to convert to an XML data structures. + :param parentElement: The parent XML data structure element, this data structure part will be added to. + :return: + """ + testcaseElement = SubElement(parentElement, "testcase") + if testcase.Classname is not None: + testcaseElement.attrib["classname"] = testcase.Classname + testcaseElement.attrib["name"] = testcase._name + if testcase._duration is not None: + testcaseElement.attrib["time"] = f"{testcase._duration.total_seconds():.6f}" + if testcase._assertionCount is not None: + testcaseElement.attrib["assertions"] = f"{testcase._assertionCount}" + + if testcase._status is TestcaseStatus.Passed: + pass + elif testcase._status is TestcaseStatus.Failed: + failureElement = SubElement(testcaseElement, "failure") + elif testcase._status is TestcaseStatus.Skipped: + skippedElement = SubElement(testcaseElement, "skipped") + else: + errorElement = SubElement(testcaseElement, "error")
+ + +
+[docs] + def __str__(self) -> str: + moduleName = self.__module__.split(".")[-1] + className = self.__class__.__name__ + return ( + f"<{moduleName}{className} {self._name} ({self._path}): {self._status.name} - suites/tests:{self.TestsuiteCount}/{self.TestcaseCount}>" + )
+
+ +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/_report_static/sphinx-reports.e44b1595f1e09d5e109d001839ba3f42.css b/_report_static/sphinx-reports.e44b1595f1e09d5e109d001839ba3f42.css new file mode 100644 index 00000000..02468d35 --- /dev/null +++ b/_report_static/sphinx-reports.e44b1595f1e09d5e109d001839ba3f42.css @@ -0,0 +1,48 @@ +/* + * Disable odd/even coloring for docutils tables if it's a coverage table. + * Otherwise, the 'nth-child' rule will always override row colors indicating the coverage level. + */ +.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td { + background-color: unset; +} + +/* + * Coloring for 0..30, 30..50, 50..80, 80..90, 90.100% coverage + */ +/* very good */ +.report-cov-below100 { + background: rgba(0, 200, 82, .4); +} +/* good */ +.report-cov-below90 { + background: rgba(0, 200, 82, .2); +} +/* modest */ +.report-cov-below80 { + background: rgba(255, 145, 0, .2); +} +/* bad */ +.report-cov-below50 { + background: rgba(255, 82, 82, .2); +} +/* very bad */ +.report-cov-below30 { + background: rgba(101, 31, 255, .2); +} +/* internal error */ +.report-cov-error{ + background: rgba(255, 0, 0, .4); +} + +.report-dep-summary-row { + font-weight: bold; +} +.report-codecov-summary-row { + font-weight: bold; +} +.report-doccov-summary-row { + font-weight: bold; +} +.report-unittest-summary-row { + font-weight: bold; +} diff --git a/_sources/CodeCoverage/index.rst.txt b/_sources/CodeCoverage/index.rst.txt new file mode 100644 index 00000000..862ecfee --- /dev/null +++ b/_sources/CodeCoverage/index.rst.txt @@ -0,0 +1,29 @@ +.. _CODECOV: + +Code Coverage +############# + +Code coverage measures used and unused code lines, statements, branches, etc. Depending on the programming language this +is measured by instrumenting the code/binary and running the program, it's test cases or simulating the code. In +generate code coverage is a measure of test coverage. Unused code is not (yet) covered by tests. + +The code coverage metric in percent is a ratio of used code versus all possibly usable code. A coverage of <100% +indicates unused code. This can be dead code (unreachable) or untested code (⇒ needs more test cases). + + +.. rubric:: Code Coverage Kinds + +* Coverage + + * Code coverage + + * statement coverage / line coverage + * branch coverage + * expression coverage + * toggle coverage + * state coverage (of state machines) + * transition coverage (of state machines) + + * Functional coverage + * Documentation coverage + * Test coverage diff --git a/_sources/CommandLineInterface.rst.txt b/_sources/CommandLineInterface.rst.txt new file mode 100644 index 00000000..6b5a243b --- /dev/null +++ b/_sources/CommandLineInterface.rst.txt @@ -0,0 +1,17 @@ +Command Line Interfaces +####################### + +When installed via PIP, the command line program ``pyedaa-reports`` is registered in the Python installation's +``Scripts`` directory. Usually this path is listed in ``PATH``, thus this program is globally available after +installation. + +The program is self-describing. Use ``pyedaa-reports`` without parameters or ``pyedaa-reports help`` to see all +available common options and commands. Each command has then it's own help page for command specific options, which can +be listed by calling ``pyedaa-reports -h`` or ``pyedaa-reports help ``. The ``pyedaa-reports``'s version and +license information is shown by calling ``pyedaa-reports version``. + +.. _References:cli: + +.. autoprogram:: pyEDAA.Reports.CLI:Application().MainParser + :prog: pyedaa-reports + :groups: diff --git a/_sources/Dependency.rst.txt b/_sources/Dependency.rst.txt new file mode 100644 index 00000000..4f2f7014 --- /dev/null +++ b/_sources/Dependency.rst.txt @@ -0,0 +1,170 @@ +.. _dependency: + +Dependency +########## + +.. |img-Reports-lib-status| image:: https://img.shields.io/librariesio/release/pypi/pyEDAA.Reports + :alt: Libraries.io status for latest release + :height: 22 + :target: https://libraries.io/github/edaa-org/pyEDAA.Reports +.. |img-Reports-req-status| image:: https://img.shields.io/requires/github/pyEDAA/pyEDAA.Reports + :alt: Requires.io + :height: 22 + :target: https://requires.io/github/edaa-org/pyEDAA.Reports/requirements/?branch=main + ++------------------------------------------+------------------------------------------+ +| `Libraries.io `_ | `Requires.io `_ | ++==========================================+==========================================+ +| |img-Reports-lib-status| | |img-Reports-req-status| | ++------------------------------------------+------------------------------------------+ + + +.. _dependency-package: + +pyEDAA.Reports Package +********************** + ++-----------------------------------------------------------------+-------------+-------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Package** | **Version** | **License** | **Dependencies** | ++=================================================================+=============+===========================================================================================+========================================================================================================================================================+ +| `pyTooling `__ | ≥7.0 | `Apache License, 2.0 `__ | *None* | ++-----------------------------------------------------------------+-------------+-------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| `ruamel.yaml `__ | ≥0.18 | `MIT `__ | *Not yet evaluated.* | ++-----------------------------------------------------------------+-------------+-------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ +| `lxml `__ | ≥5.3 | `BSD 3-Clause `__ | *Not yet evaluated.* | ++-----------------------------------------------------------------+-------------+-------------------------------------------------------------------------------------------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +.. _dependency-testing: + +Unit Testing / Coverage / Type Checking (Optional) +************************************************** + +Additional Python packages needed for testing, code coverage collection and static type checking. These packages are +only needed for developers or on a CI server, thus sub-dependencies are not evaluated further. + + +.. rubric:: Manually Installing Test Requirements + +Use the :file:`tests/requirements.txt` file to install all dependencies via ``pip3``. The file will recursively install +the mandatory dependencies too. + +.. code-block:: shell + + pip3 install -U -r tests/requirements.txt + + +.. rubric:: Dependency List + ++---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ +| **Package** | **Version** | **License** | **Dependencies** | ++=====================================================================+=============+========================================================================================+======================+ +| `pytest `__ | ≥8.3 | `MIT `__ | *Not yet evaluated.* | ++---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ +| `pytest-cov `__ | ≥6.0 | `MIT `__ | *Not yet evaluated.* | ++---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ +| `Coverage `__ | ≥7.6 | `Apache License, 2.0 `__ | *Not yet evaluated.* | ++---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ +| `mypy `__ | ≥1.13 | `MIT `__ | *Not yet evaluated.* | ++---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ +| `typing-extensions `__ | ≥4.12 | `PSF-2.0 `__ | *Not yet evaluated.* | ++---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ +| `lxml `__ | ≥5.3 | `BSD 3-Clause `__ | *Not yet evaluated.* | ++---------------------------------------------------------------------+-------------+----------------------------------------------------------------------------------------+----------------------+ + + +.. _dependency-documentation: + +Sphinx Documentation (Optional) +******************************* + +Additional Python packages needed for documentation generation. These packages are only needed for developers or on a +CI server, thus sub-dependencies are not evaluated further. + + +.. rubric:: Manually Installing Documentation Requirements + +Use the :file:`doc/requirements.txt` file to install all dependencies via ``pip3``. The file will recursively install +the mandatory dependencies too. + +.. code-block:: shell + + pip3 install -U -r doc/requirements.txt + + +.. rubric:: Dependency List + ++-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Package** | **Version** | **License** | **Dependencies** | ++=================================================================================================+==============+==========================================================================================================+======================================================================================================================================================+ +| `pyTooling `__ | ≥7.0 | `Apache License, 2.0 `__ | *None* | ++-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ +| `Sphinx `__ | ≥8.1 | `BSD 3-Clause `__ | *Not yet evaluated.* | ++-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ +| `sphinx_btd_theme `__ | ≥0.5.2 | `MIT `__ | *Not yet evaluated.* | ++-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ +| !! `sphinx_fontawesome `__ | ≥0.0.6 | `GPL 2.0 `__ | *Not yet evaluated.* | ++-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ +| `sphinx_autodoc_typehints `__ | ≥2.5 | `MIT `__ | *Not yet evaluated.* | ++-------------------------------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +.. _dependency-packaging: + +Packaging (Optional) +******************** + +Additional Python packages needed for installation package generation. These packages are only needed for developers or +on a CI server, thus sub-dependencies are not evaluated further. + + +.. rubric:: Manually Installing Packaging Requirements + +Use the :file:`build/requirements.txt` file to install all dependencies via ``pip3``. The file will recursively +install the mandatory dependencies too. + +.. code-block:: shell + + pip3 install -U -r build/requirements.txt + + +.. rubric:: Dependency List + ++----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ +| **Package** | **Version** | **License** | **Dependencies** | ++============================================================================+==============+==========================================================================================================+======================================================================================================================================================+ +| `pyTooling `__ | ≥7.0 | `Apache License, 2.0 `__ | *None* | ++----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ +| `wheel `__ | ≥0.44 | `MIT `__ | *Not yet evaluated.* | ++----------------------------------------------------------------------------+--------------+----------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------+ + + +.. _dependency-publishing: + +Publishing (CI-Server only) +*************************** + +Additional Python packages needed for publishing the generated installation package to e.g, PyPI or any equivalent +services. These packages are only needed for maintainers or on a CI server, thus sub-dependencies are not evaluated +further. + + +.. rubric:: Manually Installing Publishing Requirements + +Use the :file:`dist/requirements.txt` file to install all dependencies via ``pip3``. The file will recursively +install the mandatory dependencies too. + +.. code-block:: shell + + pip3 install -U -r dist/requirements.txt + + +.. rubric:: Dependency List + ++----------------------------------------------------------+--------------+-------------------------------------------------------------------------------------------+----------------------+ +| **Package** | **Version** | **License** | **Dependencies** | ++==========================================================+==============+===========================================================================================+======================+ +| `wheel `__ | ≥0.44 | `MIT `__ | *Not yet evaluated.* | ++----------------------------------------------------------+--------------+-------------------------------------------------------------------------------------------+----------------------+ +| `Twine `__ | ≥5.1 | `Apache License, 2.0 `__ | *Not yet evaluated.* | ++----------------------------------------------------------+--------------+-------------------------------------------------------------------------------------------+----------------------+ diff --git a/_sources/Doc-License.rst.txt b/_sources/Doc-License.rst.txt new file mode 100644 index 00000000..1258fbc2 --- /dev/null +++ b/_sources/Doc-License.rst.txt @@ -0,0 +1,353 @@ +.. _DOCLICENSE: + +.. Note:: This is a local copy of the `Creative Commons - Attribution 4.0 International (CC BY 4.0) `__. + +.. Attention:: This **CC BY 4.0** license applies only to the **documentation** of this project. + + +Creative Commons Attribution 4.0 International +############################################## + +Creative Commons Corporation (“Creative Commons”) is not a law firm and does not +provide legal services or legal advice. Distribution of Creative Commons public +licenses does not create a lawyer-client or other relationship. Creative Commons +makes its licenses and related information available on an “as-is” basis. +Creative Commons gives no warranties regarding its licenses, any material +licensed under their terms and conditions, or any related information. Creative +Commons disclaims all liability for damages resulting from their use to the +fullest extent possible. + +.. topic:: Using Creative Commons Public Licenses + + Creative Commons public licenses provide a standard set of terms and conditions + that creators and other rights holders may use to share original works of + authorship and other material subject to copyright and certain other rights + specified in the public license below. The following considerations are for + informational purposes only, are not exhaustive, and do not form part of our + licenses. + + * **Considerations for licensors:** Our public licenses are intended for use + by those authorized to give the public permission to use material in ways + otherwise restricted by copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms and conditions + of the license they choose before applying it. Licensors should also secure + all rights necessary before applying our licenses so that the public can reuse + the material as expected. Licensors should clearly mark any material not + subject to the license. This includes other CC-licensed material, or material + used under an exception or limitation to copyright. + `More considerations for licensors `__. + + * **Considerations for the public:** By using one of our public licenses, a + licensor grants the public permission to use the licensed material under + specified terms and conditions. If the licensor’s permission is not necessary + for any reason–for example, because of any applicable exception or limitation + to copyright–then that use is not regulated by the license. Our licenses grant + only permissions under copyright and certain other rights that a licensor has + authority to grant. Use of the licensed material may still be restricted for + other reasons, including because others have copyright or other rights in the + material. A licensor may make special requests, such as asking that all + changes be marked or described. Although not required by our licenses, you are + encouraged to respect those requests where reasonable. + `More considerations for the public `__. + +:xlarge:`Creative Commons Attribution 4.0 International Public License` + +By exercising the Licensed Rights (defined below), You accept and agree to be +bound by the terms and conditions of this Creative Commons Attribution 4.0 +International Public License ("Public License"). To the extent this Public +License may be interpreted as a contract, You are granted the Licensed Rights +in consideration of Your acceptance of these terms and conditions, and the +Licensor grants You such rights in consideration of benefits the Licensor +receives from making the Licensed Material available under these terms and +conditions. + +Section 1 – Definitions. +======================== + +a. **Adapted Material** means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material and in + which the Licensed Material is translated, altered, arranged, transformed, or + otherwise modified in a manner requiring permission under the Copyright and + Similar Rights held by the Licensor. For purposes of this Public License, + where the Licensed Material is a musical work, performance, or sound + recording, Adapted Material is always produced where the Licensed Material + is synched in timed relation with a moving image. + +b. **Adapter's License** means the license You apply to Your Copyright and + Similar Rights in Your contributions to Adapted Material in accordance with + the terms and conditions of this Public License. + +c. **Copyright and Similar Rights** means copyright and/or similar rights + closely related to copyright including, without limitation, performance, + broadcast, sound recording, and Sui Generis Database Rights, without regard + to how the rights are labeled or categorized. For purposes of this Public + License, the rights specified in Section 2(b)(1)-(2) are not Copyright and + Similar Rights. + +d. **Effective Technological Measures** means those measures that, in the + absence of proper authority, may not be circumvented under laws fulfilling + obligations under Article 11 of the WIPO Copyright Treaty adopted on + December 20, 1996, and/or similar international agreements. + +e. **Exceptions and Limitations** means fair use, fair dealing, and/or any + other exception or limitation to Copyright and Similar Rights that applies to + Your use of the Licensed Material. + +f. **Licensed Material** means the artistic or literary work, database, or + other material to which the Licensor applied this Public License. + +g. **Licensed Rights** means the rights granted to You subject to the terms + and conditions of this Public License, which are limited to all Copyright and + Similar Rights that apply to Your use of the Licensed Material and that the + Licensor has authority to license. + +h. **Licensor** means the individual(s) or entity(ies) granting rights under + this Public License. + +i. **Share** means to provide material to the public by any means or process + that requires permission under the Licensed Rights, such as reproduction, + public display, public performance, distribution, dissemination, + communication, or importation, and to make material available to the public + including in ways that members of the public may access the material from a + place and at a time individually chosen by them. + +j. **Sui Generis Database Rights** means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of the + Council of 11 March 1996 on the legal protection of databases, as amended + and/or succeeded, as well as other essentially equivalent rights anywhere + in the world. + +k. **You** means the individual or entity exercising the Licensed Rights + under this Public License. **Your** has a corresponding meaning. + +Section 2 – Scope. +================== + +a. **License grant.** + + 1. Subject to the terms and conditions of this Public License, the Licensor + hereby grants You a worldwide, royalty-free, non-sublicensable, + non-exclusive, irrevocable license to exercise the Licensed Rights in the + Licensed Material to: + + A. reproduce and Share the Licensed Material, in whole or in part; and + + B. produce, reproduce, and Share Adapted Material. + + 2. :underline:`Exceptions and Limitations.` For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public License does not + apply, and You do not need to comply with its terms and conditions. + + 3. :underline:`Term.` The term of this Public License is specified in Section 6(a). + + 4. :underline:`Media and formats`; :underline:`technical modifications allowed.` The Licensor + authorizes You to exercise the Licensed Rights in all media and formats + whether now known or hereafter created, and to make technical + modifications necessary to do so. The Licensor waives and/or agrees not to + assert any right or authority to forbid You from making technical + modifications necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective Technological + Measures. For purposes of this Public License, simply making modifications + authorized by this Section 2(a)(4) never produces Adapted Material. + + 5. :underline:`Downstream recipients.` + + A. :underline:`Offer from the Licensor – Licensed Material.` Every recipient of + the Licensed Material automatically receives an offer from the + Licensor to exercise the Licensed Rights under the terms and + conditions of this Public License. + + B. :underline:`No downstream restrictions.` You may not offer or impose any + additional or different terms or conditions on, or apply any Effective + Technological Measures to, the Licensed Material if doing so restricts + exercise of the Licensed Rights by any recipient of the Licensed + Material. + + 6. :underline:`No endorsement.` Nothing in this Public License constitutes or may + be construed as permission to assert or imply that You are, or that Your + use of the Licensed Material is, connected with, or sponsored, endorsed, + or granted official status by, the Licensor or others designated to + receive attribution as provided in Section 3(a)(1)(A)(i). + +b. **Other rights.** + + 1. Moral rights, such as the right of integrity, are not licensed under this + Public License, nor are publicity, privacy, and/or other similar + personality rights; however, to the extent possible, the Licensor waives + and/or agrees not to assert any such rights held by the Licensor to the + limited extent necessary to allow You to exercise the Licensed Rights, but + not otherwise. + + 2. Patent and trademark rights are not licensed under this Public License. + + 3. To the extent possible, the Licensor waives any right to collect royalties + from You for the exercise of the Licensed Rights, whether directly or + through a collecting society under any voluntary or waivable statutory or + compulsory licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + +Section 3 – License Conditions. +=============================== + +Your exercise of the Licensed Rights is expressly made subject to the following conditions. + +a. **Attribution.** + + 1. If You Share the Licensed Material (including in modified form), You must: + + A. retain the following if it is supplied by the Licensor with the + Licensed Material: + + i. identification of the creator(s) of the Licensed Material and any + others designated to receive attribution, in any reasonable manner + requested by the Licensor (including by pseudonym if designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of warranties; + + v. a URI or hyperlink to the Licensed Material to the extent reasonably + practicable; + + B. indicate if You modified the Licensed Material and retain an + indication of any previous modifications; and + + C. indicate the Licensed Material is licensed under this Public License, + and include the text of, or the URI or hyperlink to, this Public + License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any reasonable manner + based on the medium, means, and context in which You Share the Licensed + Material. For example, it may be reasonable to satisfy the conditions by + providing a URI or hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the information + required by Section 3(a)(1)(A) to the extent reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's License You apply + must not prevent recipients of the Adapted Material from complying with + this Public License. + +Section 4 – Sui Generis Database Rights. +======================================== + +Where the Licensed Rights include Sui Generis Database Rights that apply to Your +use of the Licensed Material: + +a. for the avoidance of doubt, Section 2(a)(1) grants You the right to extract, + reuse, reproduce, and Share all or a substantial portion of the contents of + the database; + +b. if You include all or a substantial portion of the database contents in a + database in which You have Sui Generis Database Rights, then the database + in which You have Sui Generis Database Rights (but not its individual + contents) is Adapted Material; and + +c. You must comply with the conditions in Section 3(a) if You Share all or a + substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not replace +Your obligations under this Public License where the Licensed Rights include +other Copyright and Similar Rights. + +Section 5 – Disclaimer of Warranties and Limitation of Liability. +================================================================= + +a. **Unless otherwise separately undertaken by the Licensor, to the extent + possible, the Licensor offers the Licensed Material as-is and as-available, + and makes no representations or warranties of any kind concerning the + Licensed Material, whether express, implied, statutory, or other. This + includes, without limitation, warranties of title, merchantability, + fitness for a particular purpose, non-infringement, absence of latent or + other defects, accuracy, or the presence or absence of errors, whether or + not known or discoverable. Where disclaimers of warranties are not allowed + in full or in part, this disclaimer may not apply to You.** + +b. **To the extent possible, in no event will the Licensor be liable to You + on any legal theory (including, without limitation, negligence) or + otherwise for any direct, special, indirect, incidental, consequential, + punitive, exemplary, or other losses, costs, expenses, or damages arising + out of this Public License or use of the Licensed Material, even if the + Licensor has been advised of the possibility of such losses, costs, expenses, + or damages. Where a limitation of liability is not allowed in full or in + part, this limitation may not apply to You.** + +c. The disclaimer of warranties and limitation of liability provided above + shall be interpreted in a manner that, to the extent possible, most + closely approximates an absolute disclaimer and waiver of all liability. + +Section 6 – Term and Termination. +================================= + +a. This Public License applies for the term of the Copyright and Similar Rights + licensed here. However, if You fail to comply with this Public License, then + Your rights under this Public License terminate automatically. + +b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided it is cured + within 30 days of Your discovery of the violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any right the + Licensor may have to seek remedies for Your violations of this Public License. + +c. For the avoidance of doubt, the Licensor may also offer the Licensed Material + under separate terms or conditions or stop distributing the Licensed Material + at any time; however, doing so will not terminate this Public License. + +d. Sections 1, 5, 6, 7, and 8 survive termination of this Public License. + +Section 7 – Other Terms and Conditions. +======================================= + +a. The Licensor shall not be bound by any additional or different terms or + conditions communicated by You unless expressly agreed. + +b. Any arrangements, understandings, or agreements regarding the Licensed + Material not stated herein are separate from and independent of the terms + and conditions of this Public License. + +Section 8 – Interpretation. +=========================== + +a. For the avoidance of doubt, this Public License does not, and shall not be + interpreted to, reduce, limit, restrict, or impose conditions on any use of + the Licensed Material that could lawfully be made without permission under + this Public License. + +b. To the extent possible, if any provision of this Public License is deemed + unenforceable, it shall be automatically reformed to the minimum extent + necessary to make it enforceable. If the provision cannot be reformed, it + shall be severed from this Public License without affecting the + enforceability of the remaining terms and conditions. + +c. No term or condition of this Public License will be waived and no failure to + comply consented to unless expressly agreed to by the Licensor. + +d. Nothing in this Public License constitutes or may be interpreted as a + limitation upon, or waiver of, any privileges and immunities that apply to + the Licensor or You, including from the legal processes of any jurisdiction + or authority. + +------------------ + +Creative Commons is not a party to its public licenses. Notwithstanding, +Creative Commons may elect to apply one of its public licenses to material it +publishes and in those instances will be considered the “Licensor.” Except for +the limited purpose of indicating that material is shared under a Creative +Commons public license or as otherwise permitted by the Creative Commons +policies published at `creativecommons.org/policies `__, +Creative Commons does not authorize the use of the trademark “Creative Commons” +or any other trademark or logo of Creative Commons without its prior written +consent including, without limitation, in connection with any unauthorized +modifications to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For the +avoidance of doubt, this paragraph does not form part of the public licenses. + +Creative Commons may be contacted at `creativecommons.org `__ diff --git a/_sources/DocCoverage/index.rst.txt b/_sources/DocCoverage/index.rst.txt new file mode 100644 index 00000000..bb339ae1 --- /dev/null +++ b/_sources/DocCoverage/index.rst.txt @@ -0,0 +1,12 @@ +.. _DOCCOV: + +Documentation Coverage +###################### + +Documentation coverage measures the presence of code documentation. It primarily counts for public language entities +like publicly visible constants and variables, parameters, types, functions, methods, classes, modules, packages, etc. +The documentation goal depends on the used coverage collection tool's settings. E.g. usually, private language entities +are not required to be documented. + +The documentation coverage metric in percent is a ratio of documented language entity versus all documentation worthy +langauge entities. A coverage of <100% indicates undocumented code. diff --git a/_sources/Glossary.rst.txt b/_sources/Glossary.rst.txt new file mode 100644 index 00000000..01428295 --- /dev/null +++ b/_sources/Glossary.rst.txt @@ -0,0 +1,38 @@ +Glossary +######## + +.. glossary:: + + Coverage + code, functional, documentation, test + + Code Coverage + tbd + + Documentation Coverage + tbd + + Functional Coverage + tbd + + JUnit + :dfn:`Java Unit Testing` framework. + + Test Coverage + tbd + + Test Entity + A :dfn:`Test Entity` is an entity in the test entity hierarchy. It can be a :dfn:`Test Case`, :dfn:`Test Suite` or + :dfn:`Test Suite Summary`. Some hierarchies (like JUnit) got extended by a :dfn:`Test Class`. + + Test Case + tbd + + Test Class + tbd + + Test Suite + tbd + + Test Suite Summary + tbd diff --git a/_sources/Installation.rst.txt b/_sources/Installation.rst.txt new file mode 100644 index 00000000..82ddc80a --- /dev/null +++ b/_sources/Installation.rst.txt @@ -0,0 +1,206 @@ +.. _INSTALL: + +Installation/Updates +#################### + +.. _INSTALL/pip: + +Using PIP to Install from PyPI +****************************** + +The following instruction are using PIP (Package Installer for Python) as a package manager and PyPI (Python Package +Index) as a source of Python packages. + + +.. _INSTALL/pip/install: + +Installing a Wheel Package from PyPI using PIP +============================================== + +.. tab-set:: + + .. tab-item:: Linux/macOS + :sync: Linux + + .. code-block:: bash + + # Basic pyTooling package + pip3 install pyEDAA.Reports + + .. tab-item:: Windows + :sync: Windows + + .. code-block:: powershell + + # Basic pyTooling package + pip install pyEDAA.Reports + +Developers can install further dependencies for documentation generation (``doc``) or running unit tests (``test``) or +just all (``all``) dependencies. + +.. tab-set:: + + .. tab-item:: Linux/macOS + :sync: Linux + + .. tab-set:: + + .. tab-item:: With Documentation Dependencies + :sync: Doc + + .. code-block:: bash + + # Install with dependencies to generate documentation + pip3 install pyEDAA.Reports[doc] + + .. tab-item:: With Unit Testing Dependencies + :sync: Unit + + .. code-block:: bash + + # Install with dependencies to run unit tests + pip3 install pyEDAA.Reports[test] + + .. tab-item:: All Developer Dependencies + :sync: All + + .. code-block:: bash + + # Install with all developer dependencies + pip install pyEDAA.Reports[all] + + .. tab-item:: Windows + :sync: Windows + + .. tab-set:: + + .. tab-item:: With Documentation Dependencies + :sync: Doc + + .. code-block:: powershell + + # Install with dependencies to generate documentation + pip install pyEDAA.Reports[doc] + + .. tab-item:: With Unit Testing Dependencies + :sync: Unit + + .. code-block:: powershell + + # Install with dependencies to run unit tests + pip install pyEDAA.Reports[test] + + .. tab-item:: All Developer Dependencies + :sync: All + + .. code-block:: powershell + + # Install with all developer dependencies + pip install pyEDAA.Reports[all] + + +.. _INSTALL/pip/update: + +Updating from PyPI using PIP +============================ + +.. tab-set:: + + .. tab-item:: Linux/macOS + :sync: Linux + + .. code-block:: bash + + pip install -U pyEDAA.Reports + + .. tab-item:: Windows + :sync: Windows + + .. code-block:: powershell + + pip3 install -U pyEDAA.Reports + + +.. _INSTALL/pip/uninstall: + +Uninstallation using PIP +======================== + +.. tab-set:: + + .. tab-item:: Linux/macOS + :sync: Linux + + .. code-block:: bash + + pip uninstall pyEDAA.Reports + + .. tab-item:: Windows + :sync: Windows + + .. code-block:: powershell + + pip3 uninstall pyEDAA.Reports + + +.. _INSTALL/setup: + +Using ``setup.py`` (legacy) +*************************** + +See sections above on how to use PIP. + +Installation using ``setup.py`` +=============================== + +.. code-block:: bash + + setup.py install + + +.. _INSTALL/building: + +Local Packaging and Installation via PIP +**************************************** + +For development and bug fixing it might be handy to create a local wheel package and also install it locally on the +development machine. The following instructions will create a local wheel package (``*.whl``) and then use PIP to +install it. As a user might have a pyEDAA.Reports installation from PyPI, it's recommended to uninstall any previous +pyEDAA.Reports packages. (This step is also needed if installing an updated local wheel file with same version number. +PIP will not detect a new version and thus not overwrite/reinstall the updated package contents.) + +Ensure :ref:`packaging requirements ` are installed. + +.. tab-set:: + + .. tab-item:: Linux/macOS + :sync: Linux + + .. code-block:: bash + + cd + + # Package the code in a wheel (*.whl) + python -m build --wheel + + # Uninstall the old package + python -m pip uninstall -y pyEDAA.Reports + + # Install from wheel + python -m pip install ./dist/pyEDAA.Reports-0.1.0-py3-none-any.whl + + .. tab-item:: Windows + :sync: Windows + + .. code-block:: powershell + + cd + + # Package the code in a wheel (*.whl) + py -m build --wheel + + # Uninstall the old package + py -m pip uninstall -y pyEDAA.Reports + + # Install from wheel + py -m pip install .\dist\pyEDAA.Reports-0.1.0-py3-none-any.whl diff --git a/_sources/License.rst.txt b/_sources/License.rst.txt new file mode 100644 index 00000000..fba66a20 --- /dev/null +++ b/_sources/License.rst.txt @@ -0,0 +1,140 @@ +.. _SRCLICENSE: + +.. Note:: This is a local copy of the `Apache License Version 2.0 `__. + +.. Attention:: This **Apache License, 2.0** applies to all **source and configuration files of project**, **except documentation**. + +Apache License 2.0 +################## + +Version 2.0, January 2004 + +:xlarge:`TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION` + + +1. Definitions. +=============== +**"License"** shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +**"Licensor"** shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +**"Legal Entity"** shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that +entity. For the purposes of this definition, **"control"** means (i) the power, direct or indirect, to cause the direction or management of such entity, whether +by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +**"You"** (or **"Your"**) shall mean an individual or Legal Entity exercising permissions granted by this License. + +**"Source"** form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and +configuration files. + +**"Object"** form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object +code, generated documentation, and conversions to other media types. + +**"Work"** shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is +included in or attached to the work (an example is provided in the Appendix below). + +**"Derivative Works"** shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +**"Contribution"** shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative +Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to +submit on behalf of the copyright owner. For the purposes of this definition, **"submitted"** means any form of electronic, verbal, or written communication +sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue +tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is +conspicuously marked or otherwise designated in writing by the copyright owner as **"Not a Contribution."** + +**"Contributor"** shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. +============================== +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. +=========================== +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such +license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of +their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim +or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then +any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. +================== +You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +* You must give any other recipients of the Work or Derivative Works a copy of this License; and +* You must cause any modified files to carry prominent notices stating that You changed the files; and +* You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source + form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and +* If the Work includes a **"NOTICE"** text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the + attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the + following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the + Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE + file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, + alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise +complies with the conditions stated in this License. + +5. Submission of Contributions. +=============================== +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any +separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. +============== +This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable +and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. +========================== +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, +MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and +assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. +=========================== +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or +consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages +for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been +advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. +============================================== +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other +liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole +responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +---------------------------------------------------------------------------------------------------------------------------------------------------------------- + +:xlarge:`Appendix: How to apply the Apache License to your work` + +To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying +information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or +class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. + +.. code-block:: none + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/_sources/Logging/index.rst.txt b/_sources/Logging/index.rst.txt new file mode 100644 index 00000000..c19bc606 --- /dev/null +++ b/_sources/Logging/index.rst.txt @@ -0,0 +1,17 @@ +.. _LOGGING: + +EDA Tool Logs +############# + +* Simulation logs +* Synthesis logs +* Implementation logs + +.. rubric:: Goals + +* structured machine readable logs with labeling and classifications +* extract resource utilisation +* extract timing results +* extract warnings, critical warnings, errors +* check for critical behavior (e.g. generated latches, unreachable states, ...) +* log filtering diff --git a/_sources/TODO.rst.txt b/_sources/TODO.rst.txt new file mode 100644 index 00000000..3144da04 --- /dev/null +++ b/_sources/TODO.rst.txt @@ -0,0 +1,4 @@ +TODOs +##### + +.. todolist:: diff --git a/_sources/Unittesting/DataModel.rst.txt b/_sources/Unittesting/DataModel.rst.txt new file mode 100644 index 00000000..8bd53490 --- /dev/null +++ b/_sources/Unittesting/DataModel.rst.txt @@ -0,0 +1,796 @@ +.. _UNITTEST/DataModel: + +Unified data model +****************** + +The unified data model for test entities (test summary, test suite, test case) implements a super-set of all (so far +known) unit test result summary file formats. pyEDAA.Report's data model is a structural and functional cleanup of the +Ant JUnit data model. Naming has been cleaned up and missing features have been added. + +As some of the JUnit XML dialects are too divergent from the original Ant + JUnit4 format, these dialects have an +independent test entity inheritance hierarchy. Nonetheless, instances of each data format can be converted to and from +the unified data model. + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + .. grid:: 2 + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/DataModel/Testcase` + ^^^ + A :dfn:`test case` is the leaf-element in the test entity hierarchy and describes an individual test run. + Test cases are grouped by test suites. + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/DataModel/Testsuite` + ^^^ + A :dfn:`test suite` is a group of test cases and/or test suites. Test suites itself can be grouped by test + suites. The test suite hierarchy's root element is a test suite summary. + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/DataModel/TestsuiteSummary` + ^^^ + The :dfn:`test suite summary` is derived from test suite and defines the root of the test suite hierarchy. + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/DataModel/Document` + ^^^ + The :dfn:`document` is derived from a test suite summary and represents a file containing a test suite + summary. + + .. grid-item:: + :columns: 6 + + .. mermaid:: + + graph TD; + doc[Document] + sum[Summary] + ts1[Testsuite] + ts2[Testsuite] + ts21[Testsuite] + tc11[Testcase] + tc12[Testcase] + tc13[Testcase] + tc21[Testcase] + tc22[Testcase] + tc211[Testcase] + tc212[Testcase] + tc213[Testcase] + + doc:::root -.-> sum:::summary + sum --> ts1:::suite + sum --> ts2:::suite + ts2 --> ts21:::suite + ts1 --> tc11:::case + ts1 --> tc12:::case + ts1 --> tc13:::case + ts2 --> tc21:::case + ts2 --> tc22:::case + ts21 --> tc211:::case + ts21 --> tc212:::case + ts21 --> tc213:::case + + classDef root fill:#4dc3ff + classDef summary fill:#80d4ff + classDef suite fill:#b3e6ff + classDef case fill:#eeccff + + +.. _UNITTEST/DataModel/TestcaseStatus: + +Testcase Status +=============== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + :class:`~pyEDAA.Reports.Unittesting.TestcaseStatus` and :class:`~pyEDAA.Reports.Unittesting.TestsuiteStatus` are + flag enumerations to describe the overall status of a test case or test suite. + + Unknown + tbd + + Excluded + tbd + + Skipped + tbd + + Weak + tbd + + Passed + tbd + + Failed + tbd + + Inverted + tbd + + Warned + tbd + + Errored + tbd + + Failed + tbd + + SetupError + tbd + + TearDownError + tbd + + Inconsistent + tbd + + + .. grid-item:: + :columns: 6 + + .. code-block:: Python + + @export + class TestcaseStatus(Flag): + """A flag enumeration describing the status of a test case.""" + Unknown = 0 #: Testcase status is uninitialized and therefore unknown. + Excluded = 1 #: Testcase was permanently excluded / disabled + Skipped = 2 #: Testcase was temporarily skipped (e.g. based on a condition) + Weak = 4 #: No assertions were recorded. + Passed = 8 #: A passed testcase, because all assertions were successful. + Failed = 16 #: A failed testcase due to at least one failed assertion. + + Mask = Excluded | Skipped | Weak | Passed | Failed + + Inverted = 128 #: To mark inverted results + UnexpectedPassed = Failed | Inverted + ExpectedFailed = Passed | Inverted + + Warned = 1024 #: Runtime warning + Errored = 2048 #: Runtime error (mostly caught exceptions) + Aborted = 4096 #: Uncaught runtime exception + + SetupError = 8192 #: Preparation / compilation error + TearDownError = 16384 #: Cleanup error / resource release error + Inconsistent = 32768 #: Dataset is inconsistent + + Flags = Warned | Errored | Aborted | SetupError | TearDownError | Inconsistent + + .. code-block:: Python + + @export + class TestsuiteStatus(Flag): + """A flag enumeration describing the status of a test suite.""" + Unknown = 0 + Excluded = 1 #: Testcase was permanently excluded / disabled + Skipped = 2 #: Testcase was temporarily skipped (e.g. based on a condition) + Empty = 4 #: No tests in suite + Passed = 8 #: Passed testcase, because all assertions succeeded + Failed = 16 #: Failed testcase due to failing assertions + + Mask = Excluded | Skipped | Empty | Passed | Failed + + Inverted = 128 #: To mark inverted results + UnexpectedPassed = Failed | Inverted + ExpectedFailed = Passed | Inverted + + Warned = 1024 #: Runtime warning + Errored = 2048 #: Runtime error (mostly caught exceptions) + Aborted = 4096 #: Uncaught runtime exception + + SetupError = 8192 #: Preparation / compilation error + TearDownError = 16384 #: Cleanup error / resource release error + + Flags = Warned | Errored | Aborted | SetupError | TearDownError + + +.. _UNITTEST/DataModel/Testcase: + +Testcase +======== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + A :class:`~pyEDAA.Reports.Unittesting.Testcase` is the leaf-element in the test entity hierarchy and describes an + individual test run. Besides a test case status, it also contains statistics like the start time or the test + duration. Test cases are grouped by test suites and need to be unique per parent test suite. + + A test case (or its base classes) implements the following properties and methods: + + :data:`~pyEDAA.Reports.Unittesting.Base.Parent` + The test case has a reference to it's parent test suite in the hierarchy. By iterating parent references, the + root element (test suite summary) be be found, which has no parent reference (``None``). + + :data:`~pyEDAA.Reports.Unittesting.Base.Name` + The test case has a name. This name must be unique per hierarchy parent, but can exist multiple times in the + overall test hierarchy. + + In case the data format uses hierarchical names like ``pyEDAA.Reports.CLI.Application``, the name is split at + the separator and multiple hierarchy levels (test suites) are created in the unified data model. To be able to + recreate such an hierarchical name, :class:`~pyEDAA.Reports.Unittesting.TestsuiteKind` is applied accordingly + to test suite's :data:`~pyEDAA.Reports.Unittesting.TestsuiteBase.Kind` field. + + :data:`~pyEDAA.Reports.Unittesting.Base.StartTime` + The test case stores a time when the individual test run was started. In combination with + :data:`~pyEDAA.Reports.Unittesting.Base.TotalDuration`, the end time can be calculated. If the start time is + unknown, set this value to ``None``. + + :data:`~pyEDAA.Reports.Unittesting.Base.SetupDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TestDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TeardownDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TotalDuration` + The test case has fields to capture the setup duration, test run duration and teardown duration. The sum of all + durations is provided by total duration. + + :pycode:`TotalDuration := SetupDuration + TestDuration + TeardownDuration` + + The :dfn:`setup duration` is the time spend on setting up a test run. If the setup duration can't be + distinguished from the test's runtime, set this value to ``None``. + + The test's runtime without setup and teardown portions is captured by :dfn:`test duration`. If the duration is + unknown, set this value to ``None``. + + The :dfn:`teardown duration` of a test run is the time spend on tearing down a test run. If the teardown + duration can't be distinguished from the test's runtime, set this value to ``None``. + + The test case has a field :dfn:`total duration` to sum up setup duration, test duration and teardown duration. + If the duration is unknown, this value will be ``None``. + + :data:`~pyEDAA.Reports.Unittesting.Base.WarningCount`, :data:`~pyEDAA.Reports.Unittesting.Base.ErrorCount`, :data:`~pyEDAA.Reports.Unittesting.Base.FatalCount` + The test case counts for warnings, errors and fatal errors observed in a test run while the test was executed. + + :meth:`~pyEDAA.Reports.Unittesting.Base.__len__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__getitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__setitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__delitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__contains__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__iter__` + The test case implements a dictionary interface, so arbitrary key-value pairs can be annotated per test entity. + + :data:`~pyEDAA.Reports.Unittesting.Testcase.Status` + The overall status of a test case. + + See also: :ref:`UNITTEST/DataModel/TestcaseStatus`. + + :data:`~pyEDAA.Reports.Unittesting.Testcase.AssertionCount`, :data:`~pyEDAA.Reports.Unittesting.Testcase.PassedAssertionCount`, :data:`~pyEDAA.Reports.Unittesting.Testcase.FailedAssertionCount` + The :dfn:`assertion count` represents the overall number of assertions (checks) in a test case. It can be + distinguished into :dfn:`passed assertions` and :dfn:`failed assertions`. If it can't be distinguished, set + passed and failed assertions to ``None``. + + :pycode:`AssertionCount := PassedAssertionCount + FailedAssertionCount` + + :meth:`~pyEDAA.Reports.Unittesting.Testcase.Copy` + tbd + + :meth:`~pyEDAA.Reports.Unittesting.Testcase.Aggregate` + Aggregate (recalculate) all durations, warnings, errors, assertions, etc. + + :meth:`~pyEDAA.Reports.Unittesting.Testcase.__str__` + tbd + + .. grid-item:: + :columns: 6 + + .. code-block:: Python + + @export + class Testcase(Base): + def __init__( + self, + name: str, + startTime: Nullable[datetime] = None, + setupDuration: Nullable[timedelta] = None, + testDuration: Nullable[timedelta] = None, + teardownDuration: Nullable[timedelta] = None, + totalDuration: Nullable[timedelta] = None, + status: TestcaseStatus = TestcaseStatus.Unknown, + assertionCount: Nullable[int] = None, + failedAssertionCount: Nullable[int] = None, + passedAssertionCount: Nullable[int] = None, + warningCount: int = 0, + errorCount: int = 0, + fatalCount: int = 0, + parent: Nullable["Testsuite"] = None + ): + ... + + @readonly + def Parent(self) -> Nullable["Testsuite"]: + ... + + @readonly + def Name(self) -> str: + ... + + @readonly + def StartTime(self) -> Nullable[datetime]: + ... + + @readonly + def SetupDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TestDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TeardownDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TotalDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def WarningCount(self) -> int: + ... + + @readonly + def ErrorCount(self) -> int: + ... + + @readonly + def FatalCount(self) -> int: + ... + + def __len__(self) -> int: + ... + + def __getitem__(self, key: str) -> Any: + ... + + def __setitem__(self, key: str, value: Any) -> None: + ... + + def __delitem__(self, key: str) -> None: + ... + + def __contains__(self, key: str) -> bool: + ... + + def __iter__(self) -> Generator[Tuple[str, Any], None, None]: + ... + + @readonly + def Status(self) -> TestcaseStatus: + ... + + @readonly + def AssertionCount(self) -> int: + ... + + @readonly + def FailedAssertionCount(self) -> int: + ... + + @readonly + def PassedAssertionCount(self) -> int: + ... + + def Copy(self) -> "Testcase": + ... + + def Aggregate(self, strict: bool = True) -> TestcaseAggregateReturnType: + ... + + def __str__(self) -> str: + ... + + +.. _UNITTEST/DataModel/Testsuite: + +Testsuite +========= + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + A :class:`~pyEDAA.Reports.Unittesting.Testsuite` is a grouping element in the test entity hierarchy and describes + a group of test runs. Besides a list of test cases and a test suite status, it also contains statistics like the + start time or the test duration for the group of tests. Test suites are grouped by other test suites or a test + suite summary and need to be unique per parent test suite. + + A test suite (or its base classes) implements the following properties and methods: + + :data:`~pyEDAA.Reports.Unittesting.Base.Parent` + The test suite has a reference to it's parent test entity in the hierarchy. By iterating parent references, the + root element (test suite summary) be be found, which has no parent reference (``None``). + + :data:`~pyEDAA.Reports.Unittesting.Base.Name` + The test suite has a name. This name must be unique per hierarchy parent, but can exist multiple times in the + overall test hierarchy. + + In case the data format uses hierarchical names like ``pyEDAA.Reports.CLI.Application``, the name is split at + the separator and multiple hierarchy levels (test suites) are created in the unified data model. To be able to + recreate such an hierarchical name, :class:`~pyEDAA.Reports.Unittesting.TestsuiteKind` is applied accordingly + to test suite's :data:`~pyEDAA.Reports.Unittesting.TestsuiteBase.Kind` field. + + :data:`~pyEDAA.Reports.Unittesting.Base.StartTime` + The test suite stores a time when the first test run was started. In combination with + :data:`~pyEDAA.Reports.Unittesting.Base.TotalDuration`, the end time can be calculated. If the start time is + unknown, set this value to ``None``. + + :data:`~pyEDAA.Reports.Unittesting.Base.SetupDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TestDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TeardownDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TotalDuration` + The test suite has fields to capture the suite's setup duration, test group run duration and suite's + teardown duration. The sum of all durations is provided by total duration. + + :pycode:`TotalDuration := SetupDuration + TestDuration + TeardownDuration` + + The :dfn:`setup duration` is the time spend on setting up a test suite. If the setup duration can't be + distinguished from the test group's runtime, set this value to ``None``. + + The test group's runtime without setup and teardown portions is captured by :dfn:`test duration`. If the + duration is unknown, set this value to ``None``. + + The :dfn:`teardown duration` of a test suite is the time spend on tearing down a test suite. If the teardown + duration can't be distinguished from the test group's runtime, set this value to ``None``. + + The test suite has a field :dfn:`total duration` to sum up setup duration, test duration and teardown duration. + If the duration is unknown, this value will be ``None``. + + :data:`~pyEDAA.Reports.Unittesting.Base.WarningCount`, :data:`~pyEDAA.Reports.Unittesting.Base.ErrorCount`, :data:`~pyEDAA.Reports.Unittesting.Base.FatalCount` + The test suite counts for warnings, errors and fatal errors observed in a test suite while the tests were + executed. + + :meth:`~pyEDAA.Reports.Unittesting.Base.__len__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__getitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__setitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__delitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__contains__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__iter__` + The test suite implements a dictionary interface, so arbitrary key-value pairs can be annotated. + + + .. todo:: TestsuiteBase APIs + + + :data:`~pyEDAA.Reports.Unittesting.Testsuite.Testcases` + tbd + + :data:`~pyEDAA.Reports.Unittesting.Testsuite.TestcaseCount` + tbd + + :data:`~pyEDAA.Reports.Unittesting.Testsuite.AssertionCount` + The overall number of assertions (checks) in a test case. + + :meth:`~pyEDAA.Reports.Unittesting.Testsuite.Aggregate` + Aggregate (recalculate) all durations, warnings, errors, assertions, etc. + + :meth:`~pyEDAA.Reports.Unittesting.Testsuite.Iterate` + tbd + + :meth:`~pyEDAA.Reports.Unittesting.Testsuite.__str__` + tbd + + .. grid-item:: + :columns: 6 + + .. code-block:: Python + + @export + class Testsuite(TestsuiteBase[TestsuiteType]): + def __init__( + self, + name: str, + kind: TestsuiteKind = TestsuiteKind.Logical, + startTime: Nullable[datetime] = None, + setupDuration: Nullable[timedelta] = None, + testDuration: Nullable[timedelta] = None, + teardownDuration: Nullable[timedelta] = None, + totalDuration: Nullable[timedelta] = None, + status: TestsuiteStatus = TestsuiteStatus.Unknown, + warningCount: int = 0, + errorCount: int = 0, + fatalCount: int = 0, + testsuites: Nullable[Iterable[TestsuiteType]] = None, + testcases: Nullable[Iterable["Testcase"]] = None, + parent: Nullable[TestsuiteType] = None + ): + ... + + @readonly + def Parent(self) -> Nullable["Testsuite"]: + ... + + @readonly + def Name(self) -> str: + ... + + @readonly + def StartTime(self) -> Nullable[datetime]: + ... + + @readonly + def SetupDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TestDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TeardownDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TotalDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def WarningCount(self) -> int: + ... + + @readonly + def ErrorCount(self) -> int: + ... + + @readonly + def FatalCount(self) -> int: + ... + + def __len__(self) -> int: + ... + + def __getitem__(self, key: str) -> Any: + ... + + def __setitem__(self, key: str, value: Any) -> None: + ... + + def __delitem__(self, key: str) -> None: + ... + + def __contains__(self, key: str) -> bool: + ... + + def __iter__(self) -> Generator[Tuple[str, Any], None, None]: + ... + + # TestsuiteBase API + + + @readonly + def Testcases(self) -> Dict[str, "Testcase"]: + ... + + @readonly + def TestcaseCount(self) -> int: + ... + + @readonly + def AssertionCount(self) -> int: + ... + + def Aggregate(self, strict: bool = True) -> TestsuiteAggregateReturnType: + ... + + def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]: + ... + + def __str__(self) -> str: + ... + + +.. _UNITTEST/DataModel/TestsuiteSummary: + +TestsuiteSummary +================ + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + A :class:`~pyEDAA.Reports.Unittesting.TestsuiteSummary` is the root element in the test entity hierarchy and + describes a group of test suites as well as overall statistics for the whole set of test cases. A test suite + summary is derived for the same base-class as a test suite, thus they share almost all properties and methods. + + A test suite summary (or its base classes) implements the following properties and methods: + + :data:`~pyEDAA.Reports.Unittesting.Base.Parent` + The test suite summary has a parent reference, but as the root element in the test entity hierarchy, its always + ``None``. + + :data:`~pyEDAA.Reports.Unittesting.Base.Name` + The test suite summary has a name. + + :data:`~pyEDAA.Reports.Unittesting.Base.StartTime` + The test suite summary stores a time when the first test runs was started. In combination with + :data:`~pyEDAA.Reports.Unittesting.Base.TotalDuration`, the end time can be calculated. If the start time is + unknown, set this value to ``None``. + + :data:`~pyEDAA.Reports.Unittesting.Base.SetupDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TestDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TeardownDuration`, :data:`~pyEDAA.Reports.Unittesting.Base.TotalDuration` + The test suite summary has fields to capture the suite summary's setup duration, overall run duration and + suite summary's teardown duration. The sum of all durations is provided by total duration. + + :pycode:`TotalDuration := SetupDuration + TestDuration + TeardownDuration` + + The :dfn:`setup duration` is the time spend on setting up an overall test run. If the setup duration can't be + distinguished from the test's runtimes, set this value to ``None``. + + The test suite summary's runtime without setup and teardown portions is captured by :dfn:`test duration`. If + the duration is unknown, set this value to ``None``. + + The :dfn:`teardown duration` of a test suite summary is the time spend on tearing down a test suite summary. If + the teardown duration can't be distinguished from the test's runtimes, set this value to ``None``. + + The test suite summary has a field :dfn:`total duration` to sum up setup duration, overall run duration and + teardown duration. If the duration is unknown, this value will be ``None``. + + :data:`~pyEDAA.Reports.Unittesting.Base.WarningCount`, :data:`~pyEDAA.Reports.Unittesting.Base.ErrorCount`, :data:`~pyEDAA.Reports.Unittesting.Base.FatalCount` + The test suite summary counts for warnings, errors and fatal errors observed in a test suite summary while the + tests were executed. + + :meth:`~pyEDAA.Reports.Unittesting.Base.__len__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__getitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__setitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__delitem__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__contains__`, :meth:`~pyEDAA.Reports.Unittesting.Base.__iter__` + The test suite summary implements a dictionary interface, so arbitrary key-value pairs can be annotated. + + .. todo:: TestsuiteBase APIs + + :meth:`~pyEDAA.Reports.Unittesting.TestsuiteSummary.Aggregate` + tbd + + :meth:`~pyEDAA.Reports.Unittesting.TestsuiteSummary.Iterate` + tbd + + :meth:`~pyEDAA.Reports.Unittesting.TestsuiteSummary.__str__` + tbd + + .. grid-item:: + :columns: 6 + + .. code-block:: Python + + @export + class TestsuiteSummary(TestsuiteBase[TestsuiteType]): + def __init__( + self, + name: str, + startTime: Nullable[datetime] = None, + setupDuration: Nullable[timedelta] = None, + testDuration: Nullable[timedelta] = None, + teardownDuration: Nullable[timedelta] = None, + totalDuration: Nullable[timedelta] = None, + status: TestsuiteStatus = TestsuiteStatus.Unknown, + warningCount: int = 0, + errorCount: int = 0, + fatalCount: int = 0, + testsuites: Nullable[Iterable[TestsuiteType]] = None, + parent: Nullable[TestsuiteType] = None + ): + ... + + @readonly + def Parent(self) -> Nullable["Testsuite"]: + ... + + @readonly + def Name(self) -> str: + ... + + @readonly + def StartTime(self) -> Nullable[datetime]: + ... + + @readonly + def SetupDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TestDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TeardownDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def TotalDuration(self) -> Nullable[timedelta]: + ... + + @readonly + def WarningCount(self) -> int: + ... + + @readonly + def ErrorCount(self) -> int: + ... + + @readonly + def FatalCount(self) -> int: + ... + + def __len__(self) -> int: + ... + + def __getitem__(self, key: str) -> Any: + ... + + def __setitem__(self, key: str, value: Any) -> None: + ... + + def __delitem__(self, key: str) -> None: + ... + + def __contains__(self, key: str) -> bool: + ... + + def __iter__(self) -> Generator[Tuple[str, Any], None, None]: + ... + + # TestsuiteBase API + + def Aggregate(self) -> TestsuiteAggregateReturnType: + ... + + def Iterate(self, scheme: IterationScheme = IterationScheme.Default) -> Generator[Union[TestsuiteType, Testcase], None, None]: + ... + + def __str__(self) -> str: + ... + + +.. _UNITTEST/DataModel/Document: + +Document +======== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + A :class:`~pyEDAA.Reports.Unittesting.Document` is a mixin-class ... + + :data:`~pyEDAA.Reports.Unittesting.Document.Path` + tbd + + :data:`~pyEDAA.Reports.Unittesting.Document.AnalysisDuration` + tbd + + :data:`~pyEDAA.Reports.Unittesting.Document.ModelConversionDuration` + tbd + + :meth:`~pyEDAA.Reports.Unittesting.Document.Analyze` + tbd + + :meth:`~pyEDAA.Reports.Unittesting.Document.Convert` + tbd + + .. grid-item:: + :columns: 6 + + .. code-block:: Python + + @export + class Document(metaclass=ExtendedType, mixin=True): + def __init__(self, path: Path): + ... + + @readonly + def Path(self) -> Path: + ... + + @readonly + def AnalysisDuration(self) -> timedelta: + ... + + @readonly + def ModelConversionDuration(self) -> timedelta: + ... + + @abstractmethod + def Analyze(self) -> None: + ... + + @abstractmethod + def Convert(self): + ... diff --git a/_sources/Unittesting/Features.rst.txt b/_sources/Unittesting/Features.rst.txt new file mode 100644 index 00000000..60f65bf9 --- /dev/null +++ b/_sources/Unittesting/Features.rst.txt @@ -0,0 +1,506 @@ +.. _UNITTEST/Features: + +Features +******** + + +.. _UNITTEST/Feature/Create: + +Create test entities +==================== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + The hierarchy of test entities (test cases, test suites and test summaries) can be constructed top-down or + bottom-up. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Test Case + :sync: Testcase + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting import Testsuite, Testcase + + # Top-down + ts1 = Testsuite("ts1") + + tc = Testcase("tc", parent=ts) + + # Bottom-up + tc1 = Testcase("tc1") + tc2 = Testcase("tc2") + + ts2 = Testsuite("ts2", testcases=(tc1, tc2)) + + # ts.AddTestcase(...) + tc3 = Testcase("tc3") + tc4 = Testcase("tc4") + + ts3 = Testsuite("ts3") + ts3.AddTestcase(tc3) + ts3.AddTestcase(tc4) + + # ts.AddTestcases(...) + tc3 = Testcase("tc3") + tc4 = Testcase("tc4") + + ts3 = Testsuite("ts3") + ts3.AddTestcases((tc3, tc4)) + + .. tab-item:: Test Suite + :sync: Testsuite + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting import Testsuite, TestsuiteSummary + + # Top-down + ts = Testsuite("ts") + + ts1 = Testsuite("ts1", parent=tss) + + # Bottom-up + ts2 = Testsuite("ts2") + ts3 = Testsuite("ts3") + + ts4 = Testsuite("ts4", testsuites=(ts2, ts3)) + + # ts.AddTestsuite(...) + ts5 = Testcase("ts5") + ts6 = Testcase("ts6") + + ts7 = Testsuite("ts7") + ts7.AddTestsuite(ts5) + ts7.AddTestsuite(ts6) + + # ts.AddTestsuites(...) + ts8 = Testcase("ts8") + ts9 = Testcase("ts9") + + ts10 = Testsuite("ts10") + ts10.AddTestsuites((ts8, ts9)) + + .. tab-item:: Test Suite Summary + :sync: TestsuiteSummary + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting import Testsuite, TestsuiteSummary + + # Top-down + + # Bottom-up + + +.. _UNITTEST/Feature/Read: + +Reading unittest reports +======================== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + A JUnit XML test report summary file can be read by creating an instance of the :class:`~pyEDAA.Reports.Unittesting.JUnit.Document` + class. Because JUnit has so many dialects, a derived subclass for the dialect might be required. By choosing the + right Document class, also the XML schema for XML schema validation gets pre-selected. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Any JUnit + :sync: AnyJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit import Document + + xmlReport = Path("AnyJUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: Ant + JUnit4 + :sync: AntJUnit4 + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.AntJUnit import Document + + xmlReport = Path("AntJUnit4-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: CTest JUnit + :sync: CTestJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document + + xmlReport = Path("CTest-JUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: GoogleTest JUnit + :sync: GTestJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document + + xmlReport = Path("GoogleTest-JUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: pyTest JUnit + :sync: pyTestJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document + + xmlReport = Path("pyTest-JUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + + +.. _UNITTEST/Feature/Convert: + +Converting unittest reports +=========================== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + Any JUnit dialect specific data model can be converted to the generic hierarchy of test entities. + + + .. note:: + + This conversion is identical for all derived dialects. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Document + :sync: Document + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit import Document + + # Read from XML file + xmlReport = Path("JUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + # Convert to unified test data model + summary = doc.ToTestsuiteSummary() + + # Convert to a tree + rootNode = doc.ToTree() + + # Convert back to a document + newXmlReport = Path("New JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + # Write to XML file + newDoc.Write() + + +.. _UNITTEST/Feature/Annotation: + +Annotations +=========== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + Every test entity can be annotated with arbitrary key-value pairs. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Testcase + :sync: Testcase + + .. code-block:: Python + + # Add annotate a key-value pair + testcase["key"] = value + + # Update existing annotation with new value + testcase["key"] = newValue + + # Check if key exists + if "key" in testcase: + pass + + # Access annoation by key + value = testcase["key"] + + # Get number of annotations + annotationCount = len(testcase) + + # Delete annotation + del testcase["key"] + + # Iterate annotations + for key, value in testcases: + pass + + .. tab-item:: Testsuite + :sync: Testsuite + + .. code-block:: Python + + # Add annotate a key-value pair + testsuite["key"] = value + + # Update existing annotation with new value + testsuite["key"] = newValue + + # Check if key exists + if "key" in testsuite: + pass + + # Access annoation by key + value = testsuite["key"] + + # Get number of annotations + annotationCount = len(testsuite) + + # Delete annotation + del testsuite["key"] + + # Iterate annotations + for key, value in testsuite: + pass + + .. tab-item:: TestsuiteSummary + :sync: TestsuiteSummary + + .. code-block:: Python + + # Add annotate a key-value pair + testsuiteSummary["key"] = value + + # Update existing annotation with new value + testsuiteSummary["key"] = newValue + + # Check if key exists + if "key" in testsuiteSummary: + pass + + # Access annoation by key + value = testsuiteSummary["key"] + + # Get number of annotations + annotationCount = len(testsuiteSummary) + + # Delete annotation + del testsuiteSummary["key"] + + # Iterate annotations + for key, value in testsuiteSummary: + pass + + + +.. _UNITTEST/Feature/Merge: + +Merging unittest reports +======================== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + add description here + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Testcase + :sync: Testcase + + .. code-block:: Python + + # add code here + +.. _UNITTEST/Feature/Concat: + +Concatenate unittest reports +============================ + +.. todo:: Planned feature. + +.. _UNITTEST/Feature/Transform: + +Transforming the reports' hierarchy +=================================== + +.. _UNITTEST/Feature/Transform/pytest: + +pytest specific transformations +------------------------------- + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + add description here + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Testcase + :sync: Testcase + + .. code-block:: Python + + # add code here + +.. _UNITTEST/Feature/Write: + +Writing unittest reports +======================== + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + A test suite summary can be converted to a document of any JUnit dialect. Internally a deep-copy is created to + convert from a hierarchy of the unified test entities to a hierarchy of specific test entities (e.g. JUnit + entities). + + When the document was created, it can be written to disk. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Any JUnit + :sync: AnyJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit import Document + + # Convert a TestsuiteSummary back to a Document + newXmlReport = Path("JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + # Write to XML file + try: + newDoc.Write() + except UnittestException as ex: + ... + + .. tab-item:: Ant + JUnit4 + :sync: AntJUnit4 + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.AntJUnit import Document + + # Convert a TestsuiteSummary back to a Document + newXmlReport = Path("JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + # Write to XML file + try: + newDoc.Write() + except UnittestException as ex: + ... + + .. tab-item:: CTest JUnit + :sync: CTestJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document + + # Convert a TestsuiteSummary back to a Document + newXmlReport = Path("JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + # Write to XML file + try: + newDoc.Write() + except UnittestException as ex: + ... + + .. tab-item:: GoogleTest JUnit + :sync: GTestJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document + + # Convert a TestsuiteSummary back to a Document + newXmlReport = Path("JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + # Write to XML file + try: + newDoc.Write() + except UnittestException as ex: + ... + + .. tab-item:: pyTest JUnit + :sync: pyTestJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document + + # Convert a TestsuiteSummary back to a Document + newXmlReport = Path("JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + # Write to XML file + try: + newDoc.Write() + except UnittestException as ex: + ... diff --git a/_sources/Unittesting/JUnitDataModel.rst.txt b/_sources/Unittesting/JUnitDataModel.rst.txt new file mode 100644 index 00000000..d9b5aaa2 --- /dev/null +++ b/_sources/Unittesting/JUnitDataModel.rst.txt @@ -0,0 +1,445 @@ +.. _UNITTEST/SpecificDataModel/JUnit: + +JUnit Data Model +================ + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + .. grid:: 2 + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/SpecificDataModel/JUnit/Testcase` + ^^^ + A :dfn:`test case` is the leaf-element in the test entity hierarchy and describes an individual test run. + Test cases are grouped by test classes. + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/SpecificDataModel/JUnit/Testclass` + ^^^ + A :dfn:`test class` is the mid-level element in the test entity hierarchy and describes a group of test + runs. Test classes are grouped by test suites. + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/SpecificDataModel/JUnit/Testsuite` + ^^^ + A :dfn:`test suite` is a group of test classes. Test suites are grouped by a test suite summary. + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/SpecificDataModel/JUnit/TestsuiteSummary` + ^^^ + The :dfn:`test suite summary` is derived from test suite and defines the root of the test suite hierarchy. + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/SpecificDataModel/JUnit/Document` + ^^^ + The :dfn:`document` is derived from a test suite summary and represents a file containing a test suite + summary. + + .. grid-item-card:: + :columns: 6 + + :ref:`UNITTEST/SpecificDataModel/JUnit/Dialects` + ^^^ + The JUnit format is not well defined, thus multiple dialects developed over time. + + .. grid-item:: + :columns: 6 + + .. mermaid:: + + graph TD; + doc[Document] + sum[Summary] + ts1[Testsuite] + ts11[Testsuite] + ts2[Testsuite] + + tc111[Testclass] + tc112[Testclass] + tc23[Testclass] + + tc1111[Testcase] + tc1112[Testcase] + tc1113[Testcase] + tc1121[Testcase] + tc1122[Testcase] + tc231[Testcase] + tc232[Testcase] + tc233[Testcase] + + doc:::root -.-> sum:::summary + sum --> ts1:::suite + sum ---> ts2:::suite + ts1 --> ts11:::suite + + ts11 --> tc111:::cls + ts11 --> tc112:::cls + ts2 --> tc23:::cls + + tc111 --> tc1111:::case + tc111 --> tc1112:::case + tc111 --> tc1113:::case + tc112 --> tc1121:::case + tc112 --> tc1122:::case + tc23 --> tc231:::case + tc23 --> tc232:::case + tc23 --> tc233:::case + + classDef root fill:#4dc3ff + classDef summary fill:#80d4ff + classDef suite fill:#b3e6ff + classDef cls fill:#ff9966 + classDef case fill:#eeccff + +.. _UNITTEST/SpecificDataModel/JUnit/Testcase: + +Testcase +-------- + +.. _UNITTEST/SpecificDataModel/JUnit/Testclass: + +Testclass +--------- + +.. _UNITTEST/SpecificDataModel/JUnit/Testsuite: + +Testsuite +--------- + +.. _UNITTEST/SpecificDataModel/JUnit/TestsuiteSummary: + +TestsuiteSummary +---------------- + +.. _UNITTEST/SpecificDataModel/JUnit/Document: + +Document +-------- + +.. _UNITTEST/SpecificDataModel/JUnit/Dialects: + +JUnit Dialects +============== + +As the JUnit XML format was not well specified and no XML Schema Definition (XSD) was provided, many variants and +dialects (and simplifications) were created by the various frameworks emitting JUnit XML files. + +.. rubric:: JUnit Dialect Comparison + ++------------------------+--------------+--------------+--------------------+------------------+--------------+ +| Feature | Any JUnit | Ant + JUnit4 | CTest JUnit | GoogleTest JUnit | pyTest JUnit | ++========================+==============+==============+====================+==================+==============+ +| Root element | testsuites | testsuite | testsuite | testsuites | testsuites | ++------------------------+--------------+--------------+--------------------+------------------+--------------+ +| Supports properties | ☑ | ☑ | | ⸺ | | ++------------------------+--------------+--------------+--------------------+------------------+--------------+ +| Testcase status | ... | ... | more status values | | | ++------------------------+--------------+--------------+--------------------+------------------+--------------+ + +.. _UNITTEST/SpecificDataModel/JUnit/Dialect/AnyJUnit: + +Any JUnit +--------- + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + The Any JUnit format uses a relaxed XML schema definition aiming to parse many JUnit XML dialects, which use a + ```` root element. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Reading Any JUnit + :sync: ReadJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit import Document + + xmlReport = Path("AnyJUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: Convert to and from Unified Data Model + :sync: ConvertToFrom + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit import Document + + # Convert to unified test data model + summary = doc.ToTestsuiteSummary() + + # Convert back to a document + newXmlReport = Path("New JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + .. tab-item:: Writing Any JUnit + :sync: WriteJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit import Document + + xmlReport = Path("AnyJUnit-Report.xml") + try: + newDoc.Write(xmlReport) + except UnittestException as ex: + ... + + +.. _UNITTEST/SpecificDataModel/JUnit/Dialect/AntJUnit4: + +Ant + JUnit4 +------------ + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + The original JUnit format created by `Ant `__ for `JUnit4 `__ + uses ```` as a root element. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Reading Ant + JUnit4 + :sync: ReadJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document + + xmlReport = Path("AntJUnit4-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: Convert to and from Unified Data Model + :sync: ConvertToFrom + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document + + # Convert to unified test data model + summary = doc.ToTestsuiteSummary() + + # Convert back to a document + newXmlReport = Path("New JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + .. tab-item:: Writing Ant + JUnit4 + :sync: WriteJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.AntJUnit4 import Document + + xmlReport = Path("AnyJUnit-Report.xml") + try: + newDoc.Write(xmlReport) + except UnittestException as ex: + ... + + + +.. _UNITTEST/SpecificDataModel/JUnit/Dialect/CTest: + +CTest JUnit +----------- + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + The CTest JUnit format written by `CTest `__ uses ```` as a root + element. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Reading CTest JUnit + :sync: ReadJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document + + xmlReport = Path("CTestJUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: Convert to and from Unified Data Model + :sync: ConvertToFrom + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document + + # Convert to unified test data model + summary = doc.ToTestsuiteSummary() + + # Convert back to a document + newXmlReport = Path("New JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + .. tab-item:: Writing CTest JUnit + :sync: WriteJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.CTestJUnit import Document + + xmlReport = Path("AnyJUnit-Report.xml") + try: + newDoc.Write(xmlReport) + except UnittestException as ex: + ... + + +.. _UNITTEST/SpecificDataModel/JUnit/Dialect/GoogleTest: + +GoogleTest JUnit +---------------- + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + The GoogleTest JUnit format written by `GoogleTest `__ (sometimes GTest) + uses ```` as a root element. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Reading GoogleTest JUnit + :sync: ReadJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document + + xmlReport = Path("GoogleTestJUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: Convert to and from Unified Data Model + :sync: ConvertToFrom + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document + + # Convert to unified test data model + summary = doc.ToTestsuiteSummary() + + # Convert back to a document + newXmlReport = Path("New JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + .. tab-item:: Writing GoogleTest JUnit + :sync: WriteJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.GoogleTestJUnit import Document + + xmlReport = Path("AnyJUnit-Report.xml") + try: + newDoc.Write(xmlReport) + except UnittestException as ex: + ... + + +.. _UNITTEST/SpecificDataModel/JUnit/Dialect/pyTest: + +pyTest JUnit +------------ + +.. grid:: 2 + + .. grid-item:: + :columns: 6 + + The pyTest JUnit format written by `pyTest `__ uses ```` as a + root element. + + .. grid-item:: + :columns: 6 + + .. tab-set:: + + .. tab-item:: Reading pyTest JUnit + :sync: ReadJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document + + xmlReport = Path("PyTestJUnit-Report.xml") + try: + doc = Document(xmlReport, parse=True) + except UnittestException as ex: + ... + + .. tab-item:: Convert to and from Unified Data Model + :sync: ConvertToFrom + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document + + # Convert to unified test data model + summary = doc.ToTestsuiteSummary() + + # Convert back to a document + newXmlReport = Path("New JUnit-Report.xml") + newDoc = Document.FromTestsuiteSummary(newXmlReport, summary) + + .. tab-item:: Writing pyTest JUnit + :sync: WriteJUnit + + .. code-block:: Python + + from pyEDAA.Reports.Unittesting.JUnit.PyTestJUnit import Document + + xmlReport = Path("AnyJUnit-Report.xml") + try: + newDoc.Write(xmlReport) + except UnittestException as ex: + ... diff --git a/_sources/Unittesting/OSVVMDataModel.rst.txt b/_sources/Unittesting/OSVVMDataModel.rst.txt new file mode 100644 index 00000000..f11cf638 --- /dev/null +++ b/_sources/Unittesting/OSVVMDataModel.rst.txt @@ -0,0 +1,8 @@ +.. _UNITTEST/SpecificDataModel/OSVVM: + +OSVVM +===== + +`Open Source VHDL Verification Methodology `__ writes test results as YAML files for its +internal data model storage. Some YAML files are written by the VHDL code of the verification framework, others are +written by `OSVVM-Scripts `__ as a test runner. diff --git a/_sources/Unittesting/index.rst.txt b/_sources/Unittesting/index.rst.txt new file mode 100644 index 00000000..24f9fa21 --- /dev/null +++ b/_sources/Unittesting/index.rst.txt @@ -0,0 +1,210 @@ +.. _UNITTEST: + +Unittesting +########### + +*pyEDAA.Reports* provides a unified and generic unittest summary data model. The data model allows the description of +testcases grouped in testsuites. Testsuites can be nested in other testsuites. The data model's root element is a +special testsuite called testsuite summary. It contains only testsuites, but no testcases. + +The data model can be filled from various sources like **Ant JUnit test reports** or **OSVVM testsuite summaries** (more +to be added). Many programming languages and/or unit testing frameworks support exporting results in the Ant JUnit +format. See below for supported formats and their variations (dialects). + +.. attention:: + + The so called JUnit XML format is the weakest file format and standard ever seen. At first was not created by JUnit + (version 4). It was added by the built system Ant, but it's not called Ant XML format nor Ant JUnit XML format. The + latest JUnit 5 uses a completely different format called :ref:`open test reporting `. As + JUnit is not the formats author, no file format documentation nor XML schema was provided. Also Ant isn't providing + any file format documentation or XML schema. Various Ant JUnit XML adopters have tried to reverse engineer a + description and XML schemas, but unfortunately many are not even compatible to each other. + + +.. include:: DataModel.rst + +.. _UNITTEST/SpecificDataModels: + +Specific Data Models +******************** + +.. include:: JUnitDataModel.rst +.. include:: OSVVMDataModel.rst + + +.. include:: Features.rst + + + +.. _UNITTEST/CLI: + +Command Line Tool +***************** + +.. code-block:: bash + + pyedaa-reports unittest --input=Ant-JUnit:data/JUnit.xml + + +.. _UNITTEST/FileFormats: + +File Formats +************ + +Unittest summary reports can be stored in various file formats. Usually these files are XML based. Due to missing +(clear) specifications and XML schema definitions, some file formats have developed dialects. Either because the +specification was unclear/not existing or because the format was specific for a single programming language, so tools +added extensions or misused XML attributes instead of designing their own file format. + +.. _UNITTEST/FileFormats/AntJUnit4: + +Ant and JUnit 4 XML +=================== + +The so-called JUnit XML format was defined by Ant when running JUnit4 test suites. Because the format was not specified +by JUnit4, many dialects spread out. Many tools and test frameworks have minor or major differences compared to the +original format. While some modifications seam logical additions or adjustments to the needs of the respective +framework, others undermine the ideas and intents of the data format. + +Many issues arise because the :ref:`Ant + JUnit4 ` format is +specific to unit testing with Java. Other languages and frameworks were lazy and didn't derive their own format, but +rather stuffed their language constructs into the concepts and limitations of the Ant + JUnit4 XML format. + +.. rubric:: JUnit Dialects + +* 🚧 Bamboo JUnit (planned) +* ✅ :ref:`CTest JUnit format ` +* ✅ :ref:`GoogleTest JUnit format ` +* 🚧 Jenkins JUnit (planned) +* ✅ :ref:`pyTest JUnit format ` + + +.. _UNITTEST/FileFormats/JUnit5: + +JUnit 5 XML +=========== + +JUnit5 uses a new format called :ref:`UNITTEST/FileFormats/OTR` (see the following section for details). This format +isn't specific to Java (packages, classes, methods, ...), but describes a generic data model. Of cause an extension for +Java specifics is provided too. + + +.. _UNITTEST/FileFormats/OTR: + +Open Test Reporting +=================== + +The `Open Test Alliance `__ created a new format called +`Open Test Reporting `__ (OTR) to overcome the shortcommings of a +missing file format for JUnit5 as well as the problems of Ant + JUnit4. + +OTR defines a structure of test groups and tests, but no specifics of a certain programming languge. The logical +structure of tests and test groups is decoupled from language specifics like namespaces, packages or classes hosting the +individual tests. + + +.. _UNITTEST/FileFormats/OSVVM: + +OSVVM YAML +========== + +The `Open Source VHDL Verification Methodology (OSVVM) `__ defines its own test report format +in YAML. While OSVVM is able to convert its own YAML files to JUnit XML files, it's recommended to use the YAML files as +data source, because these contain additional information, which can't be expressed with JUnit XML. + +The YAML files are created when OSVVM-based testbenches are executed with OSVVM's embedded TCL scripting environment +`OSVVM-Scripts `__. + +.. hint:: + + YAML was chosen instead of JSON or XML, because a YAML document isn't corrupted in case of a runtime error. The + document might be incomplete (content), but not corrupted (structural). Such a scenario is possible if a VHDL + simulator stops execution, then the document structure can't be finalized. + + + + +.. _UNITTEST/Tools: + +Frameworks / Tools +****************** + +.. _UNITTEST/Tool/CTest: + +CTest +===== + +* https://github.com/bvdberg/ctest + + +.. _UNITTEST/Tool/GoogleTest: + +GoogleTest (gtest) +================== + +* https://github.com/google/googletest + + +.. _UNITTEST/Tool/JUnit4: + +JUnit4 +====== + +* https://github.com/apache/ant +* https://github.com/junit-team/junit4 + + +.. _UNITTEST/Tool/JUnit5: + +JUnit5 +====== + + +.. _UNITTEST/Tool/OSVVM: + +OSVVM +===== + +* https://github.com/OSVVM/OSVVM +* https://github.com/OSVVM/OSVVM-Scripts + + +.. _UNITTEST/Tool/pytest: + +pytest +====== + +* https://github.com/pytest-dev/pytest + + +.. _UNITTEST/Tool/VUnit: + +VUnit +===== + +* https://github.com/VUnit/vunit + + +.. _UNITTEST/Consumers: + + +Consumers +********* + +.. _UNITTEST/Consumer/GitLab: + +GitLab +====== + +.. _UNITTEST/Consumer/Jenkins: + +Jenkins +======= + + +.. _UNITTEST/Consumer/Dorney: + +Dorney (GitHub Action) +====================== + +* https://github.com/dorny/test-reporter diff --git a/_sources/genindex.rst.txt b/_sources/genindex.rst.txt new file mode 100644 index 00000000..c07da40d --- /dev/null +++ b/_sources/genindex.rst.txt @@ -0,0 +1,4 @@ +.. This file is a placeholder and will be replaced + +Index +##### diff --git a/_sources/index.rst.txt b/_sources/index.rst.txt new file mode 100644 index 00000000..d9e01d34 --- /dev/null +++ b/_sources/index.rst.txt @@ -0,0 +1,264 @@ +.. include:: shields.inc + +.. image:: _static/logo.svg + :height: 90 px + :align: center + :target: https://GitHub.com/edaa-org/pyEDAA.Reports + +.. raw:: html + +
+ +.. raw:: latex + + \part{Introduction} + +.. only:: html + + | |SHIELD:svg:Reports-github| |SHIELD:svg:Reports-ghp-doc| |SHIELD:svg:Reports-gitter| + | |SHIELD:svg:Reports-gha-test| |SHIELD:svg:Reports-codacy-quality| + +.. Disabled shields: |SHIELD:svg:Reports-src-license| |SHIELD:svg:Reports-doc-license| |SHIELD:svg:Reports-pypi-tag| |SHIELD:svg:Reports-pypi-status| |SHIELD:svg:Reports-pypi-python| |SHIELD:svg:Reports-lib-status| |SHIELD:svg:Reports-codacy-coverage| |SHIELD:svg:Reports-codecov-coverage| |SHIELD:svg:Reports-lib-dep| |SHIELD:svg:Reports-req-status| |SHIELD:svg:Reports-lib-rank| + +.. only:: latex + + |SHIELD:png:Reports-github| |SHIELD:png:Reports-ghp-doc| |SHIELD:png:Reports-gitter| + |SHIELD:png:Reports-gha-test| |SHIELD:png:Reports-codacy-quality| + +.. Disabled shields: |SHIELD:png:Reports-src-license| |SHIELD:png:Reports-doc-license| |SHIELD:png:Reports-pypi-tag| |SHIELD:png:Reports-pypi-status| |SHIELD:png:Reports-pypi-python| |SHIELD:png:Reports-lib-status| |SHIELD:png:Reports-codacy-coverage| |SHIELD:png:Reports-codecov-coverage| |SHIELD:png:Reports-lib-dep| |SHIELD:png:Reports-req-status| |SHIELD:png:Reports-lib-rank| + +The pyEDAA.Reports Documentation +################################ + +A collection of various (EDA tool-specific) report data formats. + +.. _GOALS: + +Main Goals +********** + +This package provides abstract data models and specific implementations for report formats. The supported report formats +are commonly used for any programming language or have a specifc context with Electronic Design Automation (EDA) tools. +Examples are unit test summaries (like Ant JUnit XML), code coverage (like Cobertura) and documentation coverage reports. + +While the data models and file format implementations can be used as a library, a CLI program ``pyedaa-report`` will be +provided too. It allows reading, converting, concatenating, merging, transforming and writing report files. + +.. admonition:: Roadmap + + It's also planned to support console outputs from simulators and synthesis/implementation tools to create structured + logs and reports for filtering and data extraction. + + + + + + +Report Formats +************** + +.. grid:: 3 + + .. grid-item-card:: + :columns: 4 + + :ref:`🚧 Code Coverage ` + ^^^ + + Code coverage measures used and unused code lines, statements, branches, etc. Depending on the programming + language this is measured by instrumenting the code/binary and running the program, it's test cases or simulating + the code. In generate code coverage is a measure of test coverage. Unused code is not (yet) covered by tests. + + The code coverage metric in percent is a ratio of used code versus all possibly usable code. A coverage of <100% + indicates unused code. This can be dead code (unreachable) or untested code (⇒ needs more test cases). + + **Supported tools** + + * Coverage.py / pytest-cov + * Aldec Riviera-PRO + * others tbd. (GHDL with enabled coverage) + + .. # (via ACDB conversion of UCDB to UCIS format and then converted to Cobertura) + + **Supported file formats** + + * Cobertura + * others tbd. (gcov) + + + .. grid-item-card:: + :columns: 4 + + :ref:`🚧 Documentation Coverage ` + ^^^ + + Documentation coverage measures the presence of code documentation. It primarily counts for public language + entities like publicly visible constants and variables, parameters, types, functions, methods, classes, modules, + packages, etc. The documentation goal depends on the used coverage collection tool's settings. E.g. usually, + private language entities are not required to be documented. + + The documentation coverage metric in percent is a ratio of documented language entity versus all documentation + worthy langauge entities. A coverage of <100% indicates undocumented code. + + .. rubric:: Supported tools + + * docstr_coverage + * others tbd. (GHDL) + + .. rubric:: Supported file formats + + * tbd. + + + .. grid-item-card:: + :columns: 4 + + :ref:`Unit Test Summaries ` + ^^^ + + Results of (unit) tests (also regression tests) are collected in machine readable summary files. Test cases are + usually grouped by one or more test suites. Besides the test's result (passed, failed, skipped, ...) also the + test's outputs and durations are collected. Results can be visualized as a expandable tree structure. + + The total number of testcases indicates the spend effort in testing and applying many test vectors. In combination + with code coverage, it can be judged if the code has untested sections. + + .. rubric:: Supported features + + * :ref:`Read Ant JUnit XML files (and various dialects) ` + * :ref:`Merge Ant JUnit reports ` + * :ref:`Concatenate Ant JUnit reports ` + * :ref:`Transform the hierarchy of reports ` + * :ref:`Write Ant JUnit reports (also to other dialects) ` + + .. rubric:: Supported file formats + + * :ref:`Ant JUnit4 XML format and various dialects ` + * :ref:`JUnit5 XML format (Open Test Reporting) ` + * :ref:`OSVVM YAML format ` + + .. rubric:: Supported tools + + * :ref:`CTest ` + * :ref:`GoogleTest ` + * :ref:`Ant + JUnit4 ` + * :ref:`JUnit5 ` + * :ref:`OSVVM ` + * :ref:`pyTest ` + + + .. #grid-item-card:: + :columns: 4 + + :ref:`Tool Outputs ` + ^^^ + + .. rubric:: Supported tools + + * planned: Vivado synthesis + * planned: Vivado implementation + * others tbd. (GHDL) + + .. rubric:: Supported file formats + + * tbd. + + +.. _CONSUMERS: + +Consumers +********* + +This layer is used by: + +* `pyTooling/Actions → PublishTestResults `__ +* 🚧 `pyTooling/Sphinx-Reports `__ + + +.. _CONTRIBUTORS: + +Contributors +************ + +* `Patrick Lehmann `__ (Maintainer) +* `Unai Martinez-Corral `__ +* `and more... `__ + + +.. _LICENSE: + +.. todo:: add license texts here + +.. toctree:: + :hidden: + + Used as a layer of EDA² ➚ + +.. toctree:: + :caption: Introduction + :hidden: + + Installation + Dependency + + +.. toctree:: + :caption: Report Formats + :hidden: + + CodeCoverage/index + DocCoverage/index + Unittesting/index + Logging/index + + +.. #toctree:: + :caption: Tools + :hidden: + + Converting + Merging + + +.. raw:: latex + + \part{References and Reports} + +.. toctree:: + :caption: References and Reports + :hidden: + + CommandLineInterface + pyEDAA.Reports/pyEDAA.Reports + reports/unittests + reports/coverage/index + Doc. Coverage Report + Static Type Check Report ➚ + + +.. raw:: latex + + \part{Appendix} + +.. toctree:: + :caption: Appendix + :hidden: + + License + Doc-License + Glossary + genindex + Python Module Index + TODO + +.. toctree:: + :caption: Old Content + :hidden: + + old/Introduction + old/FunctionalCoverage + old/LineCoverage + old/Resources + old/RichLogging + old/Tracking + old/Frontends diff --git a/_sources/old/Frontends.rst.txt b/_sources/old/Frontends.rst.txt new file mode 100644 index 00000000..6e1b2571 --- /dev/null +++ b/_sources/old/Frontends.rst.txt @@ -0,0 +1,58 @@ +Frontends +========= + +Both GitHub and GitLab do provide features for displaying CI results through their web GUIs. Although they are not rich +enough for displaying all the details, OSVR generators can provide stripped down file formats matching some of the +supported readers; similarly to the JSON and xUnit outputs provided by VUnit. + +.. _API:Logging:OSVRE: + +Open Source Verification Report Explorer +---------------------------------------- + +It would be interesting to have a vendor agnostic tool for visualizing reports locally and/or in self-hosted services. +Since XML, JSON or YAML are used, web technologies (HTML + CSS + JavaScript) feel like a sensible choice. Generating an +static page which can be hosted on GitHub Pages or GitLab Pages allows granular analysis of CI results, while also being +usable locally. There are several simple and not-so-simple solutions available for xUnit files: + +* `w3schools.com/howto/howto_js_treeview `__ +* `lukejpreston.github.io/xunit-viewer `__ +* `Standalone JUnit XML report viewer `__ +* `docs.qameta.io/allure `__ +* `inorton/junit2html `__ + +Similar solutions exist based on unittest or pytest: + +* `xmlrunner/unittest-xml-reporting `__ +* `pytest-reporter `__ + + * `pytest-reporter-html1 `__ + +As a complement, extending `pyucis-viewer `__ might be evaluated, for providing +a Qt based solution. pyucis-viewer currently provides a simple bar-chart viewer for coverage data read via pyucis. + +GitHub +------ + +Although there is no official feature for using the `GitHub Checks `__ +API, there are some community actions for e.g. analysing xUnit files: `publish-unit-test-results `__. There are also multiple bindings in JavaScript, Python or golang for +interacting with GitHub's API. + +GitLab +------ + +Apart from `unit test reports `__, GitLab supports over a dozen +`artifact reports `__. + +Grafana +------- + +On top of visualizing individual reports or sets of reports at one point in time, tracking the evolution of certain +metrics throughout the development of a project can provide valuable insight. GitLab does have a built-in `Prometheus `_ monitoring system and `Grafana `_ can be optionally used as a dashboard: +`docs.gitlab.com: Grafana Dashboard Service `__. Therefore, it +would be useful to send OSVR reports to either Prometheus or some other temporal database (say Graphite, InfluxDB, etc.). + +References +---------- + +* `EDAAC/EDAAC `__: EDA Analytics Central diff --git a/_sources/old/FunctionalCoverage.rst.txt b/_sources/old/FunctionalCoverage.rst.txt new file mode 100644 index 00000000..524804d7 --- /dev/null +++ b/_sources/old/FunctionalCoverage.rst.txt @@ -0,0 +1,69 @@ +Functional coverage +=================== + +* `xUnit `__ (*every unit test is a cover point that has a binary pass|fail*). + VUnit, cocotb, fsva and others can generate xUnit reports of unit testing suites. +* When PSL is used, GHDL can generate a JSON report of cover and assert statements: :option:`--psl-report `. +* OSVVM has an internal coverge database format. +* There is an specification by Accellera, Mentor Graphics and Cadence named Unified Coverage Interoperability Standard (UCIS) and a matching Unified Coverage Database (UCDB). + +xUnit +----- + +VUnit has built-in support for generating `xUnit `__ (XML) reports. In fact, +VUnit's name comes from *VHDL unit testing framework* (see `Wikipedia: List of unit testing frameworks `__). +CLI option ``-x`` allows specifying the target file name. Two different formats are supported: `Jenkins `__ +(`JUnit `__) and `Bamboo `__. JUnit is +also supported on GitLab CI: `docs.gitlab.com: Unit test reports `__. +Python's unittest (and, therefore, pytest) was originally inspired by JUnit, so it has a similar flavor as unit testing +frameworks in other languages. Moreover, there is `junitparser `__, a Python tool +for manipulating xUnit XML files. + +Therefore, by using VUnit's simulator interface and test runner infrastructure, it is already possible to generate fine +grained reports in a standard format. This might be useful for users of OSVVM and/or UVVM, which don't have an +equivalent feature. + +Cocotb can also generate xUnit reports, independently from VUnit. See `docs.cocotb.org: COCOTB_RESULTS_FILE `__. +Precisely, this is related to the duplicated test/regression management features in both frameworks. At the moment, +users are expected to handle them independently when mixed (HDL + cocotb) testsuites are run. However, there is work in +progress for hopefully unifying them automatically (through some post-simulation helper hook). Anyway, while generated +independently, the OSVR core can be used for aggregating them. + +.. NOTE:: In the JUnit XML format, the result of each test is only explicitly provided in case of failure, error or skip. + Therefore, the absence of result indicates a passed test case. + +PSL report +---------- + +As explained in :option:`--psl-report `, "*for each PSL cover and assert statements, the name, source location and whether it passed or failed is reported*" by GHDL in a JSON format. Therefore, it should be trivial +to import these reports in OSVR similarly to how xUnit reports are handled. + +OSVVM +----- + +OSVVM has a non-trivial built-in database format for the advanced functional coverage features provided by +`CoveragePkg `__ (see `OSVVM/Documentation: CoveragePkg_*.pdf `__). There is work in progress with developers of OSVVM for evaluating how +to export it to some standard format, such as xUnit, UCB, or some other XML/JSON/YAML format. + +The main constraint for displaying combined results of multidimensional coverage analysis is that xUnit is expected to have a single level of hierarchy (suites and tests). Hence, unlike previous projects, OSVVM might need some more elaborated format. + +Unified Coverage Database (UCDB) +-------------------------------- + +Unified Coverage Database (UCDB) is one of the components of the Unified Coverage Interoperability Standard (UCIS) +developed by Accellera, Mentor Graphics and Cadence. The UCDB is used by Siemens' tools for tracking results, and they +have a GUI module for browsing them. At first sight, UCDB/UCIS are complex and not easy to work with, however, most of +the potential result types are already covered by the specification (see `Unified Coverage Interoperability Standard Version `__ +and `OSVVM Forums: Cover group and Mentor UCDB `__). +See also `OSVVM Forums: UCIS / UCDB `__. +Fortunately, there is an open source Python package that provides an API to UCIS data (`fvutils/pyucis `__) +as well as an open source Qt based GUI (`fvutils/pyucis-viewer `__). pyucis +can write coverage data in UCIS XML-interchange format and to mentor UCDB via the UCIS library provided by Questa. +Hence, it might be possible to dump results from open source frameworks/methodologies/tools to UCDB for reusing Siemens' +or fvutils' GUIs, or vice versa. + +.. NOTE:: From an open source community perspective, it feels more sensible to dump content from UCDB to an open source + XML/JSON/YAML format specification. However, as far as we are aware, such FLOSS specification adapted to hardware + designs does not exist yet. Moreover, the most used HDL languages are neither open source. Hence, although not ideal, + using UCDB wouldn't be disruptive in this regard. Should you know about any open source alternative, or if you + represent Accelera, Siemens' and/or Cadence and want to open source UCDB/UCIS, please `let us know `__! diff --git a/_sources/old/Introduction.rst.txt b/_sources/old/Introduction.rst.txt new file mode 100644 index 00000000..4a033ff2 --- /dev/null +++ b/_sources/old/Introduction.rst.txt @@ -0,0 +1,27 @@ +Introduction +############ + +While all tools provide feedback by logging into terminal, many of them do also provide results through some report file +format. +However, there is no standard/universal report format which can gather all the diferent types of results that EDA tools +can provide. +pyEDAA.Reports is a proposal for achieving it. + +As shown in the figure below, the main purpose is to allow reusing existing frontends (such as `GitHub Checks `__, +Gitlab `job artifacts `__ and `unit test reports `__, +and/or `Grafana `__) from software testing/verification, rather than reinventing a solution from +scratch. By the same token, we would like to use coverage and test report formats which are compatible with the existing +open source ecosystem used by software people. + +.. figure:: _static/overview.png + :alt: Open Source Verification Report + :align: center + + Sources, core and outputs. + +The most basic functionality is adding a hierarchy level on top of xUnit, for aggregating multiple xUnit reports +corresponding to the same design/project. +That additional hierarchy might be encoded as an additional field in the XML, or by prepending suite names with specific +keywords. +On top of that, some content in hardware project reports need some more elaborated formats. +In the following subsections, each report type is analysed. diff --git a/_sources/old/LineCoverage.rst.txt b/_sources/old/LineCoverage.rst.txt new file mode 100644 index 00000000..217e959a --- /dev/null +++ b/_sources/old/LineCoverage.rst.txt @@ -0,0 +1,8 @@ +Line coverage +============= + +Several open source tools (such as GCC or PyPI's `coverage `__ package) produce +line coverage results in `gcov `__ format. Moreover, utilities exist for generating +reports from gcov files. For instance `gcovr `__ can produce ``html``, ``xml`` +(`Cobertura `__), ``sonarqube`` and ``json``. Furthermore, some HDL tools, such +as GHDL with GCC backend, can generate gcov results too. diff --git a/_sources/old/Resources.rst.txt b/_sources/old/Resources.rst.txt new file mode 100644 index 00000000..7afa88fe --- /dev/null +++ b/_sources/old/Resources.rst.txt @@ -0,0 +1,26 @@ +Synthesis and implementation results +==================================== + +Some synthesis and all implementation tools do provide reports about area/resource usage and estimated maximum clock +frequency. However, most tools do print tables in logs. Parsing them is not complex (see `YosysHQ/arachne-pnr#78 `__), but it needs to be done ad-hoc. Some vendors, such as Vivado, do also +report resource in text logs, but do allow to export them as ``*.xls`` (say CSV) files. + +Edalize supports parsing/reading reports from some EDA tools: + +* ``edalize.reporting`` +* ``edalize.vivado_reporting`` +* ``edalize.quartus_reporting`` +* ``edalize.ise_reporting`` + +`SymbiFlow/fpga-tool-perf `__ does also support extracting results from +Vivado, Yosys, Verilog to Routing and Nextpnr. +Moreover, results are gathered in a Collab Dashboard: `Symbiflow Dashboard GCS `__. + +`mattvenn/logLUTs `__ allows parsing yosys and nextpnr logfiles to then plot LUT, +flip-flop and maximum frequency stats. + +.. NOTE:: + The fields in resource usage reports can be provided as absolute values or relative to the capacity of the device. + Therefore, fields in resource reports of OSVR can and should match the ones in `hdl/constraints: template/device.info.yml `__. + Since both OSVR and the device template in hdl/constraints are subject to change yet, we should make them similar to + existing solutions. diff --git a/_sources/old/RichLogging.rst.txt b/_sources/old/RichLogging.rst.txt new file mode 100644 index 00000000..8b3940f9 --- /dev/null +++ b/_sources/old/RichLogging.rst.txt @@ -0,0 +1,16 @@ +Semantic/rich logging +===================== + +xUnit report files (XML) typically provide the relevant raw log output together with the errored, failed or skipped +result. +However, most verification frameworks, tools and methodologies do have more granular information about each entry. +At least, the severity level is a built-in feature in VHDL, and several projects do provide additional logging utilities +with further severity levels or failure reasons. +For instance, VUnit supports custom logging levels, and can export rich logs to CSV files. +Moreover, `pyIPCMI `__ includes vendor log processing features for classifying and +optionally filtering the logs. +Therefore, it would be interesting to support preserving the semantic information (at least the severity or specific +vendor error/report code), in the extended xUnit report format used in OSVR. +On top of that, `librecores/eda-log-parser `__ supports parsing logs from +Verilator and Vivado, along with generating custom log entries to be used in CI systems/services, such as Azure or +GitHub Actions. diff --git a/_sources/old/Tracking.rst.txt b/_sources/old/Tracking.rst.txt new file mode 100644 index 00000000..7188c800 --- /dev/null +++ b/_sources/old/Tracking.rst.txt @@ -0,0 +1,20 @@ +Tracking requirements +===================== + +Industries developing systems for critical applications do typically require tracking specification requirements through +the developement of the products. +See, for instance, `Using GitLab for ISO 26262-6:2018 - Product development at the software level `__. +Hence, it is very valuable to annotate tests with requirements, and then cross-reference tests and CI runs with those. +In the open source ecosystem, some projects create test cases for each reported MWE through a GitHub/GitLab issue. +Therefore, in such contexts the issue numbers, tags or milestones might be considered requirements to be tracked. + +There is an example by Lars Asplund (from VUnit), for illustrating the usage of VUnit attributes for tracking requirements: +`LarsAsplund/vunit_attributes `__. +It provides requirement to attribute mapping through the ``--export-json`` option, which is a richer format than the +xUnit produced with ``-x``. +In the example, additional analysis features are provided through a requirement coverage analysis script: +`analyze_requirement_coverage.py `__. +The list of requirements is defined in a CSV file. + +Precisely, field ``Metadata`` proposed in the OSVR ``Testcase`` class is expected to contain data such as the attributes. +That is, to integrate VUnit's attribute tracking, with other frameworks which might provide similar features. diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.Dependency.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.Dependency.rst.txt new file mode 100644 index 00000000..f71f493c --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.Dependency.rst.txt @@ -0,0 +1,12 @@ +.. # Template modified by Patrick Lehmann + * removed automodule on top, because private members are activated for autodoc (no doubled documentation). + * Made sections like 'submodules' bold text, but no headlines to reduce number of ToC levels. + +========================= +pyEDAA.Reports.Dependency +========================= + +.. automodule:: pyEDAA.Reports.Dependency + + +.. currentmodule:: pyEDAA.Reports.Dependency diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.DocumentationCoverage.Python.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.DocumentationCoverage.Python.rst.txt new file mode 100644 index 00000000..27f69b79 --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.DocumentationCoverage.Python.rst.txt @@ -0,0 +1,129 @@ +.. # Template modified by Patrick Lehmann + * removed automodule on top, because private members are activated for autodoc (no doubled documentation). + * Made sections like 'submodules' bold text, but no headlines to reduce number of ToC levels. + +=========================================== +pyEDAA.Reports.DocumentationCoverage.Python +=========================================== + +.. automodule:: pyEDAA.Reports.DocumentationCoverage.Python + + +.. currentmodule:: pyEDAA.Reports.DocumentationCoverage.Python + + +**Exceptions** + +- :py:exc:`DocStrCoverageError`: + Common base class for all non-exit exceptions. + + + +**Classes** + +- :py:class:`Coverage`: + This base-class for :class:`ClassCoverage` and :class:`AggregatedCoverage` represents a basic set of documentation coverage metrics. + +- :py:class:`AggregatedCoverage`: + This base-class for :class:`ModuleCoverage` and :class:`PackageCoverage` represents an extended set of documentation coverage metrics, especially with aggregated metrics. + +- :py:class:`ClassCoverage`: + This class represents the class documentation coverage for Python classes. + +- :py:class:`ModuleCoverage`: + This class represents the module documentation coverage for Python modules. + +- :py:class:`PackageCoverage`: + This class represents the package documentation coverage for Python packages. + +- :py:class:`DocStrCoverage`: + A wrapper class for the docstr_coverage package and it's analyzer producing a documentation coverage model. + + + +--------------------- + +**Exceptions** + + + + +.. autoexception:: DocStrCoverageError + + .. rubric:: Inheritance + .. inheritance-diagram:: DocStrCoverageError + :parts: 1 + + +--------------------- + +**Classes** + + + + +.. autoclass:: Coverage + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Coverage + :parts: 1 + +.. autoclass:: AggregatedCoverage + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: AggregatedCoverage + :parts: 1 + +.. autoclass:: ClassCoverage + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: ClassCoverage + :parts: 1 + +.. autoclass:: ModuleCoverage + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: ModuleCoverage + :parts: 1 + +.. autoclass:: PackageCoverage + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: PackageCoverage + :parts: 1 + +.. autoclass:: DocStrCoverage + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: DocStrCoverage + :parts: 1 diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.DocumentationCoverage.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.DocumentationCoverage.rst.txt new file mode 100644 index 00000000..f59b1fdd --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.DocumentationCoverage.rst.txt @@ -0,0 +1,151 @@ +.. # Template modified by Patrick Lehmann + * removed automodule on top, because private members are activated for autodoc (no doubled documentation). + * Made sections like 'submodules' bold text, but no headlines to reduce number of ToC levels. + +==================================== +pyEDAA.Reports.DocumentationCoverage +==================================== + +.. automodule:: pyEDAA.Reports.DocumentationCoverage + + + +**Submodules** + +.. toctree:: + :maxdepth: 1 + + pyEDAA.Reports.DocumentationCoverage.Python + +.. currentmodule:: pyEDAA.Reports.DocumentationCoverage + + +**Exceptions** + +- :py:exc:`DocCoverageException`: + Common base class for all non-exit exceptions. + + + +**Classes** + +- :py:class:`CoverageState`: + Support for flags + +- :py:class:`Base`: + Undocumented. + +- :py:class:`_Type`: + Undocumented. + +- :py:class:`Class`: + Undocumented. + +- :py:class:`_Unit`: + Undocumented. + +- :py:class:`Module`: + Undocumented. + +- :py:class:`Package`: + Undocumented. + + + +--------------------- + +**Exceptions** + + + + +.. autoexception:: DocCoverageException + + .. rubric:: Inheritance + .. inheritance-diagram:: DocCoverageException + :parts: 1 + + +--------------------- + +**Classes** + + + + +.. autoclass:: CoverageState + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: CoverageState + :parts: 1 + +.. autoclass:: Base + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Base + :parts: 1 + +.. autoclass:: _Type + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: _Type + :parts: 1 + +.. autoclass:: Class + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Class + :parts: 1 + +.. autoclass:: _Unit + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: _Unit + :parts: 1 + +.. autoclass:: Module + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Module + :parts: 1 + +.. autoclass:: Package + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Package + :parts: 1 diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.OSVVM.AlertLog.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.OSVVM.AlertLog.rst.txt new file mode 100644 index 00000000..6701c027 --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.OSVVM.AlertLog.rst.txt @@ -0,0 +1,11 @@ +.. # Template created by Patrick Lehmann + +Python Class Reference +###################### + +Reference of all packages and modules: + +.. automodule:: pyEDAA.Reports.OSVVM.AlertLog + +.. toctree:: + :maxdepth: 1 diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.OSVVM.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.OSVVM.rst.txt new file mode 100644 index 00000000..bdd444d0 --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.OSVVM.rst.txt @@ -0,0 +1,13 @@ +.. # Template created by Patrick Lehmann + +Python Class Reference +###################### + +Reference of all packages and modules: + +.. automodule:: pyEDAA.Reports.OSVVM + +.. toctree:: + :maxdepth: 1 + + pyEDAA.Reports.OSVVM.AlertLog \ No newline at end of file diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.Requirement.Python.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.Requirement.Python.rst.txt new file mode 100644 index 00000000..acaae6d1 --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.Requirement.Python.rst.txt @@ -0,0 +1,52 @@ +.. # Template modified by Patrick Lehmann + * removed automodule on top, because private members are activated for autodoc (no doubled documentation). + * Made sections like 'submodules' bold text, but no headlines to reduce number of ToC levels. + +================================= +pyEDAA.Reports.Requirement.Python +================================= + +.. automodule:: pyEDAA.Reports.Requirement.Python + + +.. currentmodule:: pyEDAA.Reports.Requirement.Python + + +**Classes** + +- :py:class:`Requirement`: + Undocumented. + +- :py:class:`RequirementsFile`: + Undocumented. + + + +--------------------- + +**Classes** + + + + +.. autoclass:: Requirement + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Requirement + :parts: 1 + +.. autoclass:: RequirementsFile + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: RequirementsFile + :parts: 1 diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.Requirement.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.Requirement.rst.txt new file mode 100644 index 00000000..bdb4d88c --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.Requirement.rst.txt @@ -0,0 +1,20 @@ +.. # Template modified by Patrick Lehmann + * removed automodule on top, because private members are activated for autodoc (no doubled documentation). + * Made sections like 'submodules' bold text, but no headlines to reduce number of ToC levels. + +========================== +pyEDAA.Reports.Requirement +========================== + +.. automodule:: pyEDAA.Reports.Requirement + + + +**Submodules** + +.. toctree:: + :maxdepth: 1 + + pyEDAA.Reports.Requirement.Python + +.. currentmodule:: pyEDAA.Reports.Requirement diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.Resources.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.Resources.rst.txt new file mode 100644 index 00000000..b18d45b7 --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.Resources.rst.txt @@ -0,0 +1,12 @@ +.. # Template modified by Patrick Lehmann + * removed automodule on top, because private members are activated for autodoc (no doubled documentation). + * Made sections like 'submodules' bold text, but no headlines to reduce number of ToC levels. + +======================== +pyEDAA.Reports.Resources +======================== + +.. automodule:: pyEDAA.Reports.Resources + + +.. currentmodule:: pyEDAA.Reports.Resources diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.Unittesting.JUnit.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.Unittesting.JUnit.rst.txt new file mode 100644 index 00000000..945467f7 --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.Unittesting.JUnit.rst.txt @@ -0,0 +1,212 @@ +.. # Template modified by Patrick Lehmann + * removed automodule on top, because private members are activated for autodoc (no doubled documentation). + * Made sections like 'submodules' bold text, but no headlines to reduce number of ToC levels. + +================================ +pyEDAA.Reports.Unittesting.JUnit +================================ + +.. automodule:: pyEDAA.Reports.Unittesting.JUnit + + +.. currentmodule:: pyEDAA.Reports.Unittesting.JUnit + + +**Exceptions** + +- :py:exc:`UnittestException`: + Base-exception for all unit test related exceptions. + +- :py:exc:`AlreadyInHierarchyException`: + A unit test exception raised if the element is already part of a hierarchy. + +- :py:exc:`DuplicateTestsuiteException`: + A unit test exception raised on duplicate test suites (by name). + +- :py:exc:`DuplicateTestcaseException`: + A unit test exception raised on duplicate test cases (by name). + + + +**Classes** + +- :py:class:`JUnitException`: + An exception-mixin for JUnit format specific exceptions. + +- :py:class:`JUnitReaderMode`: + Support for flags + +- :py:class:`Base`: + Base-class for all test entities (test cases, test classes, test suites, ...). + +- :py:class:`BaseWithProperties`: + Base-class for all test entities supporting properties (test cases, test suites, ...). + +- :py:class:`Testcase`: + A testcase is the leaf-entity in the test entity hierarchy representing an individual test run. + +- :py:class:`TestsuiteBase`: + Base-class for all test suites and for test summaries. + +- :py:class:`Testclass`: + A test class is a low-level element in the test entity hierarchy representing a group of tests. + +- :py:class:`Testsuite`: + A testsuite is a mid-level element in the test entity hierarchy representing a logical group of tests. + +- :py:class:`TestsuiteSummary`: + Base-class for all test suites and for test summaries. + +- :py:class:`Document`: + Base-class for all test suites and for test summaries. + + + +--------------------- + +**Exceptions** + + + + +.. autoexception:: UnittestException + + .. rubric:: Inheritance + .. inheritance-diagram:: UnittestException + :parts: 1 + +.. autoexception:: AlreadyInHierarchyException + + .. rubric:: Inheritance + .. inheritance-diagram:: AlreadyInHierarchyException + :parts: 1 + +.. autoexception:: DuplicateTestsuiteException + + .. rubric:: Inheritance + .. inheritance-diagram:: DuplicateTestsuiteException + :parts: 1 + +.. autoexception:: DuplicateTestcaseException + + .. rubric:: Inheritance + .. inheritance-diagram:: DuplicateTestcaseException + :parts: 1 + + +--------------------- + +**Classes** + + + + +.. autoclass:: JUnitException + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: JUnitException + :parts: 1 + +.. autoclass:: JUnitReaderMode + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: JUnitReaderMode + :parts: 1 + +.. autoclass:: Base + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Base + :parts: 1 + +.. autoclass:: BaseWithProperties + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: BaseWithProperties + :parts: 1 + +.. autoclass:: Testcase + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Testcase + :parts: 1 + +.. autoclass:: TestsuiteBase + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: TestsuiteBase + :parts: 1 + +.. autoclass:: Testclass + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Testclass + :parts: 1 + +.. autoclass:: Testsuite + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Testsuite + :parts: 1 + +.. autoclass:: TestsuiteSummary + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: TestsuiteSummary + :parts: 1 + +.. autoclass:: Document + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Document + :parts: 1 diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.Unittesting.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.Unittesting.rst.txt new file mode 100644 index 00000000..ad824d41 --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.Unittesting.rst.txt @@ -0,0 +1,290 @@ +.. # Template modified by Patrick Lehmann + * removed automodule on top, because private members are activated for autodoc (no doubled documentation). + * Made sections like 'submodules' bold text, but no headlines to reduce number of ToC levels. + +========================== +pyEDAA.Reports.Unittesting +========================== + +.. automodule:: pyEDAA.Reports.Unittesting + + + +**Submodules** + +.. toctree:: + :maxdepth: 1 + + pyEDAA.Reports.Unittesting.JUnit + +.. currentmodule:: pyEDAA.Reports.Unittesting + + +**Exceptions** + +- :py:exc:`UnittestException`: + Base-exception for all unit test related exceptions. + +- :py:exc:`AlreadyInHierarchyException`: + A unit test exception raised if the element is already part of a hierarchy. + +- :py:exc:`DuplicateTestsuiteException`: + A unit test exception raised on duplicate test suites (by name). + +- :py:exc:`DuplicateTestcaseException`: + A unit test exception raised on duplicate test cases (by name). + + + +**Classes** + +- :py:class:`TestcaseStatus`: + A flag enumeration describing the status of a test case. + +- :py:class:`TestsuiteStatus`: + A flag enumeration describing the status of a test suite. + +- :py:class:`TestsuiteKind`: + Enumeration describing the kind of test suite. + +- :py:class:`IterationScheme`: + A flag enumeration for selecting the test suite iteration scheme. + +- :py:class:`Base`: + Base-class for all test entities (test cases, test suites, ...). + +- :py:class:`Testcase`: + A testcase is the leaf-entity in the test entity hierarchy representing an individual test run. + +- :py:class:`TestsuiteBase`: + Base-class for all test suites and for test summaries. + +- :py:class:`Testsuite`: + A testsuite is a mid-level element in the test entity hierarchy representing a group of tests. + +- :py:class:`TestsuiteSummary`: + A testsuite summary is the root element in the test entity hierarchy representing a summary of all test suites and cases. + +- :py:class:`Document`: + A mixin-class representing a unit test summary document (file). + +- :py:class:`Merged`: + A mixin-class representing a merged test entity. + +- :py:class:`Combined`: + Undocumented. + +- :py:class:`MergedTestcase`: + A testcase is the leaf-entity in the test entity hierarchy representing an individual test run. + +- :py:class:`MergedTestsuite`: + A testsuite is a mid-level element in the test entity hierarchy representing a group of tests. + +- :py:class:`MergedTestsuiteSummary`: + A testsuite summary is the root element in the test entity hierarchy representing a summary of all test suites and cases. + + + +--------------------- + +**Exceptions** + + + + +.. autoexception:: UnittestException + + .. rubric:: Inheritance + .. inheritance-diagram:: UnittestException + :parts: 1 + +.. autoexception:: AlreadyInHierarchyException + + .. rubric:: Inheritance + .. inheritance-diagram:: AlreadyInHierarchyException + :parts: 1 + +.. autoexception:: DuplicateTestsuiteException + + .. rubric:: Inheritance + .. inheritance-diagram:: DuplicateTestsuiteException + :parts: 1 + +.. autoexception:: DuplicateTestcaseException + + .. rubric:: Inheritance + .. inheritance-diagram:: DuplicateTestcaseException + :parts: 1 + + +--------------------- + +**Classes** + + + + +.. autoclass:: TestcaseStatus + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: TestcaseStatus + :parts: 1 + +.. autoclass:: TestsuiteStatus + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: TestsuiteStatus + :parts: 1 + +.. autoclass:: TestsuiteKind + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: TestsuiteKind + :parts: 1 + +.. autoclass:: IterationScheme + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: IterationScheme + :parts: 1 + +.. autoclass:: Base + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Base + :parts: 1 + +.. autoclass:: Testcase + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Testcase + :parts: 1 + +.. autoclass:: TestsuiteBase + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: TestsuiteBase + :parts: 1 + +.. autoclass:: Testsuite + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Testsuite + :parts: 1 + +.. autoclass:: TestsuiteSummary + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: TestsuiteSummary + :parts: 1 + +.. autoclass:: Document + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Document + :parts: 1 + +.. autoclass:: Merged + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Merged + :parts: 1 + +.. autoclass:: Combined + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: Combined + :parts: 1 + +.. autoclass:: MergedTestcase + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: MergedTestcase + :parts: 1 + +.. autoclass:: MergedTestsuite + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: MergedTestsuite + :parts: 1 + +.. autoclass:: MergedTestsuiteSummary + :members: + :private-members: + :special-members: + :inherited-members: + :exclude-members: __weakref__ + + .. rubric:: Inheritance + .. inheritance-diagram:: MergedTestsuiteSummary + :parts: 1 diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.helper.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.helper.rst.txt new file mode 100644 index 00000000..3e57778e --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.helper.rst.txt @@ -0,0 +1,11 @@ +.. # Template created by Patrick Lehmann + +Python Class Reference +###################### + +Reference of all packages and modules: + +.. automodule:: pyEDAA.Reports.helper + +.. toctree:: + :maxdepth: 1 diff --git a/_sources/pyEDAA.Reports/pyEDAA.Reports.rst.txt b/_sources/pyEDAA.Reports/pyEDAA.Reports.rst.txt new file mode 100644 index 00000000..04673961 --- /dev/null +++ b/_sources/pyEDAA.Reports/pyEDAA.Reports.rst.txt @@ -0,0 +1,19 @@ +.. # Template created by Patrick Lehmann + +Python Class Reference +###################### + +Reference of all packages and modules: + +.. automodule:: pyEDAA.Reports + +.. toctree:: + :maxdepth: 1 + + pyEDAA.Reports.Dependency + pyEDAA.Reports.DocumentationCoverage + pyEDAA.Reports.OSVVM + pyEDAA.Reports.Requirement + pyEDAA.Reports.Resources + pyEDAA.Reports.Unittesting + pyEDAA.Reports.helper \ No newline at end of file diff --git a/_sources/reports/coverage/index.rst.txt b/_sources/reports/coverage/index.rst.txt new file mode 100644 index 00000000..e50cb57a --- /dev/null +++ b/_sources/reports/coverage/index.rst.txt @@ -0,0 +1,8 @@ +Code Coverage Report +#################### + +Code coverage report generated with `pytest `__ and `Coverage.py `__\ . + + +.. report:code-coverage:: + :packageid: src diff --git a/_sources/reports/doccoverage.rst.txt b/_sources/reports/doccoverage.rst.txt new file mode 100644 index 00000000..eadecad6 --- /dev/null +++ b/_sources/reports/doccoverage.rst.txt @@ -0,0 +1,7 @@ +Documentation Coverage +###################### + +Documentation coverage generated by `docstr-coverage `__\ . + +.. report:doc-coverage:: + :packageid: src diff --git a/_sources/reports/typing/index.rst.txt b/_sources/reports/typing/index.rst.txt new file mode 100644 index 00000000..3a22a255 --- /dev/null +++ b/_sources/reports/typing/index.rst.txt @@ -0,0 +1,8 @@ +Static Type Checking Report +########################### + +*Placeholder for the Static Type Checking report generated with* ``mypy``. + +.. #raw:: html + +