File pytest-shard-0.1.2.obscpio of Package python-pytest-shard

07070100000000000041ED0000000000000000000000025FD3CDFC00000000000000000000000000000000000000000000001D00000000pytest-shard-0.1.2/.circleci07070100000001000081A40000000000000000000000015FD3CDFC000001E6000000000000000000000000000000000000002800000000pytest-shard-0.1.2/.circleci/config.ymlversion: 2.1

executors:
  my-executor:
    docker:
      - image: circleci/python:3.6
    environment:
      NUM_CPUS: 2  # more CPUs visible but we're throttled to 2, which breaks auto-detec

jobs:
  build:
    executor: my-executor

    steps:
      - checkout
      - run: sudo pip install tox
      - run: tox -p ${NUM_CPUS}
      - store_artifacts:
          path: /tmp/test-reports
          destination: test-reports
      - store_test_results:
          path: /tmp/test-reports07070100000002000081A40000000000000000000000015FD3CDFC000006F2000000000000000000000000000000000000001E00000000pytest-shard-0.1.2/.gitignore# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
#  Usually these files are written by a python script from a template
#  before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
#   According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
#   However, in case of collaboration, if having platform-specific dependencies or dependencies
#   having no cross-platform support, pipenv may install dependencies that don't work, or not
#   install all needed dependencies.
#Pipfile.lock

# celery beat schedule file
celerybeat-schedule

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# PyCharm project settings
.idea/

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# pytype
.pytype/

# Pyre type checker
.pyre/
07070100000003000081A40000000000000000000000015FD3CDFC0000041B000000000000000000000000000000000000001B00000000pytest-shard-0.1.2/LICENSECopyright 2019 Adam Gleave

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
07070100000004000081A40000000000000000000000015FD3CDFC00000908000000000000000000000000000000000000001D00000000pytest-shard-0.1.2/README.md[![CircleCI](https://circleci.com/gh/AdamGleave/pytest-shard.svg?style=svg)](https://circleci.com/gh/AdamGleave/pytest-shard)
[![PyPI version](https://badge.fury.io/py/pytest-shard.svg)](https://badge.fury.io/py/pytest-shard)

# pytest-shard

Shards tests based on a hash of their test name enabling easy parallelism across machines, suitable for a wide variety of continuous integration services. Tests are split at the finest level of granularity, individual test cases, enabling parallelism even if all of your tests are in a single file (or even single parameterized test method).

## Features

`pytest-shard` aims for simplicity. When installed, simply run:

```
$ pytest --shard-id=I --num-shards=N
```

where `I` is the index of this shard and `N` the total number of shards. For example, to split tests across two machines:

```
# On machine 1:
$ pytest --shard-id=0 --num-shards=2
# On machine 2:
$ pytest --shard-id=1 --num-shards=2
```

The intended use case is for continuous integration services that allow you to run jobs in parallel. For CircleCI, enable [parallelism](https://circleci.com/docs/2.0/parallelism-faster-jobs/) and then use:
```
pytest --shard-id=${CIRCLE_NODE_INDEX} --num-shards=${CIRCLE_NODE_TOTAL}
```

On Travis, you must define the environment variables explicitly, but can use a [similar approach](https://docs.travis-ci.com/user/speeding-up-the-build/).

## Alternatives

[pytest-xdist](https://github.com/pytest-dev/pytest-xdist) allows you to parallelize tests across cores on a single machine, and can also schedule tests on a remote machine. I use `pytest-shard` to split tests across CI workers, and `pytest-xdist` to parallelize across CPU cores within each worker.

`pytest-shard` does not take into account the run time of tests, which can lead to suboptimal allocations. [pytest-circleci-parallelized](https://github.com/ryanwilsonperkin/pytest-circleci-parallelized) uses test run time, but can only split at the granularity of classes, and is specific to CircleCI.

Please open a PR if there are other promising alternatives that I have overlooked.

## Installation

You can install `pytest-shard` via `pip`:

```
$ pip install pytest-shard
```

## Contributions

Contributions are welcome. Test may be run using `tox`.

## License

This software is MIT licensed.
07070100000005000081A40000000000000000000000015FD3CDFC00000264000000000000000000000000000000000000001C00000000pytest-shard-0.1.2/pylintrc[MESSAGES CONTROL]

# disable these warnings
disable=
	# disagree with the rule: it's only sometimes clearer
	no-else-return,
	# too many false positives
	arguments-differ,
	duplicate-code,
	# Python formatting cleaner, we don't care about minor performance overhead
	logging-format-interpolation,
	# snake_case naming style: spurious errors for short variable names
	C0103,
	# hanging indent, see black GH issue #48 
	C0330,
	# We intend to leave TODO comments in place
	fixme,


[BASIC]

# Minimum line length for functions/classes that require docstrings, shorter
# # ones are exempt.
docstring-min-length=10
07070100000006000081A40000000000000000000000015FD3CDFC00000039000000000000000000000000000000000000002200000000pytest-shard-0.1.2/pyproject.toml[tool.black]
line-length = 100
target-version = ["py37"]
07070100000007000041ED0000000000000000000000025FD3CDFC00000000000000000000000000000000000000000000002000000000pytest-shard-0.1.2/pytest_shard07070100000008000081A40000000000000000000000015FD3CDFC0000005A000000000000000000000000000000000000002C00000000pytest-shard-0.1.2/pytest_shard/__init__.py"""Shard tests to support parallelism across multiple machines."""

__version__ = "0.1.2"
07070100000009000081A40000000000000000000000015FD3CDFC00000801000000000000000000000000000000000000003000000000pytest-shard-0.1.2/pytest_shard/pytest_shard.py"""Shard tests to support parallelism across multiple machines."""

import hashlib
from typing import Iterable, List, Sequence

from _pytest import nodes  # for type checking only


def positive_int(x) -> int:
    x = int(x)
    if x < 0:
        raise ValueError(f"Argument {x} must be positive")
    return x


def pytest_addoption(parser):
    """Add pytest-shard specific configuration parameters."""
    group = parser.getgroup("shard")
    group.addoption(
        "--shard-id", dest="shard_id", type=positive_int, default=0, help="Number of this shard."
    )
    group.addoption(
        "--num-shards",
        dest="num_shards",
        type=positive_int,
        default=1,
        help="Total number of shards.",
    )


def pytest_report_collectionfinish(config, items: Sequence[nodes.Node]) -> str:
    """Log how many and, if verbose, which items are tested in this shard."""
    msg = f"Running {len(items)} items in this shard"
    if config.option.verbose > 0:
        msg += ": " + ", ".join([item.nodeid for item in items])
    return msg


def sha256hash(x: str) -> int:
    return int.from_bytes(hashlib.sha256(x.encode()).digest(), "little")


def filter_items_by_shard(
    items: Iterable[nodes.Node], shard_id: int, num_shards: int
) -> Sequence[nodes.Node]:
    """Computes `items` that should be tested in `shard_id` out of `num_shards` total shards."""
    shards = [sha256hash(item.nodeid) % num_shards for item in items]

    new_items = []
    for shard, item in zip(shards, items):
        if shard == shard_id:
            new_items.append(item)
    return new_items


def pytest_collection_modifyitems(config, items: List[nodes.Node]):
    """Mutate the collection to consist of just items to be tested in this shard."""
    shard_id = config.getoption("shard_id")
    shard_total = config.getoption("num_shards")
    if shard_id >= shard_total:
        raise ValueError("shard_num = f{shard_num} must be less than shard_total = f{shard_total}")

    items[:] = filter_items_by_shard(items, shard_id, shard_total)
0707010000000A000081A40000000000000000000000015FD3CDFC00000181000000000000000000000000000000000000001D00000000pytest-shard-0.1.2/setup.cfg[coverage:report]
exclude_lines =
    pragma: no cover
omit =
    setup.py

[coverage:run]
include=
    pytest_shard/
    tests/*

[flake8]
max-line-length=100
ignore = W503,E203

[isort]
known_first_party=evaluating_rewards,tests
default_section=THIRDPARTY
multi_line_output=3
force_sort_within_sections=True
line_length=100

[pytype]
inputs = evaluating_rewards
python_version = 3.7
0707010000000B000081A40000000000000000000000015FD3CDFC0000047F000000000000000000000000000000000000001C00000000pytest-shard-0.1.2/setup.pyfrom setuptools import setup
import os

import pytest_shard


def read(file_name):
    file_path = os.path.join(os.path.dirname(__file__), file_name)
    with open(file_path, "r") as f:
        return f.read()


setup(
    name="pytest-shard",
    version=pytest_shard.__version__,
    packages=["pytest_shard"],
    license="MIT",
    url="https://github.com/AdamGleave/pytest-shard",
    install_requires=["pytest"],
    long_description=read("README.md"),
    long_description_content_type="text/markdown",
    python_requires=">=3.6",
    # the following makes a plugin available to pytest
    entry_points={"pytest11": ["pytest-shard = pytest_shard.pytest_shard"]},
    classifiers=[
        "Framework :: Pytest",
        "Intended Audience :: Developers",
        "Topic :: Software Development :: Testing",
        "Development Status :: 4 - Beta",
        "Programming Language :: Python",
        "Programming Language :: Python :: 3.6",
        "Programming Language :: Python :: 3.7",
        "Programming Language :: Python :: 3.8",
        "Operating System :: OS Independent",
        "License :: OSI Approved :: MIT License",
    ],
)
0707010000000C000041ED0000000000000000000000025FD3CDFC00000000000000000000000000000000000000000000001900000000pytest-shard-0.1.2/tests0707010000000D000081A40000000000000000000000015FD3CDFC00000000000000000000000000000000000000000000002500000000pytest-shard-0.1.2/tests/__init__.py0707010000000E000081A40000000000000000000000015FD3CDFC000006D5000000000000000000000000000000000000002E00000000pytest-shard-0.1.2/tests/test_pytest_shard.py"""Test pytest_shard.pytest_shard."""

import collections
import itertools

import hypothesis
from hypothesis import strategies
import pytest

from pytest_shard import pytest_shard


@hypothesis.given(strategies.integers(min_value=0))
def test_positive_int_with_pos(x):
    assert pytest_shard.positive_int(x) == x
    assert pytest_shard.positive_int(str(x)) == x


@hypothesis.given(strategies.integers(max_value=-1))
def test_positive_int_with_neg(x):
    with pytest.raises(ValueError):
        pytest_shard.positive_int(x)
    with pytest.raises(ValueError):
        pytest_shard.positive_int(str(x))


def test_positive_int_with_non_num():
    invalid = ["foobar", "x1", "1x"]
    for s in invalid:
        with pytest.raises(ValueError):
            pytest_shard.positive_int(s)


@hypothesis.given(strategies.text())
def test_sha256hash_deterministic(s):
    x = pytest_shard.sha256hash(s)
    y = pytest_shard.sha256hash(s)
    assert x == y
    assert type(x) == int


@hypothesis.given(strategies.text(), strategies.text())
def test_sha256hash_no_clash(s1, s2):
    if s1 != s2:
        assert pytest_shard.sha256hash(s1) != pytest_shard.sha256hash(s2)


MockItem = collections.namedtuple("MockItem", "nodeid")


@hypothesis.given(
    names=strategies.lists(strategies.text(), unique=True),
    num_shards=strategies.integers(min_value=1, max_value=500),
)
def test_filter_items_by_shard(names, num_shards):
    items = [MockItem(name) for name in names]

    filtered = [
        pytest_shard.filter_items_by_shard(items, shard_id=i, num_shards=num_shards)
        for i in range(num_shards)
    ]
    all_filtered = list(itertools.chain(*filtered))
    assert len(all_filtered) == len(items)
    assert set(all_filtered) == set(items)
0707010000000F000081A40000000000000000000000015FD3CDFC00000302000000000000000000000000000000000000001B00000000pytest-shard-0.1.2/tox.ini[tox]
envlist = py36,shard0,shard1,flake8,black

[testenv]
deps = hypothesis
commands = pytest --junitxml=/tmp/test-reports/junit-{envname}.xml tests/ {posargs}

[testenv:shard0]
deps = hypothesis
commands = pytest --shard-id=0 --num-shards=2 tests/ {posargs}

[testenv:shard1]
deps = hypothesis
commands = pytest --shard-id=1 --num-shards=2 tests/ {posargs}

[testenv:flake8]
skip_install = true
deps = flake8
commands = flake8 pytest_shard/ tests/ setup.py

[testenv:black]
basepython = python3
skip_install = true
deps = black
commands = black --check pytest_shard/ tests/ setup.py

[testenv:pylint]
basepython = python3
deps = pylint
commands = pylint pytest_shard/ tests/ setup.py

[testenv:pytype]
basepython = python3
deps = pytype
commands = pytype pytest_shard/07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!30 blocks
openSUSE Build Service is sponsored by