diff --git a/.Dockerignore b/.Dockerignore new file mode 100644 index 0000000..1e12da6 --- /dev/null +++ b/.Dockerignore @@ -0,0 +1,3 @@ +build* + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..2072870 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,10 @@ +FROM ubuntu:20.04 +MAINTAINER Nitrokey + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt update && apt install -qy --no-install-recommends \ + libhidapi-dev libusb-1.0-0-dev cmake gcc g++ make doxygen pkg-config alien git graphviz \ + && rm -rf /var/lib/apt/lists/* +RUN mkdir -p /app + +WORKDIR /app \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6ac2a2d --- /dev/null +++ b/Makefile @@ -0,0 +1,58 @@ +NPROC=$(shell nproc) +IMAGE_NAME=libnk-build +BUILD_DIR=build + +.PHONY: docker-build-all +docker-build-all: + @echo "Running isolated build" + $(MAKE) docker-build-image + $(MAKE) docker-build + +.PHONY: docker-build-image +docker-build-image: + sudo docker build -t $(IMAGE_NAME) . + +.PHONY: docker-build +docker-build: + sudo docker run -it -v $(PWD):/app $(IMAGE_NAME) bash -c "make ci-build" + +.PHONY: docker-clean +docker-clean: + cd $(BUILD_DIR) && $(MAKE) clean + sudo docker rmi $(IMAGE_NAME) --force + +.PHONY: ci-build +ci-build: + mkdir -p $(BUILD_DIR) && rm -rf $(BUILD_DIR)/* + cd $(BUILD_DIR) && cmake .. && $(MAKE) -j$(NPROC) package + cd $(BUILD_DIR) && ctest -VV + cd $(BUILD_DIR) && mkdir -p install && $(MAKE) install DESTDIR=install + @echo "== Results available in $(BUILD_DIR)" + @date + +.PHONY: ci-tests +ci-tests: + pip install pytest --user + pip install -r unittest/requirements.txt --user + cd unittest && python3 -m pytest -sv test_offline.py + +REPORT_NAME=libnitrokey-tests-report.html +PYTEST_ARG=-vx --randomly-dont-reorganize --template=html1/index.html --report=$(REPORT_NAME) + +tests-setup: + cd unittest && pipenv --python $(shell which python3) && pipenv install --dev + +.PHONY: tests-pro +tests-pro: + cd unittest && pipenv run pytest $(PYTEST_ARG) test_pro.py ; xdg-open $(REPORT_NAME) + +.PHONY: tests-storage +tests-storage: + cd unittest && pipenv run pytest $(PYTEST_ARG) test_pro.py + cd unittest && pipenv run pytest $(PYTEST_ARG) test_storage.py ; xdg-open $(REPORT_NAME) + +.PHONY: clean clean-all +clean: + -rm $(REPORT_NAME) + +clean-all: clean docker-clean \ No newline at end of file diff --git a/README.md b/README.md index fbd9b48..4e6ff18 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,3 @@ -[![Build Status](https://travis-ci.org/Nitrokey/libnitrokey.svg?branch=master)](https://travis-ci.org/Nitrokey/libnitrokey) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FNitrokey%2Flibnitrokey.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FNitrokey%2Flibnitrokey?ref=badge_shield) - # libnitrokey libnitrokey is a project to communicate with Nitrokey Pro and Storage devices in a clean and easy manner. Written in C++14, testable with `py.test` and `Catch` frameworks, with C API, Python access (through CFFI and C API, in future with Pybind11). @@ -29,8 +26,20 @@ Following libraries are needed to use libnitrokey on Linux (names of the package - libusb-1.0-0-dev -## Compilation +## Build libnitrokey uses CMake as its main build system. As a secondary option it offers building through Qt's qMake. + +### Docker Isolated Build + +To run a docker isolated build it suffices to run the helper command: + +```bash +# build docker image, and execute the build +$ make docker-build-all +``` + +Currently, Ubuntu 20.04 is used as a build base. Results will be placed in the `./build/` directory. + ### Qt A Qt's .pro project file is provided for direct compilation and for inclusion to other projects. Using it directly is not recommended due to lack of dependencies check and not implemented library versioning. @@ -77,7 +86,9 @@ Other build options (all take either `ON` or `OFF`): ### Meson -It is possible to use Meson and Ninja to build the project as well (currently available only `master` branch). +Note: Meson build is currently not tested. Please file a ticket in case it would not work for you. + +It is possible to use Meson and Ninja to build the project as well. Please run: ``` meson builddir @@ -92,6 +103,8 @@ pip install --user cffi # for python 2.x pip3 install cffi # for python 3.x ``` ## Python2 +Note: Python 2 is not supported anymore. + Just import it, read the C API header and it is done! You have access to the library. Here is an example (in Python 2) printing HOTP code for Pro or Storage device, assuming it is run in root directory [(full example)](python_bindings_example.py):
@@ -269,6 +282,21 @@ Please check [NK_C_API.h](NK_C_API.h) (C API) for high level commands and [libni **Warning!** Before you run unittests please change both your Admin and User PINs on your Nitrostick to defaults (`12345678` and `123456` respectively), or change the values in tests source code. If you do not change them, the tests might lock your device temporarily. If it's too late already, you can reset your Nitrokey using instructions from [homepage](https://www.nitrokey.com/de/documentation/how-reset-nitrokey). +## Helper + +Here are Python tests helper calls: + +```bash +# setup, requires pipenv installed +$ make tests-setup +# For Nitrokey Pro +$ make tests-pro +# For Nitrokey Storage +$ make tests-storage +``` + +See below for the further information. + ## Python tests libnitrokey has a great suite of tests written in Python 3 under the path: [unittest/test_*.py](https://github.com/Nitrokey/libnitrokey/tree/master/unittest): * `test_pro.py` - contains tests of OTP, Password Safe and PIN control functionality. Could be run on both Pro and Storage devices. @@ -384,14 +412,13 @@ firmware code should show how things works: (for Nitrokey Pro, for Storage similarly). # Known issues / tasks -* Currently only one device can be connected at a time (experimental work could be found in `wip-multiple_devices` branch), -* C++ API needs some reorganization to C++ objects (instead of pointers to byte arrays). This will be also preparing for integration with Pybind11, +* C++ API needs some reorganization to C++ objects (instead of pointers to byte arrays). This would be also preparation for Pybind11 integration; * Fix compilation warnings. Other tasks might be listed either in [TODO](TODO) file or on project's issues page. # License -This project is licensed under LGPL version 3. License text could be found under [LICENSE](LICENSE) file. +This project is licensed under LGPL version 3. License text can be found under [LICENSE](LICENSE) file. # Roadmap To check what issues will be fixed and when please check [milestones](https://github.com/Nitrokey/libnitrokey/milestones) page. diff --git a/unittest/Pipfile b/unittest/Pipfile new file mode 100644 index 0000000..f850d5c --- /dev/null +++ b/unittest/Pipfile @@ -0,0 +1,21 @@ +[[source]] +name = "pypi" +url = "https://pypi.org/simple" +verify_ssl = true + +[dev-packages] + +[packages] +cffi = "*" +pytest-repeat = "*" +pytest-randomly = "*" +tqdm = "*" +oath = "*" +pyexpect = "*" +pytest = "*" +pytest-reporter-html1 = "*" +pytest-reporter = "*" +hypothesis = "*" + +[requires] +python_version = "3.9" diff --git a/unittest/Pipfile.lock b/unittest/Pipfile.lock new file mode 100644 index 0000000..5cd69bd --- /dev/null +++ b/unittest/Pipfile.lock @@ -0,0 +1,311 @@ +{ + "_meta": { + "hash": { + "sha256": "d28c0452bc16be89d23169d3ee02f01c9450dffc395b010e30bfd658453cbd87" + }, + "pipfile-spec": 6, + "requires": { + "python_version": "3.9" + }, + "sources": [ + { + "name": "pypi", + "url": "https://pypi.org/simple", + "verify_ssl": true + } + ] + }, + "default": { + "ansi2html": { + "hashes": [ + "sha256:50517716d2e54f4167dadcec583fc7339e3684e4ff50a5f3d516fc8b54ce5875", + "sha256:69316be8c68ac91c5582d397c2890e69c993cc7cda52062ac7e45fcb660d8edc" + ], + "markers": "python_version >= '3.6'", + "version": "==1.7.0" + }, + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" + }, + "cffi": { + "hashes": [ + "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3", + "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2", + "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636", + "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20", + "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728", + "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27", + "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66", + "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443", + "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0", + "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7", + "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39", + "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605", + "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a", + "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37", + "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029", + "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139", + "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc", + "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df", + "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14", + "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880", + "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2", + "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a", + "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e", + "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474", + "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024", + "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8", + "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0", + "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e", + "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a", + "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e", + "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032", + "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6", + "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e", + "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b", + "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e", + "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954", + "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962", + "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c", + "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4", + "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55", + "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962", + "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023", + "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c", + "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6", + "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8", + "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382", + "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7", + "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc", + "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997", + "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796" + ], + "index": "pypi", + "version": "==1.15.0" + }, + "docutils": { + "hashes": [ + "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c", + "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==0.18.1" + }, + "htmlmin": { + "hashes": [ + "sha256:50c1ef4630374a5d723900096a961cff426dff46b48f34d194a81bbe14eca178" + ], + "version": "==0.1.12" + }, + "hypothesis": { + "hashes": [ + "sha256:92b7a7d6f0829d48bc1f87c496dfa601eb9d815549bb12daa601184807733c5b", + "sha256:9d17ebf4137e15f6db24793dad7268b8b5afcfc78b770f307c94872f1d96850a" + ], + "index": "pypi", + "version": "==6.45.0" + }, + "importlib-metadata": { + "hashes": [ + "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6", + "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539" + ], + "markers": "python_version < '3.10'", + "version": "==4.11.3" + }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, + "jinja2": { + "hashes": [ + "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119", + "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9" + ], + "markers": "python_version >= '3.7'", + "version": "==3.1.1" + }, + "markupsafe": { + "hashes": [ + "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", + "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", + "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", + "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", + "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", + "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", + "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", + "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", + "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", + "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", + "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", + "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", + "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", + "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", + "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", + "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", + "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", + "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", + "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", + "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", + "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", + "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", + "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", + "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", + "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", + "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", + "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", + "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", + "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", + "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", + "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", + "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", + "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", + "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", + "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", + "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", + "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", + "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", + "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", + "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" + ], + "markers": "python_version >= '3.7'", + "version": "==2.1.1" + }, + "oath": { + "hashes": [ + "sha256:503092f388f041f91737f6b3bd5b83e8cf3f40c7d9bc87bcfbfac33e0ae6d685", + "sha256:bd6b20d20f2c4e3f53523ee900211dca75aeeca72f4f5a9fd8dcacc175fe1c0b" + ], + "index": "pypi", + "version": "==1.4.4" + }, + "packaging": { + "hashes": [ + "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", + "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" + ], + "markers": "python_version >= '3.6'", + "version": "==21.3" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" + ], + "markers": "python_version >= '3.6'", + "version": "==1.0.0" + }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, + "pycparser": { + "hashes": [ + "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", + "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.21" + }, + "pyexpect": { + "hashes": [ + "sha256:1653b1a34b420e891d104f4d5ebac6ba313aec5bbf0feb0700c73f96a1384f8b", + "sha256:96e900d6af928a94c2a75b4935ddda44872c218121d0467c549ae19e7608a9a2" + ], + "index": "pypi", + "version": "==1.0.21" + }, + "pyparsing": { + "hashes": [ + "sha256:7bf433498c016c4314268d95df76c81b842a4cb2b276fa3312cfb1e1d85f6954", + "sha256:ef7b523f6356f763771559412c0d7134753f037822dad1b16945b7b846f7ad06" + ], + "markers": "python_full_version >= '3.6.8'", + "version": "==3.0.8" + }, + "pytest": { + "hashes": [ + "sha256:13d0e3ccfc2b6e26be000cb6568c832ba67ba32e719443bfe725814d3c42433c", + "sha256:a06a0425453864a270bc45e71f783330a7428defb4230fb5e6a731fde06ecd45" + ], + "index": "pypi", + "version": "==7.1.2" + }, + "pytest-randomly": { + "hashes": [ + "sha256:9f013b8c1923130f3d0a286fde56e1fc52cfb3547b8eedf2765c460cee979c7f", + "sha256:a3c680d2b8150cf766311a80a1f92da64c3dd819045cda834fbf1b0ac4891610" + ], + "index": "pypi", + "version": "==3.11.0" + }, + "pytest-repeat": { + "hashes": [ + "sha256:4474a7d9e9137f6d8cc8ae297f8c4168d33c56dd740aa78cfffe562557e6b96e", + "sha256:5cd3289745ab3156d43eb9c8e7f7d00a926f3ae5c9cf425bec649b2fe15bad5b" + ], + "index": "pypi", + "version": "==0.9.1" + }, + "pytest-reporter": { + "hashes": [ + "sha256:5418505ca48e9bf8a40bf28eda33a9b189a3add57422b722cb8d23f8ecf2edba", + "sha256:f5cfd4ab231e845709382bdc4c41fa97b942f3eb5caeccce0fb8e76afb6b11fb" + ], + "index": "pypi", + "version": "==0.5.2" + }, + "pytest-reporter-html1": { + "hashes": [ + "sha256:8b36cc94e0029fb093983095fd887000d0a21f1ebfa21745335fc91ee028366c", + "sha256:e99824d148e4a91e57861b0c7965935f77f9a56a1ba92f79e1320ba8c489420d" + ], + "index": "pypi", + "version": "==0.8.2" + }, + "sortedcontainers": { + "hashes": [ + "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", + "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" + ], + "version": "==2.4.0" + }, + "tomli": { + "hashes": [ + "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" + ], + "markers": "python_version >= '3.7'", + "version": "==2.0.1" + }, + "tqdm": { + "hashes": [ + "sha256:40be55d30e200777a307a7585aee69e4eabb46b4ec6a4b4a5f2d9f11e7d5408d", + "sha256:74a2cdefe14d11442cedf3ba4e21a3b84ff9a2dbdc6cfae2c34addb2a14a5ea6" + ], + "index": "pypi", + "version": "==4.64.0" + }, + "zipp": { + "hashes": [ + "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad", + "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099" + ], + "markers": "python_version >= '3.7'", + "version": "==3.8.0" + } + }, + "develop": {} +}