├── .github └── workflows │ └── test.yml ├── .gitignore ├── AUTHORS ├── CHANGELOG.rst ├── LICENSE ├── MANIFEST.in ├── Pipfile ├── Pipfile.lock ├── README.rst ├── setup.py ├── src └── pytest_stress │ ├── __init__.py │ └── pytest_stress.py ├── tests └── test_stress.py └── tox.ini /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request, workflow_dispatch] 4 | 5 | env: 6 | FORCE_COLOR: 1 7 | 8 | jobs: 9 | test: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | python-version: ["3.6", "3.7", "3.8", "3.9", "3.10-dev"] 15 | os: [ubuntu-latest, macos-latest, windows-latest] 16 | include: 17 | # Add new helper variables to existing jobs 18 | - {python-version: "3.6", TOX_ENV: "py36"} 19 | - {python-version: "3.7", TOX_ENV: "py37"} 20 | - {python-version: "3.8", TOX_ENV: "py38"} 21 | - {python-version: "3.9", TOX_ENV: "py39"} 22 | - {python-version: "3.10-dev", TOX_ENV: "py310"} 23 | 24 | steps: 25 | - uses: actions/checkout@v2 26 | 27 | - name: Set up Python ${{ matrix.python-version }} 28 | uses: actions/setup-python@v2 29 | with: 30 | python-version: ${{ matrix.python-version }} 31 | 32 | - name: Get pip cache dir 33 | id: pip-cache 34 | run: | 35 | echo "::set-output name=dir::$(pip cache dir)" 36 | 37 | - name: Cache 38 | uses: actions/cache@v2 39 | with: 40 | path: ${{ steps.pip-cache.outputs.dir }} 41 | key: 42 | ${{ matrix.os }}-${{ matrix.python-version }}-v1-${{ hashFiles('**/tox.ini') }} 43 | restore-keys: | 44 | ${{ matrix.os }}-${{ matrix.python-version }}-v1- 45 | 46 | - name: Install dependencies 47 | run: | 48 | python -m pip install -U pip 49 | python -m pip install -U wheel 50 | python -m pip install -U tox 51 | 52 | - name: Tox tests 53 | run: | 54 | tox -e ${{ matrix.TOX_ENV }} 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | .pytest_cache 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask instance folder 58 | instance/ 59 | 60 | # Sphinx documentation 61 | docs/_build/ 62 | 63 | # MkDocs documentation 64 | /site/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # Apps 76 | .idea/ 77 | .mypy_cache/ 78 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Imran Mumtaz -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ========= 2 | Changelog 3 | ========= 4 | 5 | Versions follow `Semantic Versioning `_ (``..``). 6 | 7 | pytest-stress 1.0.0 (2019-11-16) 8 | ================================ 9 | 10 | * First release v1.0.0. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2019 Imran Mumtaz 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.rst 3 | 4 | recursive-exclude * __pycache__ 5 | recursive-exclude * *.py[co] 6 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | url = "https://pypi.org/simple" 3 | verify_ssl = true 4 | name = "pypi" 5 | 6 | [dev-packages] 7 | cookiecutter = "*" 8 | pytest = "*" 9 | tox = "*" 10 | flake8 = "*" 11 | 12 | [requires] 13 | python_version = "3.7" 14 | -------------------------------------------------------------------------------- /Pipfile.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "hash": { 4 | "sha256": "6856ce12cf353d3ad22dcb0ac481514e4316aa46e39fbcf7a6858a91ed20313c" 5 | }, 6 | "pipfile-spec": 6, 7 | "requires": { 8 | "python_version": "3.7" 9 | }, 10 | "sources": [ 11 | { 12 | "name": "pypi", 13 | "url": "https://pypi.org/simple", 14 | "verify_ssl": true 15 | } 16 | ] 17 | }, 18 | "default": {}, 19 | "develop": { 20 | "appdirs": { 21 | "hashes": [ 22 | "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", 23 | "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" 24 | ], 25 | "version": "==1.4.4" 26 | }, 27 | "arrow": { 28 | "hashes": [ 29 | "sha256:8cbe6a629b1c54ae11b52d6d9e70890089241958f63bc59467e277e34b7a5378", 30 | "sha256:b8fe13abf3517abab315e09350c903902d1447bd311afbc17547ba1cb3ff5bd8" 31 | ], 32 | "version": "==1.1.0" 33 | }, 34 | "attrs": { 35 | "hashes": [ 36 | "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", 37 | "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" 38 | ], 39 | "version": "==21.2.0" 40 | }, 41 | "binaryornot": { 42 | "hashes": [ 43 | "sha256:359501dfc9d40632edc9fac890e19542db1a287bbcfa58175b66658392018061", 44 | "sha256:b8b71173c917bddcd2c16070412e369c3ed7f0528926f70cac18a6c97fd563e4" 45 | ], 46 | "version": "==0.4.4" 47 | }, 48 | "certifi": { 49 | "hashes": [ 50 | "sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee", 51 | "sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8" 52 | ], 53 | "version": "==2021.5.30" 54 | }, 55 | "chardet": { 56 | "hashes": [ 57 | "sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa", 58 | "sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5" 59 | ], 60 | "version": "==4.0.0" 61 | }, 62 | "click": { 63 | "hashes": [ 64 | "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", 65 | "sha256:fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6" 66 | ], 67 | "version": "==8.0.1" 68 | }, 69 | "cookiecutter": { 70 | "hashes": [ 71 | "sha256:430eb882d028afb6102c084bab6cf41f6559a77ce9b18dc6802e3bc0cc5f4a30", 72 | "sha256:efb6b2d4780feda8908a873e38f0e61778c23f6a2ea58215723bcceb5b515dac" 73 | ], 74 | "index": "pypi", 75 | "version": "==1.7.2" 76 | }, 77 | "distlib": { 78 | "hashes": [ 79 | "sha256:106fef6dc37dd8c0e2c0a60d3fca3e77460a48907f335fa28420463a6f799736", 80 | "sha256:23e223426b28491b1ced97dc3bbe183027419dfc7982b4fa2f05d5f3ff10711c" 81 | ], 82 | "version": "==0.3.2" 83 | }, 84 | "filelock": { 85 | "hashes": [ 86 | "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", 87 | "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" 88 | ], 89 | "version": "==3.0.12" 90 | }, 91 | "flake8": { 92 | "hashes": [ 93 | "sha256:1aa8990be1e689d96c745c5682b687ea49f2e05a443aff1f8251092b0014e378", 94 | "sha256:3b9f848952dddccf635be78098ca75010f073bfe14d2c6bda867154bea728d2a" 95 | ], 96 | "index": "pypi", 97 | "version": "==3.9.1" 98 | }, 99 | "idna": { 100 | "hashes": [ 101 | "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", 102 | "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" 103 | ], 104 | "version": "==2.10" 105 | }, 106 | "importlib-metadata": { 107 | "hashes": [ 108 | "sha256:960d52ba7c21377c990412aca380bf3642d734c2eaab78a2c39319f67c6a5786", 109 | "sha256:e592faad8de1bda9fe920cf41e15261e7131bcf266c30306eec00e8e225c1dd5" 110 | ], 111 | "markers": "python_version < '3.8'", 112 | "version": "==4.4.0" 113 | }, 114 | "iniconfig": { 115 | "hashes": [ 116 | "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", 117 | "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" 118 | ], 119 | "version": "==1.1.1" 120 | }, 121 | "jinja2": { 122 | "hashes": [ 123 | "sha256:03e47ad063331dd6a3f04a43eddca8a966a26ba0c5b7207a9a9e4e08f1b29419", 124 | "sha256:a6d58433de0ae800347cab1fa3043cebbabe8baa9d29e668f1c768cb87a333c6" 125 | ], 126 | "version": "==2.11.3" 127 | }, 128 | "jinja2-time": { 129 | "hashes": [ 130 | "sha256:d14eaa4d315e7688daa4969f616f226614350c48730bfa1692d2caebd8c90d40", 131 | "sha256:d3eab6605e3ec8b7a0863df09cc1d23714908fa61aa6986a845c20ba488b4efa" 132 | ], 133 | "version": "==0.2.0" 134 | }, 135 | "markupsafe": { 136 | "hashes": [ 137 | "sha256:00bc623926325b26bb9605ae9eae8a215691f33cae5df11ca5424f06f2d1f473", 138 | "sha256:09027a7803a62ca78792ad89403b1b7a73a01c8cb65909cd876f7fcebd79b161", 139 | "sha256:09c4b7f37d6c648cb13f9230d847adf22f8171b1ccc4d5682398e77f40309235", 140 | "sha256:1027c282dad077d0bae18be6794e6b6b8c91d58ed8a8d89a89d59693b9131db5", 141 | "sha256:13d3144e1e340870b25e7b10b98d779608c02016d5184cfb9927a9f10c689f42", 142 | "sha256:195d7d2c4fbb0ee8139a6cf67194f3973a6b3042d742ebe0a9ed36d8b6f0c07f", 143 | "sha256:22c178a091fc6630d0d045bdb5992d2dfe14e3259760e713c490da5323866c39", 144 | "sha256:24982cc2533820871eba85ba648cd53d8623687ff11cbb805be4ff7b4c971aff", 145 | "sha256:29872e92839765e546828bb7754a68c418d927cd064fd4708fab9fe9c8bb116b", 146 | "sha256:2beec1e0de6924ea551859edb9e7679da6e4870d32cb766240ce17e0a0ba2014", 147 | "sha256:3b8a6499709d29c2e2399569d96719a1b21dcd94410a586a18526b143ec8470f", 148 | "sha256:43a55c2930bbc139570ac2452adf3d70cdbb3cfe5912c71cdce1c2c6bbd9c5d1", 149 | "sha256:46c99d2de99945ec5cb54f23c8cd5689f6d7177305ebff350a58ce5f8de1669e", 150 | "sha256:500d4957e52ddc3351cabf489e79c91c17f6e0899158447047588650b5e69183", 151 | "sha256:535f6fc4d397c1563d08b88e485c3496cf5784e927af890fb3c3aac7f933ec66", 152 | "sha256:596510de112c685489095da617b5bcbbac7dd6384aeebeda4df6025d0256a81b", 153 | "sha256:62fe6c95e3ec8a7fad637b7f3d372c15ec1caa01ab47926cfdf7a75b40e0eac1", 154 | "sha256:6788b695d50a51edb699cb55e35487e430fa21f1ed838122d722e0ff0ac5ba15", 155 | "sha256:6dd73240d2af64df90aa7c4e7481e23825ea70af4b4922f8ede5b9e35f78a3b1", 156 | "sha256:6f1e273a344928347c1290119b493a1f0303c52f5a5eae5f16d74f48c15d4a85", 157 | "sha256:6fffc775d90dcc9aed1b89219549b329a9250d918fd0b8fa8d93d154918422e1", 158 | "sha256:717ba8fe3ae9cc0006d7c451f0bb265ee07739daf76355d06366154ee68d221e", 159 | "sha256:79855e1c5b8da654cf486b830bd42c06e8780cea587384cf6545b7d9ac013a0b", 160 | "sha256:7c1699dfe0cf8ff607dbdcc1e9b9af1755371f92a68f706051cc8c37d447c905", 161 | "sha256:7fed13866cf14bba33e7176717346713881f56d9d2bcebab207f7a036f41b850", 162 | "sha256:84dee80c15f1b560d55bcfe6d47b27d070b4681c699c572af2e3c7cc90a3b8e0", 163 | "sha256:88e5fcfb52ee7b911e8bb6d6aa2fd21fbecc674eadd44118a9cc3863f938e735", 164 | "sha256:8defac2f2ccd6805ebf65f5eeb132adcf2ab57aa11fdf4c0dd5169a004710e7d", 165 | "sha256:98bae9582248d6cf62321dcb52aaf5d9adf0bad3b40582925ef7c7f0ed85fceb", 166 | "sha256:98c7086708b163d425c67c7a91bad6e466bb99d797aa64f965e9d25c12111a5e", 167 | "sha256:9add70b36c5666a2ed02b43b335fe19002ee5235efd4b8a89bfcf9005bebac0d", 168 | "sha256:9bf40443012702a1d2070043cb6291650a0841ece432556f784f004937f0f32c", 169 | "sha256:a6a744282b7718a2a62d2ed9d993cad6f5f585605ad352c11de459f4108df0a1", 170 | "sha256:acf08ac40292838b3cbbb06cfe9b2cb9ec78fce8baca31ddb87aaac2e2dc3bc2", 171 | "sha256:ade5e387d2ad0d7ebf59146cc00c8044acbd863725f887353a10df825fc8ae21", 172 | "sha256:b00c1de48212e4cc9603895652c5c410df699856a2853135b3967591e4beebc2", 173 | "sha256:b1282f8c00509d99fef04d8ba936b156d419be841854fe901d8ae224c59f0be5", 174 | "sha256:b1dba4527182c95a0db8b6060cc98ac49b9e2f5e64320e2b56e47cb2831978c7", 175 | "sha256:b2051432115498d3562c084a49bba65d97cf251f5a331c64a12ee7e04dacc51b", 176 | "sha256:b7d644ddb4dbd407d31ffb699f1d140bc35478da613b441c582aeb7c43838dd8", 177 | "sha256:ba59edeaa2fc6114428f1637ffff42da1e311e29382d81b339c1817d37ec93c6", 178 | "sha256:bf5aa3cbcfdf57fa2ee9cd1822c862ef23037f5c832ad09cfea57fa846dec193", 179 | "sha256:c8716a48d94b06bb3b2524c2b77e055fb313aeb4ea620c8dd03a105574ba704f", 180 | "sha256:caabedc8323f1e93231b52fc32bdcde6db817623d33e100708d9a68e1f53b26b", 181 | "sha256:cd5df75523866410809ca100dc9681e301e3c27567cf498077e8551b6d20e42f", 182 | "sha256:cdb132fc825c38e1aeec2c8aa9338310d29d337bebbd7baa06889d09a60a1fa2", 183 | "sha256:d53bc011414228441014aa71dbec320c66468c1030aae3a6e29778a3382d96e5", 184 | "sha256:d73a845f227b0bfe8a7455ee623525ee656a9e2e749e4742706d80a6065d5e2c", 185 | "sha256:d9be0ba6c527163cbed5e0857c451fcd092ce83947944d6c14bc95441203f032", 186 | "sha256:e249096428b3ae81b08327a63a485ad0878de3fb939049038579ac0ef61e17e7", 187 | "sha256:e8313f01ba26fbbe36c7be1966a7b7424942f670f38e666995b88d012765b9be", 188 | "sha256:feb7b34d6325451ef96bc0e36e1a6c0c1c64bc1fbec4b854f4529e51887b1621" 189 | ], 190 | "version": "==1.1.1" 191 | }, 192 | "mccabe": { 193 | "hashes": [ 194 | "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", 195 | "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" 196 | ], 197 | "version": "==0.6.1" 198 | }, 199 | "packaging": { 200 | "hashes": [ 201 | "sha256:5b327ac1320dc863dca72f4514ecc086f31186744b84a230374cc1fd776feae5", 202 | "sha256:67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a" 203 | ], 204 | "version": "==20.9" 205 | }, 206 | "pluggy": { 207 | "hashes": [ 208 | "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0", 209 | "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d" 210 | ], 211 | "version": "==0.13.1" 212 | }, 213 | "poyo": { 214 | "hashes": [ 215 | "sha256:3e2ca8e33fdc3c411cd101ca395668395dd5dc7ac775b8e809e3def9f9fe041a", 216 | "sha256:e26956aa780c45f011ca9886f044590e2d8fd8b61db7b1c1cf4e0869f48ed4dd" 217 | ], 218 | "version": "==0.5.0" 219 | }, 220 | "py": { 221 | "hashes": [ 222 | "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3", 223 | "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a" 224 | ], 225 | "version": "==1.10.0" 226 | }, 227 | "pycodestyle": { 228 | "hashes": [ 229 | "sha256:514f76d918fcc0b55c6680472f0a37970994e07bbb80725808c17089be302068", 230 | "sha256:c389c1d06bf7904078ca03399a4816f974a1d590090fecea0c63ec26ebaf1cef" 231 | ], 232 | "version": "==2.7.0" 233 | }, 234 | "pyflakes": { 235 | "hashes": [ 236 | "sha256:7893783d01b8a89811dd72d7dfd4d84ff098e5eed95cfa8905b22bbffe52efc3", 237 | "sha256:f5bc8ecabc05bb9d291eb5203d6810b49040f6ff446a756326104746cc00c1db" 238 | ], 239 | "version": "==2.3.1" 240 | }, 241 | "pyparsing": { 242 | "hashes": [ 243 | "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", 244 | "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" 245 | ], 246 | "version": "==2.4.7" 247 | }, 248 | "pytest": { 249 | "hashes": [ 250 | "sha256:671238a46e4df0f3498d1c3270e5deb9b32d25134c99b7d75370a68cfbe9b634", 251 | "sha256:6ad9c7bdf517a808242b998ac20063c41532a570d088d77eec1ee12b0b5574bc" 252 | ], 253 | "index": "pypi", 254 | "version": "==6.2.3" 255 | }, 256 | "python-dateutil": { 257 | "hashes": [ 258 | "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", 259 | "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" 260 | ], 261 | "version": "==2.8.1" 262 | }, 263 | "python-slugify": { 264 | "hashes": [ 265 | "sha256:6d8c5df75cd4a7c3a2d21e257633de53f52ab0265cd2d1dc62a730e8194a7380", 266 | "sha256:f13383a0b9fcbe649a1892b9c8eb4f8eab1d6d84b84bb7a624317afa98159cab" 267 | ], 268 | "version": "==5.0.2" 269 | }, 270 | "requests": { 271 | "hashes": [ 272 | "sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804", 273 | "sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e" 274 | ], 275 | "version": "==2.25.1" 276 | }, 277 | "six": { 278 | "hashes": [ 279 | "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", 280 | "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" 281 | ], 282 | "version": "==1.16.0" 283 | }, 284 | "text-unidecode": { 285 | "hashes": [ 286 | "sha256:1311f10e8b895935241623731c2ba64f4c455287888b18189350b67134a822e8", 287 | "sha256:bad6603bb14d279193107714b288be206cac565dfa49aa5b105294dd5c4aab93" 288 | ], 289 | "version": "==1.3" 290 | }, 291 | "toml": { 292 | "hashes": [ 293 | "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", 294 | "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" 295 | ], 296 | "version": "==0.10.2" 297 | }, 298 | "tox": { 299 | "hashes": [ 300 | "sha256:05a4dbd5e4d3d8269b72b55600f0b0303e2eb47ad5c6fe76d3576f4c58d93661", 301 | "sha256:e007673f3595cede9b17a7c4962389e4305d4a3682a6c5a4159a1453b4f326aa" 302 | ], 303 | "index": "pypi", 304 | "version": "==3.23.0" 305 | }, 306 | "typing-extensions": { 307 | "hashes": [ 308 | "sha256:0ac0f89795dd19de6b97debb0c6af1c70987fd80a2d62d1958f7e56fcc31b497", 309 | "sha256:50b6f157849174217d0656f99dc82fe932884fb250826c18350e159ec6cdf342", 310 | "sha256:779383f6086d90c99ae41cf0ff39aac8a7937a9283ce0a414e5dd782f4c94a84" 311 | ], 312 | "markers": "python_version < '3.8'", 313 | "version": "==3.10.0.0" 314 | }, 315 | "urllib3": { 316 | "hashes": [ 317 | "sha256:753a0374df26658f99d826cfe40394a686d05985786d946fbe4165b5148f5a7c", 318 | "sha256:a7acd0977125325f516bda9735fa7142b909a8d01e8b2e4c8108d0984e6e0098" 319 | ], 320 | "index": "pypi", 321 | "version": "==1.26.5" 322 | }, 323 | "virtualenv": { 324 | "hashes": [ 325 | "sha256:14fdf849f80dbb29a4eb6caa9875d476ee2a5cf76a5f5415fa2f1606010ab467", 326 | "sha256:2b0126166ea7c9c3661f5b8e06773d28f83322de7a3ff7d06f0aed18c9de6a76" 327 | ], 328 | "version": "==20.4.7" 329 | }, 330 | "zipp": { 331 | "hashes": [ 332 | "sha256:3607921face881ba3e026887d8150cca609d517579abe052ac81fc5aeffdbd76", 333 | "sha256:51cb66cc54621609dd593d1787f286ee42a5c0adbb4b29abea5a63edc3e03098" 334 | ], 335 | "version": "==3.4.1" 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============= 2 | Pytest-stress 3 | ============= 4 | 5 | .. image:: https://github.com/pytest-dev/pytest-stress/actions/workflows/test.yml/badge.svg 6 | :target: https://github.com/pytest-dev/pytest-cov/actions 7 | :alt: See Build Status on GitHub Actions 8 | 9 | .. image:: https://img.shields.io/pypi/v/pytest-stress.svg 10 | :target: https://pypi.org/project/pytest-stress 11 | :alt: PyPI version 12 | 13 | .. image:: https://img.shields.io/pypi/pyversions/pytest-stress.svg 14 | :target: https://pypi.org/project/pytest-stress 15 | :alt: Python versions 16 | 17 | A plugin that allows you to loop tests for a user-defined amount of time. 18 | 19 | Requirements 20 | ------------ 21 | 22 | Only tested with `Pytest`_ version 5.2.2. 23 | 24 | 25 | Installation 26 | ------------ 27 | 28 | You can install "pytest-stress" via `pip`_ from `PyPI`_ (highly recommend installing in a `Pipenv`_):: 29 | 30 | $ pip3 install pytest-stress 31 | 32 | Usage 33 | ----- 34 | 35 | Loop tests for 30 seconds:: 36 | 37 | $ pytest --seconds 30 38 | 39 | Loop tests for 45 minutes:: 40 | 41 | $ pytest --minutes 45 42 | 43 | Loop tests for 8 hours:: 44 | 45 | $ pytest --hours 8 46 | 47 | Loop tests for 1 hour 8 minutes and 9 seconds:: 48 | 49 | $ pytest --hours 1 --minutes 8 --seconds 9 50 | 51 | Need to wait some time after each test loop? Don't say I don't love you:: 52 | 53 | $ pytest --delay 5 --hours 4 --minutes 30 54 | 55 | You can also add these values to config files:: 56 | 57 | [pytest] 58 | addopts = --hours 1 --minutes 30 59 | 60 | Note: These loop times include setup and teardown operations as well. So if you have a test setup that takes 5 61 | seconds, your actual tests will run for 5 seconds less than your desired time. 62 | 63 | Contributing 64 | ------------ 65 | Contributions are very welcome! Tests can be run with `tox`_, please ensure 66 | the coverage at least stays the same before you submit a pull request. 67 | 68 | License 69 | ------- 70 | 71 | Distributed under the terms of the `MIT`_ license, "pytest-stress" is free and open source software 72 | 73 | 74 | Issues 75 | ------ 76 | 77 | If you encounter any problems, please `file an issue`_ along with a detailed description. 78 | 79 | ____ 80 | 81 | This `pytest`_ plugin was generated with `Cookiecutter`_ along with `@hackebrot`_'s `cookiecutter-pytest-plugin`_ template. 82 | 83 | .. _`Cookiecutter`: https://github.com/audreyr/cookiecutter 84 | .. _`@hackebrot`: https://github.com/hackebrot 85 | .. _`MIT`: http://opensource.org/licenses/MIT 86 | .. _`BSD-3`: http://opensource.org/licenses/BSD-3-Clause 87 | .. _`GNU GPL v3.0`: http://www.gnu.org/licenses/gpl-3.0.txt 88 | .. _`Apache Software License 2.0`: http://www.apache.org/licenses/LICENSE-2.0 89 | .. _`cookiecutter-pytest-plugin`: https://github.com/pytest-dev/cookiecutter-pytest-plugin 90 | .. _`file an issue`: https://github.com/pytest-dev/pytest-stress/issues 91 | .. _`pytest`: https://github.com/pytest-dev/pytest 92 | .. _`tox`: https://tox.readthedocs.io/en/latest/ 93 | .. _`pip`: https://pypi.org/project/pip/ 94 | .. _`pipenv`: https://pypi.org/project/pipenv/ 95 | .. _`PyPI`: https://pypi.org/project 96 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import codecs 4 | import os 5 | 6 | from setuptools import setup, find_packages 7 | 8 | 9 | def read(file_name): 10 | file_path = os.path.join(os.path.dirname(__file__), file_name) 11 | return codecs.open(file_path, encoding="utf-8").read() 12 | 13 | 14 | setup( 15 | name="pytest-stress", 16 | version="1.0.1", 17 | author="Imran Mumtaz", 18 | author_email="iomumtaz@gmail.com", 19 | maintainer="Imran Mumtaz", 20 | maintainer_email="iomumtaz@gmail.com", 21 | license="MIT", 22 | url="https://pypi.org/project/pytest-stress", 23 | description="A Pytest plugin that allows you to loop tests for a user defined amount of time.", 24 | long_description=read("README.rst"), 25 | packages=find_packages(where="src"), 26 | package_dir={"": "src"}, 27 | project_urls={ 28 | 'Homepage': 'https://github.com/pytest-dev/pytest-stress', 29 | 'Source': 'https://github.com/pytest-dev/pytest-stress', 30 | 'Tracker': 'https://github.com/pytest-dev/pytest-stress/issues', 31 | }, 32 | python_requires=">=3.6", 33 | install_requires=["pytest>=3.6.0"], 34 | entry_points={"pytest11": ["pytest_stress = pytest_stress.pytest_stress"]}, 35 | classifiers=[ 36 | "Development Status :: 4 - Beta", 37 | "Framework :: Pytest", 38 | "Intended Audience :: Developers", 39 | "Topic :: Software Development :: Testing", 40 | "Programming Language :: Python", 41 | "Programming Language :: Python :: 3", 42 | "Programming Language :: Python :: 3.6", 43 | "Programming Language :: Python :: 3.7", 44 | "Programming Language :: Python :: 3.8", 45 | "Programming Language :: Python :: 3.9", 46 | "Programming Language :: Python :: 3.10", 47 | "Operating System :: OS Independent", 48 | "License :: OSI Approved :: MIT License", 49 | ], 50 | ) 51 | -------------------------------------------------------------------------------- /src/pytest_stress/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pytest-dev/pytest-stress/2f4a6fb8a3be26963854a3ed7f341668b3cdeefa/src/pytest_stress/__init__.py -------------------------------------------------------------------------------- /src/pytest_stress/pytest_stress.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import time 3 | 4 | SECONDS_IN_HOUR = 3600 5 | SECONDS_IN_MINUTE = 60 6 | SHORTEST_AMOUNT_OF_TIME = 0 7 | 8 | 9 | class InvalidTimeParameterError(Exception): 10 | pass 11 | 12 | 13 | def _get_delay_time(session): 14 | """ 15 | Helper function to extract the delay time from the session. 16 | 17 | :param session: Pytest session object. 18 | :return: Returns the delay time for each test loop. 19 | """ 20 | return session.config.option.delay 21 | 22 | 23 | def _get_total_time(session): 24 | """ 25 | Takes all the user available time options, adds them and returns it in seconds. 26 | 27 | :param session: Pytest session object. 28 | :return: Returns total amount of time in seconds. 29 | """ 30 | hours_in_seconds = session.config.option.hours * SECONDS_IN_HOUR 31 | minutes_in_seconds = session.config.option.minutes * SECONDS_IN_MINUTE 32 | seconds = session.config.option.seconds 33 | total_time = hours_in_seconds + minutes_in_seconds + seconds 34 | if total_time < SHORTEST_AMOUNT_OF_TIME: 35 | raise InvalidTimeParameterError(f"Total time cannot be less than: {SHORTEST_AMOUNT_OF_TIME}!") 36 | return total_time 37 | 38 | 39 | def _print_loop_count(count): 40 | """ 41 | Helper function to simply print out what loop number we're on. 42 | 43 | :param count: The number to print. 44 | :return: None. 45 | """ 46 | column_length = shutil.get_terminal_size().columns 47 | print("\n") 48 | print(f" Loop # {count} ".center(column_length, "=")) 49 | 50 | 51 | def _timed_out(session, start_time): 52 | """ 53 | Helper function to check if the user specified amount of time has lapsed. 54 | 55 | :param session: Pytest session object. 56 | :return: Returns True if the timeout has expired, False otherwise. 57 | """ 58 | return time.time() - start_time > _get_total_time(session) 59 | 60 | 61 | def pytest_addoption(parser): 62 | """ 63 | Add our command line options. 64 | """ 65 | stress = parser.getgroup("stress") 66 | stress.addoption( 67 | "--delay", 68 | action="store", 69 | default=0, 70 | help="The amount of time to wait between each test loop.", 71 | type=int, 72 | ) 73 | stress.addoption( 74 | "--hours", 75 | action="store", 76 | default=0, 77 | help="The number of hours to loop the tests for.", 78 | type=int, 79 | ) 80 | stress.addoption( 81 | "--minutes", 82 | action="store", 83 | default=0, 84 | help="The number of minutes to loop the tests for.", 85 | type=int, 86 | ) 87 | stress.addoption( 88 | "--seconds", 89 | action="store", 90 | default=0, 91 | help="The number of seconds to loop the tests for.", 92 | type=int, 93 | ) 94 | 95 | 96 | def pytest_runtestloop(session): 97 | """ 98 | Reimplement the test loop but loop for the user defined amount of time. 99 | 100 | Note: Check against pytest repo for any updates so we don't fall behind. 101 | """ 102 | if session.testsfailed and not session.config.option.continue_on_collection_errors: 103 | raise session.Interrupted("%d errors during collection" % session.testsfailed) 104 | 105 | if session.config.option.collectonly: 106 | return True 107 | 108 | start_time = time.time() 109 | count = 1 110 | 111 | while True: 112 | if _get_total_time(session): 113 | _print_loop_count(count) 114 | for index, item in enumerate(session.items): 115 | next_item = session.items[index + 1] if index + 1 < len(session.items) else None 116 | item.config.hook.pytest_runtest_protocol(item=item, nextitem=next_item) 117 | if session.shouldfail: 118 | raise session.Failed(session.shouldfail) 119 | if session.shouldstop: 120 | raise session.Interrupted(session.shouldstop) 121 | count += 1 122 | if _timed_out(session, start_time): 123 | break 124 | time.sleep(_get_delay_time(session)) 125 | return True 126 | -------------------------------------------------------------------------------- /tests/test_stress.py: -------------------------------------------------------------------------------- 1 | pytest_plugins = ("pytester",) 2 | 3 | 4 | def test_help_message(testdir): 5 | result = testdir.runpytest("--help") 6 | # fnmatch_lines does an assertion internally 7 | result.stdout.fnmatch_lines( 8 | [ 9 | "stress:", 10 | "*--delay=DELAY*The amount of time to wait between each test loop.", 11 | "*--hours=HOURS*The number of hours to loop the tests for.", 12 | "*--minutes=MINUTES*The number of minutes to loop the tests for.", 13 | "*--seconds=SECONDS*The number of seconds to loop the tests for.", 14 | ] 15 | ) 16 | 17 | 18 | def test_ini_file(testdir): 19 | testdir.makeini( 20 | """ 21 | [pytest] 22 | addopts = --delay=0 --hours=0 --minutes=0 --seconds=0 23 | """ 24 | ) 25 | 26 | testdir.makepyfile( 27 | """ 28 | import pytest 29 | 30 | @pytest.fixture 31 | def addopts(request): 32 | return request.config.getini('addopts') 33 | 34 | def test_ini(addopts): 35 | assert addopts[0] == "--delay=0" 36 | assert addopts[1] == "--hours=0" 37 | assert addopts[2] == "--minutes=0" 38 | assert addopts[3] == "--seconds=0" 39 | """ 40 | ) 41 | 42 | result = testdir.runpytest("-v") 43 | 44 | # fnmatch_lines does an assertion internally 45 | result.stdout.fnmatch_lines( 46 | ["*::test_ini PASSED*", ] 47 | ) 48 | 49 | # Make sure that that we get a '0' exit code for the testsuite 50 | assert result.ret == 0 51 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | # For more information about tox, see https://tox.readthedocs.io/en/latest/ 2 | [tox] 3 | envlist = py36, py37, py38, py39, py310, flake8 4 | 5 | [testenv] 6 | passenv = 7 | FORCE_COLOR 8 | commands = pytest tests --verbose 9 | deps = pytest>=3.0 10 | 11 | [testenv:flake8] 12 | basepython = python 13 | deps = flake8 14 | commands = flake8 15 | 16 | [flake8] 17 | max-line-length = 120 18 | --------------------------------------------------------------------------------