├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .pre-commit-config.yaml ├── AUTHORS ├── CHANGES.rst ├── LICENSE ├── README.rst ├── development.rst ├── pyproject.toml ├── src └── pytest_metadata │ ├── __init__.py │ ├── ci │ ├── __init__.py │ ├── appveyor.py │ ├── bitbucket.py │ ├── circleci.py │ ├── codebuild.py │ ├── gitlab_ci.py │ ├── jenkins.py │ ├── taskcluster.py │ └── travis_ci.py │ ├── hooks.py │ └── plugin.py ├── tests ├── __init__.py └── test_metadata.py └── tox.ini /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | tags: 6 | - "[0-9]+.[0-9]+.[0-9]+" 7 | - "[0-9]+.[0-9]+.[0-9]+rc[0-9]+" 8 | 9 | jobs: 10 | publish: 11 | if: github.repository == 'pytest-dev/pytest-metadata' 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | persist-credentials: false 18 | 19 | - name: Build and Check Package 20 | uses: hynek/build-and-inspect-python-package@v2 21 | 22 | - name: Download Package 23 | uses: actions/download-artifact@v4 24 | with: 25 | name: Packages 26 | path: dist 27 | 28 | - name: Publish package to PyPI 29 | uses: pypa/gh-action-pypi-publish@release/v1 30 | with: 31 | password: ${{ secrets.PYPI_TOKEN }} 32 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | schedule: 9 | - cron: '0 16 * * *' # Run daily at 14:00 UTC 10 | 11 | pull_request: 12 | 13 | concurrency: 14 | group: ${{ github.workflow }}-${{ github.ref }} 15 | cancel-in-progress: ${{ ! contains(github.ref, github.event.repository.default_branch) }} 16 | 17 | jobs: 18 | test: 19 | name: ${{ matrix.tox-env }} 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: false 23 | matrix: 24 | tox-env: ["3.8", "3.9", "3.10", "3.11", "3.12", "pypy3.10", "devel"] 25 | 26 | steps: 27 | - uses: actions/checkout@v4 28 | 29 | - name: Set up python 30 | uses: actions/setup-python@v5 31 | with: 32 | python-version: ${{ matrix.tox-env == 'devel' && 3.12 || matrix.tox-env }} # default is for devel 33 | 34 | - name: Install tox 35 | run: | 36 | python -m pip install --upgrade pip 37 | pip install tox 38 | 39 | - name: Cache tox environments 40 | uses: actions/cache@v4 41 | with: 42 | path: .tox 43 | key: ubuntu-latest-tox-${{ matrix.tox-env }}-${{ hashFiles('pyproject.toml', 'tox.ini') }} 44 | restore-keys: | 45 | ubuntu-latest-tox-${{ matrix.tox-env }}- 46 | 47 | - name: Run tests 48 | run: tox -e ${{ matrix.tox-env }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | __version.py 3 | *.egg-info/ 4 | *.pyc 5 | .cache 6 | .eggs 7 | .tox 8 | 9 | build 10 | dist 11 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/psf/black 3 | rev: 24.1.1 4 | hooks: 5 | - id: black 6 | args: [--safe, --quiet] 7 | language_version: python3 8 | 9 | - repo: https://github.com/pre-commit/pre-commit-hooks 10 | rev: v4.5.0 11 | hooks: 12 | - id: trailing-whitespace 13 | - id: end-of-file-fixer 14 | 15 | - repo: https://github.com/PyCQA/flake8 16 | rev: 7.0.0 17 | hooks: 18 | - id: flake8 19 | exclude: docs 20 | language_version: python3 21 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Agustín Herranz 2 | Arminius (numirias) 3 | Dave Hunt 4 | Imran Mumtaz 5 | Jim Brännlund 6 | Sam Clements 7 | Tomas Krizek 8 | Zac Hatfield-Dodds 9 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Release Notes 2 | ------------- 3 | 4 | 3.1.1 (2024-02-08) 5 | ------------------ 6 | 7 | * Add environment variables for AWS CodeBuild CI. 8 | 9 | * Thanks to `@dougch `_ for the PR. 10 | 11 | 3.1.0 (2024-02-01) 12 | ------------------ 13 | 14 | * Support Pytest 8.0 15 | 16 | 3.0.0 (2023-05-27) 17 | ------------------ 18 | 19 | * Group CLI options 20 | 21 | * Thanks to `@xmuskrat `_ for reporting. 22 | 23 | * Switch to Hatch 24 | 25 | * Use `pytest.stash` internally instead of `_metadata` 26 | 27 | * Simplify code 28 | 29 | * Thanks to `@LieYangGH `_ for the PR. 30 | 31 | 2.0.4 (2022-10-30) 32 | ------------------ 33 | 34 | * Fix deprecated `pytest.mark.optionalhook` marker. 35 | 36 | 2.0.3 (2022-10-26) 37 | ------------------ 38 | 39 | * Remove `py` dependency 40 | 41 | * Thanks to `@Czaki `_ for reporting. 42 | 43 | 2.0.2 (2022-07-15) 44 | ------------------ 45 | 46 | * Allow all python versions above 3.7 47 | 48 | 2.0.1 (2022-03-26) 49 | ------------------ 50 | 51 | * Fix issues with switching to pyproject.toml 52 | 53 | * Thanks to `@dvzrv `_ and `@eltrufas `_ for raising issues and providing fixes. 54 | 55 | 2.0.0 (2022-03-22) 56 | ------------------ 57 | 58 | * Drop support for python 2.7 and 3.6 59 | 60 | * Add support for python 3.9 and 3.10 61 | 62 | * Introduce pyproject.toml 63 | 64 | * Provide metadata via JSON file 65 | 66 | * Thanks to `@digitalorder `_ for the PR 67 | 68 | 1.11.0 (2020-11-72) 69 | ------------------- 70 | 71 | * Provide a session fixture to include metadata in Junit XMLs as property tags. 72 | 73 | * Thanks to `@sanga `_ for the PR 74 | 75 | 1.10.0 (2020-06-24) 76 | ------------------ 77 | 78 | * Compatible with ``pytest-xdist`` 1.22.3+, now including 2.0+ 79 | 80 | * Thanks to `@Zac-HD `_ for the PR 81 | 82 | 1.9.0 (2020-04-03) 83 | ------------------ 84 | 85 | * Add ``--metadata-from-json`` argument to support passing metadata as json. 86 | 87 | * Thanks to `@ImXron `_ for the PR 88 | 89 | * Add support for python 3.8. 90 | 91 | * Thanks to `@hugovk `_ for the PR 92 | 93 | * Remove always-masked GitLab environment variables. 94 | 95 | * Thanks to `@borntyping `_ for the PR 96 | 97 | 1.8.0 (2019-01-03) 98 | ------------------ 99 | 100 | * Add environment variables for Bitbucket pipelines (CI). 101 | 102 | 1.7.0 (2018-04-05) 103 | ------------------ 104 | 105 | * Add hook for modifying metadata. 106 | 107 | * Thanks to `@j19sch `_ for the PR 108 | 109 | 1.6.0 (2018-02-21) 110 | ------------------ 111 | 112 | * Only show metadata in console when ``--verbose`` is specified. 113 | 114 | 1.5.1 (2017-11-28) 115 | ------------------ 116 | 117 | * Support latest versions of pytest, which no longer vendor pluggy. 118 | 119 | 1.5.0 (2017-05-15) 120 | ------------------ 121 | 122 | * Add environment variables for GitLab CI. 123 | 124 | * Thanks to `@tinproject `_ for the PR 125 | 126 | 1.4.0 (2017-05-04) 127 | ------------------ 128 | 129 | * Allow additional metadata to be specified on the command-line. 130 | 131 | * Thanks to `@BeyondEvil `_ for the PR 132 | 133 | 1.3.0 (2017-03-01) 134 | ------------------ 135 | 136 | * Display initial metadata in report header. 137 | * Update metadata when running with xdist processes instead of overwriting. 138 | 139 | 1.2.0 (2017-02-24) 140 | ------------------ 141 | 142 | * Added environment variables for AppVeyor and CircleCI. 143 | * Try to run ``pytest_configure`` first so that other plugins can contribute to 144 | the metadata. 145 | 146 | 1.1.0 (2017-02-16) 147 | ------------------ 148 | 149 | * Moved pytest related packages into 'Packages' as a dictionary. 150 | * Changed installed plugins from a list to a dictionary. 151 | 152 | 1.0.0 (2017-02-16) 153 | ------------------ 154 | 155 | * Initial release 156 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This Source Code Form is subject to the terms of the Mozilla Public 2 | License, v. 2.0. If a copy of the MPL was not distributed with this 3 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | pytest-metadata 2 | =============== 3 | 4 | pytest-metadata is a plugin for `pytest `_ that provides 5 | access to test session metadata. 6 | 7 | .. image:: https://img.shields.io/badge/license-MPL%202.0-blue.svg 8 | :target: https://github.com/pytest-dev/pytest-metadata/blob/master/LICENSE 9 | :alt: License 10 | .. image:: https://img.shields.io/pypi/v/pytest-metadata.svg 11 | :target: https://pypi.python.org/pypi/pytest-metadata/ 12 | :alt: PyPI 13 | .. image:: https://img.shields.io/travis/pytest-dev/pytest-metadata.svg 14 | :target: https://travis-ci.org/pytest-dev/pytest-metadata/ 15 | .. image:: https://img.shields.io/badge/code%20style-black-000000.svg 16 | :target: https://github.com/ambv/black 17 | :alt: Travis 18 | .. image:: https://img.shields.io/github/issues-raw/pytest-dev/pytest-metadata.svg 19 | :target: https://github.com/pytest-dev/pytest-metadata/issues 20 | :alt: Issues 21 | .. image:: https://img.shields.io/requires/github/pytest-dev/pytest-metadata.svg 22 | :target: https://requires.io/github/pytest-dev/pytest-metadata/requirements/?branch=master 23 | :alt: Requirements 24 | 25 | Requirements 26 | ------------ 27 | 28 | You will need the following in order to use pytest-metadata: 29 | 30 | - Python 3.8+ or PyPy3 31 | 32 | Installation 33 | ------------ 34 | 35 | To install pytest-metadata: 36 | 37 | .. code-block:: bash 38 | 39 | $ pip install pytest-metadata 40 | 41 | Contributing 42 | ------------ 43 | 44 | We welcome contributions. 45 | 46 | To learn more, see `Development `_ 47 | 48 | Available metadata 49 | ------------------ 50 | 51 | The following metadata is gathered by this plugin: 52 | 53 | ======== =============== =================================== 54 | Key Description Example 55 | ======== =============== =================================== 56 | Python Python version 3.6.4 57 | Platform Platform Darwin-17.4.0-x86_64-i386-64bit 58 | Packages pytest packages {'py': '1.5.2', 'pytest': '3.4.1'} 59 | Plugins pytest plugins {'metadata': '1.6.0'} 60 | ======== =============== =================================== 61 | 62 | Additional metadata 63 | ------------------- 64 | 65 | You can provide your own metadata (key, value pair) by specifying ``--metadata`` on the commandline:: 66 | 67 | pytest --metadata foo bar 68 | 69 | Note: You can provide multiple sets of ``--metadata``:: 70 | 71 | pytest --metadata foo bar --metadata baz zoo 72 | 73 | There's also the possibility of passing in metadata as a JSON string:: 74 | 75 | pytest --metadata-from-json '{"cat_says": "bring the cat nip", "human_says": "yes kitty"}' 76 | 77 | Alternatively a JSON can be read from a given file:: 78 | 79 | pytest --metadata-from-json-file path/to/valid/file.json 80 | 81 | Continuous integration 82 | ---------------------- 83 | 84 | When run in a continuous integration environment, additional metadata is added 85 | from environment variables. Below is a list of the supported continuous 86 | integration providers, along with links to the environment variables that are 87 | added to metadata if they're present. 88 | 89 | * `AppVeyor `_ 90 | * `Bitbucket `_ 91 | * `CircleCI `_ 92 | * `GitLab CI `_ 93 | * `Jenkins `_ 94 | * `TaskCluster `_ 95 | * `Travis CI `_ 96 | 97 | Note that if you're using `Tox `_ to run your tests 98 | then you will need to `pass down any additional environment variables `_ 99 | for these to be picked up. 100 | 101 | Viewing metadata 102 | ---------------- 103 | 104 | If you pass ``--verbose`` on the command line when running your tests, then the 105 | metadata will be displayed in the terminal report header:: 106 | 107 | pytest --verbose 108 | ============================ test session starts ============================ 109 | platform darwin -- Python 3.6.4, pytest-3.4.1, py-1.5.2, pluggy-0.6.0 -- /usr/bin/python 110 | cachedir: .pytest_cache 111 | metadata: {'Python': '3.6.4', 'Platform': 'Darwin-17.4.0-x86_64-i386-64bit', 'Packages': {'pytest': '3.4.1', 'py': '1.5.2', 'pluggy': '0.6.0'}, 'Plugins': {'metadata': '1.6.0'}} 112 | plugins: metadata-1.6.0 113 | 114 | Including metadata in Junit XML 115 | ------------------------------- 116 | 117 | Pytest-metadata provides the session scoped fixture :code:`include_metadata_in_junit_xml` that you may use to include any metadata in Junit XML as ``property`` tags. 118 | For example the following test module 119 | 120 | .. code-block:: python 121 | 122 | import pytest 123 | 124 | pytestmark = pytest.mark.usefixtures('include_metadata_in_junit_xml') 125 | 126 | def test(): 127 | pass 128 | 129 | when called with 130 | 131 | .. code-block:: bash 132 | 133 | pytest --metadata Daffy Duck --junit-xml=results.xml 134 | 135 | would produce the following XML 136 | 137 | .. code-block:: xml 138 | 139 | 140 | 141 | 142 | 143 | 144 | ... 145 | 146 | Accessing metadata 147 | ------------------ 148 | 149 | To add/modify/delete metadata at the end of metadata collection, you can use the ``pytest_metadata`` hook: 150 | 151 | .. code-block:: python 152 | 153 | import pytest 154 | @pytest.hookimpl(optionalhook=True) 155 | def pytest_metadata(metadata): 156 | metadata.pop("password", None) 157 | 158 | To access the metadata from a test or fixture, you can use the ``metadata`` 159 | fixture: 160 | 161 | .. code-block:: python 162 | 163 | def test_metadata(metadata): 164 | assert 'metadata' in metadata['Plugins'] 165 | 166 | To access the metadata from a plugin, you can use the ``stash`` attribute of 167 | the ``config`` object. This can be used to read/add/modify the metadata: 168 | 169 | .. code-block:: python 170 | 171 | def pytest_configure(config): 172 | metadata = config.pluginmanager.getplugin("metadata") 173 | if metadata: 174 | from pytest_metadata.plugin import metadata_key 175 | config.stash[metadata_key]['foo'] = 'bar' 176 | 177 | Plugin integrations 178 | ------------------- 179 | 180 | Here's a handy list of plugins that either read or contribute to the metadata: 181 | 182 | * `pytest-base-url `_ - Adds the 183 | base URL to the metadata. 184 | * `pytest-html `_ - Displays the 185 | metadata at the start of each report. 186 | * `pytest-reporter-html1 `_ - 187 | Presents metadata as part of the report. 188 | * `pytest-selenium `_ - Adds the 189 | driver, capabilities, and remote server to the metadata. 190 | 191 | Resources 192 | --------- 193 | 194 | - `Release Notes `_ 195 | - `Issue Tracker `_ 196 | - `Code `_ 197 | -------------------------------------------------------------------------------- /development.rst: -------------------------------------------------------------------------------- 1 | Development 2 | =========== 3 | 4 | To contribute to `pytest-metadata` you can use `Hatch `_ to manage 5 | a python virtual environment and `pre-commit `_ to help you with 6 | styling and formatting. 7 | 8 | To setup the virtual environment and pre-commit, run: 9 | 10 | .. code-block:: bash 11 | 12 | $ hatch -e test run pre-commit install 13 | 14 | If you're not using ``Hatch``, to install ``pre-commit``, run: 15 | 16 | .. code-block:: bash 17 | 18 | $ pip install pre-commit 19 | $ pre-commit install 20 | 21 | 22 | Automated Testing 23 | ----------------- 24 | 25 | All pull requests and merges are tested in `GitHub Actions `_ 26 | based on the workflows defined in ``.github/workflows``. 27 | 28 | Running Tests 29 | ------------- 30 | 31 | You will need `Tox `_ installed to run the tests 32 | against the supported Python versions. If you're using ``Hatch`` it will be 33 | installed for you. 34 | 35 | With ``Hatch``, run: 36 | 37 | .. code-block:: bash 38 | 39 | $ hatch -e test run tox 40 | 41 | Otherwise, to install and run, do: 42 | 43 | .. code-block:: bash 44 | 45 | $ pip install tox 46 | $ tox 47 | 48 | Releasing a new version 49 | ----------------------- 50 | 51 | Follow these steps to release a new version of the project: 52 | 53 | #. Update your local master with the upstream master (``git pull --rebase upstream master``) 54 | #. Create a new branch and update ``CHANGES.rst`` with the new version, today's date, and all changes/new features 55 | #. Update ``pyproject.toml`` with the new version 56 | #. Commit and push the new branch and then create a new pull request 57 | #. Wait for tests and reviews and then merge the branch 58 | #. Once merged, update your local master again (``git pull --rebase upstream master``) 59 | #. Tag the release with the new release version (``git tag ``) 60 | #. Push the tag (``git push upstream --tags``) 61 | #. Done. 62 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | build-backend = "hatchling.build" 3 | requires = [ 4 | "hatch-vcs>=0.3", 5 | "hatchling>=1.13", 6 | ] 7 | 8 | [project] 9 | name = "pytest-metadata" 10 | description = "pytest plugin for test session metadata" 11 | readme = "README.rst" 12 | license = "MPL-2.0" 13 | requires-python = ">=3.8" 14 | keywords = [ 15 | "pytest", 16 | "metadata", 17 | ] 18 | authors = [ 19 | { name = "Dave Hunt", email = "dhunt@mozilla.com" }, 20 | { name = "Jim Brannlund", email = "jimbrannlund@fastmail.com" }, 21 | ] 22 | classifiers = [ 23 | "Development Status :: 5 - Production/Stable", 24 | "Framework :: Pytest", 25 | "Intended Audience :: Developers", 26 | "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", 27 | "Natural Language :: English", 28 | "Operating System :: POSIX", 29 | "Operating System :: Microsoft :: Windows", 30 | "Operating System :: MacOS :: MacOS X", 31 | "Programming Language :: Python :: 3.8", 32 | "Programming Language :: Python :: 3.9", 33 | "Programming Language :: Python :: 3.10", 34 | "Programming Language :: Python :: 3.11", 35 | "Programming Language :: Python :: 3.12", 36 | "Programming Language :: Python :: Implementation :: CPython", 37 | "Programming Language :: Python :: Implementation :: PyPy", 38 | "Topic :: Software Development :: Quality Assurance", 39 | "Topic :: Software Development :: Testing", 40 | "Topic :: Utilities", 41 | ] 42 | dependencies = [ 43 | "pytest>=7.0.0", 44 | ] 45 | dynamic = [ 46 | "version", 47 | ] 48 | 49 | [project.optional-dependencies] 50 | test = [ 51 | "black>=22.1.0", 52 | "flake8>=4.0.1", 53 | "pre-commit>=2.17.0", 54 | "tox>=3.24.5", 55 | ] 56 | 57 | [project.urls] 58 | Homepage = "https://github.com/pytest-dev/pytest-metadata" 59 | Tracker = "https://github.com/pytest-dev/pytest-metadata/issues" 60 | Source = "https://github.com/pytest-dev/pytest-metadata" 61 | 62 | [project.entry-points.pytest11] 63 | metadata = 'pytest_metadata.plugin' 64 | 65 | [tool.hatch.envs.test] 66 | features = [ 67 | "test", 68 | ] 69 | 70 | [tool.hatch.version] 71 | source = "vcs" 72 | 73 | [tool.hatch.build.targets.sdist] 74 | exclude = [ 75 | "/.github", 76 | ] 77 | 78 | [tool.hatch.build.hooks.vcs] 79 | version-file = "src/pytest_metadata/__version.py" 80 | -------------------------------------------------------------------------------- /src/pytest_metadata/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pytest-dev/pytest-metadata/16b39d2b8ec7500d566ff8b45576ea866d2c719c/src/pytest_metadata/__init__.py -------------------------------------------------------------------------------- /src/pytest_metadata/ci/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pytest-dev/pytest-metadata/16b39d2b8ec7500d566ff8b45576ea866d2c719c/src/pytest_metadata/ci/__init__.py -------------------------------------------------------------------------------- /src/pytest_metadata/ci/appveyor.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | ENVIRONMENT_VARIABLES = [ 6 | "APPVEYOR_API_URL", 7 | "APPVEYOR_ACCOUNT_NAME", 8 | "APPVEYOR_PROJECT_ID", 9 | "APPVEYOR_PROJECT_NAME", 10 | "APPVEYOR_PROJECT_SLUG", 11 | "APPVEYOR_BUILD_FOLDER", 12 | "APPVEYOR_BUILD_ID", 13 | "APPVEYOR_BUILD_NUMBER", 14 | "APPVEYOR_BUILD_VERSION", 15 | "APPVEYOR_PULL_REQUEST_NUMBER", 16 | "APPVEYOR_PULL_REQUEST_TITLE", 17 | "APPVEYOR_JOB_ID", 18 | "APPVEYOR_JOB_NAME", 19 | "APPVEYOR_REPO_PROVIDER", 20 | "APPVEYOR_REPO_SCM", 21 | "APPVEYOR_REPO_NAME", 22 | "APPVEYOR_REPO_BRANCH", 23 | "APPVEYOR_REPO_TAG", 24 | "APPVEYOR_REPO_TAG_NAME", 25 | "APPVEYOR_REPO_COMMIT", 26 | "APPVEYOR_REPO_COMMIT_AUTHOR", 27 | "APPVEYOR_REPO_COMMIT_AUTHOR_EMAIL", 28 | "APPVEYOR_REPO_COMMIT_TIMESTAMP", 29 | "APPVEYOR_REPO_COMMIT_MESSAGE", 30 | "APPVEYOR_REPO_COMMIT_MESSAGE_EXTENDED", 31 | "APPVEYOR_SCHEDULED_BUILD", 32 | "APPVEYOR_FORCED_BUILD", 33 | "APPVEYOR_RE_BUILD", 34 | "PLATFORM", 35 | "CONFIGURATION", 36 | ] 37 | -------------------------------------------------------------------------------- /src/pytest_metadata/ci/bitbucket.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | ENVIRONMENT_VARIABLES = [ 6 | "CI", 7 | "BITBUCKET_BUILD_NUMBER", 8 | "BITBUCKET_CLONE_DIR", 9 | "BITBUCKET_COMMIT", 10 | "BITBUCKET_REPO_OWNER", 11 | "BITBUCKET_REPO_OWNER_UUID", 12 | "BITBUCKET_REPO_SLUG", 13 | "BITBUCKET_REPO_UUID", 14 | "BITBUCKET_BRANCH", 15 | "BITBUCKET_TAG", 16 | "BITBUCKET_BOOKMARK", 17 | "BITBUCKET_PARALLEL_STEP", 18 | "BITBUCKET_PARALLEL_STEP_COUNT", 19 | ] 20 | -------------------------------------------------------------------------------- /src/pytest_metadata/ci/circleci.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | ENVIRONMENT_VARIABLES = [ 6 | "CIRCLE_PROJECT_USERNAME", 7 | "CIRCLE_PROJECT_REPONAME", 8 | "CIRCLE_BRANCH", 9 | "CIRCLE_TAG", 10 | "CIRCLE_SHA1", 11 | "CIRCLE_REPOSITORY_URL", 12 | "CIRCLE_COMPARE_URL", 13 | "CIRCLE_BUILD_URL", 14 | "CIRCLE_BUILD_NUM", 15 | "CIRCLE_PREVIOUS_BUILD_NUM", 16 | "CI_PULL_REQUESTS", 17 | "CI_PULL_REQUEST", 18 | "CIRCLE_ARTIFACTS", 19 | "CIRCLE_USERNAME", 20 | "CIRCLE_TEST_REPORTS", 21 | "CIRCLE_PR_USERNAME", 22 | "CIRCLE_PR_REPONAME", 23 | "CIRCLE_PR_NUMBER", 24 | "CIRCLE_NODE_TOTAL", 25 | "CIRCLE_NODE_INDEX", 26 | "CIRCLE_BUILD_IMAGE", 27 | ] 28 | -------------------------------------------------------------------------------- /src/pytest_metadata/ci/codebuild.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | # Based on https://docs.aws.amazon.com/codebuild/latest/userguide/build-env-ref-env-vars.html 6 | 7 | ENVIRONMENT_VARIABLES = [ 8 | "AWS_REGION", 9 | "CODEBUILD_BUILD_ID", 10 | "CODEBUILD_BUILD_NUMBER", 11 | "CODEBUILD_RESOLVED_SOURCE_VERSION", 12 | "CODEBUILD_SOURCE_REPO_URL", 13 | "CODEBUILD_SOURCE_VERSION", 14 | ] 15 | -------------------------------------------------------------------------------- /src/pytest_metadata/ci/gitlab_ci.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | ENVIRONMENT_VARIABLES = [ 6 | "CI", 7 | "CI_COMMIT_REF_NAME", 8 | "CI_COMMIT_REF_SLUG", 9 | "CI_COMMIT_SHA", 10 | "CI_COMMIT_TAG", 11 | "CI_DEBUG_TRACE", 12 | "CI_ENVIRONMENT_NAME", 13 | "CI_ENVIRONMENT_SLUG", 14 | "CI_JOB_ID", 15 | "CI_JOB_MANUAL", 16 | "CI_JOB_NAME", 17 | "CI_JOB_STAGE", 18 | "CI_RUNNER_DESCRIPTION", 19 | "CI_RUNNER_ID", 20 | "CI_RUNNER_TAGS", 21 | "CI_PIPELINE_ID", 22 | "CI_PIPELINE_TRIGGERED", 23 | "CI_PROJECT_DIR", 24 | "CI_PROJECT_ID", 25 | "CI_PROJECT_NAME", 26 | "CI_PROJECT_NAMESPACE", 27 | "CI_PROJECT_PATH", 28 | "CI_PROJECT_URL", 29 | "CI_REGISTRY", 30 | "CI_REGISTRY_IMAGE", 31 | "CI_REGISTRY_USER", 32 | "CI_SERVER", 33 | "CI_SERVER_NAME", 34 | "CI_SERVER_REVISION", 35 | "CI_SERVER_VERSION", 36 | "ARTIFACT_DOWNLOAD_ATTEMPTS", 37 | "GET_SOURCES_ATTEMPTS", 38 | "GITLAB_CI", 39 | "GITLAB_USER_ID", 40 | "GITLAB_USER_EMAIL", 41 | "RESTORE_CACHE_ATTEMPTS", 42 | ] 43 | -------------------------------------------------------------------------------- /src/pytest_metadata/ci/jenkins.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | ENVIRONMENT_VARIABLES = [ 6 | "BUILD_NUMBER", 7 | "BUILD_ID", 8 | "BUILD_URL", 9 | "NODE_NAME", 10 | "JOB_NAME", 11 | "BUILD_TAG", 12 | "EXECUTOR_NUMBER", 13 | "JENKINS_URL", 14 | "JAVA_HOME", 15 | "WORKSPACE", 16 | "SVN_REVISION", 17 | "CVS_BRANCH", 18 | "GIT_COMMIT", 19 | "GIT_URL", 20 | "GIT_BRANCH", 21 | ] 22 | -------------------------------------------------------------------------------- /src/pytest_metadata/ci/taskcluster.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | ENVIRONMENT_VARIABLES = ["TASK_ID", "RUN_ID"] 6 | -------------------------------------------------------------------------------- /src/pytest_metadata/ci/travis_ci.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | ENVIRONMENT_VARIABLES = [ 6 | "TRAVIS_BRANCH", 7 | "TRAVIS_BUILD_ID", 8 | "TRAVIS_BUILD_NUMBER", 9 | "TRAVIS_COMMIT", 10 | "TRAVIS_COMMIT_MESSAGE", 11 | "TRAVIS_COMMIT_RANGE", 12 | "TRAVIS_EVENT_TYPE", 13 | "TRAVIS_JOB_ID", 14 | "TRAVIS_JOB_NUMBER", 15 | "TRAVIS_OS_NAME", 16 | "TRAVIS_PULL_REQUEST", 17 | "TRAVIS_PULL_REQUEST_BRANCH", 18 | "TRAVIS_PULL_REQUEST_SHA", 19 | "TRAVIS_PULL_REQUEST_SLUG", 20 | "TRAVIS_REPO_SLUG", 21 | "TRAVIS_SUDO", 22 | "TRAVIS_TAG", 23 | ] 24 | -------------------------------------------------------------------------------- /src/pytest_metadata/hooks.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | 5 | 6 | def pytest_metadata(metadata, config): 7 | """Called after collecting metadata""" 8 | -------------------------------------------------------------------------------- /src/pytest_metadata/plugin.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import json 5 | import os 6 | import platform 7 | 8 | try: 9 | import _pytest._pluggy as pluggy 10 | except ImportError: 11 | import pluggy 12 | import pytest 13 | 14 | from pytest_metadata.ci import ( 15 | appveyor, 16 | bitbucket, 17 | circleci, 18 | codebuild, 19 | gitlab_ci, 20 | jenkins, 21 | taskcluster, 22 | travis_ci, 23 | ) 24 | 25 | CONTINUOUS_INTEGRATION = [ 26 | appveyor.ENVIRONMENT_VARIABLES, 27 | bitbucket.ENVIRONMENT_VARIABLES, 28 | circleci.ENVIRONMENT_VARIABLES, 29 | codebuild.ENVIRONMENT_VARIABLES, 30 | gitlab_ci.ENVIRONMENT_VARIABLES, 31 | jenkins.ENVIRONMENT_VARIABLES, 32 | taskcluster.ENVIRONMENT_VARIABLES, 33 | travis_ci.ENVIRONMENT_VARIABLES, 34 | ] 35 | 36 | metadata_key = pytest.StashKey[dict]() 37 | 38 | 39 | def pytest_addhooks(pluginmanager): 40 | from pytest_metadata import hooks 41 | 42 | pluginmanager.add_hookspecs(hooks) 43 | 44 | 45 | @pytest.fixture(scope="session") 46 | def metadata(pytestconfig): 47 | """Provide test session metadata""" 48 | return pytestconfig.stash[metadata_key] 49 | 50 | 51 | @pytest.fixture(scope="session") 52 | def include_metadata_in_junit_xml(metadata, pytestconfig, record_testsuite_property): 53 | """Provide test session metadata""" 54 | metadata_ = pytestconfig.stash[metadata_key] 55 | for name, value in metadata_.items(): 56 | record_testsuite_property(name, value) 57 | 58 | 59 | def pytest_addoption(parser): 60 | group = parser.getgroup("pytest-metadata") 61 | group.addoption( 62 | "--metadata", 63 | action="append", 64 | default=[], 65 | dest="metadata", 66 | metavar=("key", "value"), 67 | nargs=2, 68 | help="additional metadata.", 69 | ) 70 | group.addoption( 71 | "--metadata-from-json", 72 | action="store", 73 | default="{}", 74 | dest="metadata_from_json", 75 | help="additional metadata from a json string.", 76 | ) 77 | group.addoption( 78 | "--metadata-from-json-file", 79 | type=str, 80 | dest="metadata_from_json_file", 81 | help="additional metadata from a json file.", 82 | ) 83 | 84 | 85 | @pytest.hookimpl(tryfirst=True) 86 | def pytest_configure(config): 87 | config.stash[metadata_key] = { 88 | "Python": platform.python_version(), 89 | "Platform": platform.platform(), 90 | "Packages": { 91 | "pytest": pytest.__version__, 92 | "pluggy": pluggy.__version__, 93 | }, 94 | } 95 | config.stash[metadata_key].update({k: v for k, v in config.getoption("metadata")}) 96 | config.stash[metadata_key].update( 97 | json.loads(config.getoption("metadata_from_json")) 98 | ) 99 | 100 | if config.getoption("metadata_from_json_file"): 101 | with open(config.getoption("metadata_from_json_file"), "r") as json_file: 102 | config.stash[metadata_key].update(json.load(json_file)) 103 | plugins = dict() 104 | for plugin, dist in config.pluginmanager.list_plugin_distinfo(): 105 | name, version = dist.project_name, dist.version 106 | if name.startswith("pytest-"): 107 | name = name[7:] 108 | plugins[name] = version 109 | config.stash[metadata_key]["Plugins"] = plugins 110 | 111 | for provider in CONTINUOUS_INTEGRATION: 112 | for var in provider: 113 | if os.environ.get(var): 114 | config.stash[metadata_key].update({var: os.environ.get(var)}) 115 | 116 | if hasattr(config, "workeroutput"): 117 | config.workeroutput["metadata"] = config.stash[metadata_key] 118 | config.hook.pytest_metadata(metadata=config.stash[metadata_key], config=config) 119 | 120 | 121 | def pytest_report_header(config): 122 | if config.getoption("verbose") > 0: 123 | return "metadata: {0}".format(config.stash[metadata_key]) 124 | 125 | 126 | @pytest.hookimpl(optionalhook=True) 127 | def pytest_testnodedown(node): 128 | # note that any metadata from remote workers will be replaced with the 129 | # environment from the final worker to quit 130 | if hasattr(node, "workeroutput"): 131 | node.config.stash[metadata_key].update(node.workeroutput["metadata"]) 132 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pytest-dev/pytest-metadata/16b39d2b8ec7500d566ff8b45576ea866d2c719c/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_metadata.py: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | import pytest 5 | from xml.etree import ElementTree as ET 6 | 7 | pytest_plugins = ("pytester",) 8 | 9 | 10 | def test_metadata(pytester): 11 | pytester.makepyfile( 12 | """ 13 | def test_pass(metadata): 14 | for k in ['Python', 'Platform', 'Packages']: 15 | assert k in metadata 16 | assert 'JENKINS_URL' not in metadata 17 | """ 18 | ) 19 | result = pytester.runpytest() 20 | assert result.ret == 0 21 | 22 | 23 | def test_environment_variables(pytester, monkeypatch): 24 | monkeypatch.setenv("JENKINS_URL", "foo") 25 | monkeypatch.setenv("GIT_COMMIT", "bar") 26 | pytester.makepyfile( 27 | """ 28 | def test_pass(metadata): 29 | assert metadata.get('JENKINS_URL') == 'foo' 30 | assert metadata.get('GIT_COMMIT') == 'bar' 31 | """ 32 | ) 33 | result = pytester.runpytest() 34 | assert result.ret == 0 35 | 36 | 37 | def test_additional_metadata(pytester): 38 | pytester.makepyfile( 39 | """ 40 | def test_pass(metadata): 41 | assert metadata.get('Dave') == 'Hunt' 42 | assert metadata.get('Jim') == 'Bob' 43 | """ 44 | ) 45 | result = pytester.runpytest( 46 | "--metadata", "Dave", "Hunt", "--metadata", "Jim", "Bob" 47 | ) 48 | assert result.ret == 0 49 | 50 | 51 | @pytest.mark.parametrize("junit_format", ["xunit1", "xunit2"]) 52 | def test_junit_integration(pytester, junit_format): 53 | pytester.makepyfile( 54 | """ 55 | import pytest 56 | 57 | pytestmark = pytest.mark.usefixtures('include_metadata_in_junit_xml') 58 | 59 | def test_pass(): 60 | pass 61 | """ 62 | ) 63 | result = pytester.runpytest( 64 | "--metadata", 65 | "Daffy", 66 | "Duck", 67 | "--junit-xml=results.xml", 68 | "--override-ini='junit_family={}'".format(junit_format), 69 | ) 70 | assert result.ret == 0 71 | results_file = pytester.path.joinpath("results.xml") 72 | assert results_file.exists() 73 | with results_file.open() as fd: 74 | xml = ET.parse(fd) 75 | properties = xml.findall(".//property") 76 | xml_metadata = [p.attrib for p in properties] 77 | # value passed on the cmdline appears 78 | assert {"name": "Daffy", "value": "Duck"} in xml_metadata 79 | 80 | 81 | def test_additional_metadata_from_json(pytester): 82 | pytester.makepyfile( 83 | """ 84 | def test_pass(metadata): 85 | assert metadata.get('Imran') == 'Mumtaz' 86 | """ 87 | ) 88 | result = pytester.runpytest("--metadata-from-json", '{"Imran": "Mumtaz"}') 89 | assert result.ret == 0 90 | 91 | 92 | def test_additional_metadata_from_json_file(pytester): 93 | pytester.makepyfile( 94 | """ 95 | def test_pass(metadata): 96 | assert metadata.get('John') == 'Cena' 97 | """ 98 | ) 99 | pytester.makefile(".json", temp='{"John": "Cena"}') 100 | 101 | result = pytester.runpytest("--metadata-from-json-file", "temp.json") 102 | assert result.ret == 0 103 | 104 | 105 | def test_additional_metadata_using_key_values_json_str_and_file(pytester): 106 | pytester.makepyfile( 107 | """ 108 | def test_pass(metadata): 109 | assert metadata.get('John') == 'Cena' 110 | assert metadata.get('Dwayne') == 'Johnson' 111 | assert metadata.get('Andre') == 'The Giant' 112 | """ 113 | ) 114 | pytester.makefile(".json", temp='{"Andre": "The Giant"}') 115 | 116 | result = pytester.runpytest( 117 | "--metadata", 118 | "John", 119 | "Cena", 120 | "--metadata-from-json", 121 | '{"Dwayne": "Johnson"}', 122 | "--metadata-from-json-file", 123 | "temp.json", 124 | ) 125 | assert result.ret == 0 126 | 127 | 128 | def test_metadata_hook(pytester): 129 | pytester.makeconftest( 130 | """ 131 | import pytest 132 | @pytest.hookimpl(optionalhook=True) 133 | def pytest_metadata(metadata): 134 | metadata['Dave'] = 'Hunt' 135 | """ 136 | ) 137 | pytester.makepyfile( 138 | """ 139 | def test_pass(metadata): 140 | assert metadata.get('Dave') == 'Hunt' 141 | """ 142 | ) 143 | result = pytester.runpytest() 144 | assert result.ret == 0 145 | 146 | 147 | def test_report_header(pytester): 148 | result = pytester.runpytest() 149 | assert not any(line.startswith("metadata:") for line in result.stdout.lines) 150 | result = pytester.runpytest("-v") 151 | assert any(line.startswith("metadata:") for line in result.stdout.lines) 152 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py{3.8, 3.9, 3.10, 3.11, 3.12, py3.10}, linting 3 | isolated_build = True 4 | 5 | [testenv] 6 | setenv = 7 | PYTHONDONTWRITEBYTECODE=1 8 | commands = pytest -s -ra --color=yes {posargs} 9 | 10 | [testenv:linting] 11 | skip_install = True 12 | basepython = python3 13 | deps = pre-commit 14 | commands = pre-commit run --all-files --show-diff-on-failure 15 | 16 | [testenv:devel] 17 | description = Tests with unreleased deps 18 | basepython = python3 19 | pip_pre = True 20 | deps = 21 | pytest @ git+https://github.com/pytest-dev/pytest.git 22 | 23 | [flake8] 24 | max-line-length = 120 25 | exclude = .eggs,.tox 26 | # rationale here: 27 | # https://github.com/psf/black/blob/master/docs/the_black_code_style.md#slices 28 | extend-ignore = E203 29 | 30 | [pytest] 31 | testpaths = tests 32 | --------------------------------------------------------------------------------