mirror of
https://github.com/dragonflydb/dragonfly.git
synced 2025-05-10 18:05:44 +02:00
Fix test hypothesis (#4380)
* fix:test-hypothesis --------- Co-authored-by: Roman Gershman <roman@dragonflydb.io>
This commit is contained in:
parent
99f52642c9
commit
6f0072e462
16 changed files with 1075 additions and 943 deletions
6
.github/workflows/test-fakeredis.yml
vendored
6
.github/workflows/test-fakeredis.yml
vendored
|
@ -70,7 +70,11 @@ jobs:
|
||||||
working-directory: tests/fakeredis
|
working-directory: tests/fakeredis
|
||||||
run: |
|
run: |
|
||||||
poetry run pytest test/ \
|
poetry run pytest test/ \
|
||||||
--ignore test/test_hypothesis.py \
|
--ignore test/test_hypothesis/test_hash.py \
|
||||||
|
--ignore test/test_hypothesis/test_set.py \
|
||||||
|
--ignore test/test_hypothesis/test_zset.py \
|
||||||
|
--ignore test/test_hypothesis/test_joint.py \
|
||||||
|
--ignore test/test_hypothesis/test_transaction.py \
|
||||||
--ignore test/test_mixins/test_bitmap_commands.py \
|
--ignore test/test_mixins/test_bitmap_commands.py \
|
||||||
--junit-xml=results-tests.xml --html=report-tests.html -v
|
--junit-xml=results-tests.xml --html=report-tests.html -v
|
||||||
continue-on-error: true # For now to mark the flow as successful
|
continue-on-error: true # For now to mark the flow as successful
|
||||||
|
|
232
tests/fakeredis/poetry.lock
generated
232
tests/fakeredis/poetry.lock
generated
|
@ -1,31 +1,31 @@
|
||||||
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.8.5 and should not be changed by hand.
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-timeout"
|
name = "async-timeout"
|
||||||
version = "4.0.3"
|
version = "5.0.1"
|
||||||
description = "Timeout context manager for asyncio programs"
|
description = "Timeout context manager for asyncio programs"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
{file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"},
|
||||||
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
{file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "attrs"
|
||||||
version = "24.2.0"
|
version = "24.3.0"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Classes Without Boilerplate"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "attrs-24.2.0-py3-none-any.whl", hash = "sha256:81921eb96de3191c8258c199618104dd27ac608d9366f5e35d011eae1867ede2"},
|
{file = "attrs-24.3.0-py3-none-any.whl", hash = "sha256:ac96cd038792094f438ad1f6ff80837353805ac950cd2aa0e0625ef19850c308"},
|
||||||
{file = "attrs-24.2.0.tar.gz", hash = "sha256:5cfb1b9148b5b086569baec03f20d7b6bf3bcacc9a42bebf87ffaaca362f6346"},
|
{file = "attrs-24.3.0.tar.gz", hash = "sha256:8f5c07333d543103541ba7be0e2ce16eeee8130cb0b3f9238ab904ce1e85baff"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
||||||
cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
||||||
dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
||||||
docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"]
|
||||||
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"]
|
||||||
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
|
tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"]
|
||||||
|
@ -43,73 +43,73 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "coverage"
|
name = "coverage"
|
||||||
version = "7.6.4"
|
version = "7.6.10"
|
||||||
description = "Code coverage measurement for Python"
|
description = "Code coverage measurement for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "coverage-7.6.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5f8ae553cba74085db385d489c7a792ad66f7f9ba2ee85bfa508aeb84cf0ba07"},
|
{file = "coverage-7.6.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5c912978f7fbf47ef99cec50c4401340436d200d41d714c7a4766f377c5b7b78"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8165b796df0bd42e10527a3f493c592ba494f16ef3c8b531288e3d0d72c1f6f0"},
|
{file = "coverage-7.6.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a01ec4af7dfeb96ff0078ad9a48810bb0cc8abcb0115180c6013a6b26237626c"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c8b95bf47db6d19096a5e052ffca0a05f335bc63cef281a6e8fe864d450a72"},
|
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3b204c11e2b2d883946fe1d97f89403aa1811df28ce0447439178cc7463448a"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8ed9281d1b52628e81393f5eaee24a45cbd64965f41857559c2b7ff19385df51"},
|
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:32ee6d8491fcfc82652a37109f69dee9a830e9379166cb73c16d8dc5c2915165"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0809082ee480bb8f7416507538243c8863ac74fd8a5d2485c46f0f7499f2b491"},
|
{file = "coverage-7.6.10-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675cefc4c06e3b4c876b85bfb7c59c5e2218167bbd4da5075cbe3b5790a28988"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d541423cdd416b78626b55f123412fcf979d22a2c39fce251b350de38c15c15b"},
|
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f4f620668dbc6f5e909a0946a877310fb3d57aea8198bde792aae369ee1c23b5"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:58809e238a8a12a625c70450b48e8767cff9eb67c62e6154a642b21ddf79baea"},
|
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:4eea95ef275de7abaef630c9b2c002ffbc01918b726a39f5a4353916ec72d2f3"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:c9b8e184898ed014884ca84c70562b4a82cbc63b044d366fedc68bc2b2f3394a"},
|
{file = "coverage-7.6.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e2f0280519e42b0a17550072861e0bc8a80a0870de260f9796157d3fca2733c5"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-win32.whl", hash = "sha256:6bd818b7ea14bc6e1f06e241e8234508b21edf1b242d49831831a9450e2f35fa"},
|
{file = "coverage-7.6.10-cp310-cp310-win32.whl", hash = "sha256:bc67deb76bc3717f22e765ab3e07ee9c7a5e26b9019ca19a3b063d9f4b874244"},
|
||||||
{file = "coverage-7.6.4-cp310-cp310-win_amd64.whl", hash = "sha256:06babbb8f4e74b063dbaeb74ad68dfce9186c595a15f11f5d5683f748fa1d172"},
|
{file = "coverage-7.6.10-cp310-cp310-win_amd64.whl", hash = "sha256:0f460286cb94036455e703c66988851d970fdfd8acc2a1122ab7f4f904e4029e"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:73d2b73584446e66ee633eaad1a56aad577c077f46c35ca3283cd687b7715b0b"},
|
{file = "coverage-7.6.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ea3c8f04b3e4af80e17bab607c386a830ffc2fb88a5484e1df756478cf70d1d3"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:51b44306032045b383a7a8a2c13878de375117946d68dcb54308111f39775a25"},
|
{file = "coverage-7.6.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:507a20fc863cae1d5720797761b42d2d87a04b3e5aeb682ef3b7332e90598f43"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b3fb02fe73bed561fa12d279a417b432e5b50fe03e8d663d61b3d5990f29546"},
|
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d37a84878285b903c0fe21ac8794c6dab58150e9359f1aaebbeddd6412d53132"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed8fe9189d2beb6edc14d3ad19800626e1d9f2d975e436f84e19efb7fa19469b"},
|
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a534738b47b0de1995f85f582d983d94031dffb48ab86c95bdf88dc62212142f"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b369ead6527d025a0fe7bd3864e46dbee3aa8f652d48df6174f8d0bac9e26e0e"},
|
{file = "coverage-7.6.10-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d7a2bf79378d8fb8afaa994f91bfd8215134f8631d27eba3e0e2c13546ce994"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ade3ca1e5f0ff46b678b66201f7ff477e8fa11fb537f3b55c3f0568fbfe6e718"},
|
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:6713ba4b4ebc330f3def51df1d5d38fad60b66720948112f114968feb52d3f99"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:27fb4a050aaf18772db513091c9c13f6cb94ed40eacdef8dad8411d92d9992db"},
|
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ab32947f481f7e8c763fa2c92fd9f44eeb143e7610c4ca9ecd6a36adab4081bd"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4f704f0998911abf728a7783799444fcbbe8261c4a6c166f667937ae6a8aa522"},
|
{file = "coverage-7.6.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7bbd8c8f1b115b892e34ba66a097b915d3871db7ce0e6b9901f462ff3a975377"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-win32.whl", hash = "sha256:29155cd511ee058e260db648b6182c419422a0d2e9a4fa44501898cf918866cf"},
|
{file = "coverage-7.6.10-cp311-cp311-win32.whl", hash = "sha256:299e91b274c5c9cdb64cbdf1b3e4a8fe538a7a86acdd08fae52301b28ba297f8"},
|
||||||
{file = "coverage-7.6.4-cp311-cp311-win_amd64.whl", hash = "sha256:8902dd6a30173d4ef09954bfcb24b5d7b5190cf14a43170e386979651e09ba19"},
|
{file = "coverage-7.6.10-cp311-cp311-win_amd64.whl", hash = "sha256:489a01f94aa581dbd961f306e37d75d4ba16104bbfa2b0edb21d29b73be83609"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12394842a3a8affa3ba62b0d4ab7e9e210c5e366fbac3e8b2a68636fb19892c2"},
|
{file = "coverage-7.6.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:27c6e64726b307782fa5cbe531e7647aee385a29b2107cd87ba7c0105a5d3853"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2b6b4c83d8e8ea79f27ab80778c19bc037759aea298da4b56621f4474ffeb117"},
|
{file = "coverage-7.6.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c56e097019e72c373bae32d946ecf9858fda841e48d82df7e81c63ac25554078"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d5b8007f81b88696d06f7df0cb9af0d3b835fe0c8dbf489bad70b45f0e45613"},
|
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7827a5bc7bdb197b9e066cdf650b2887597ad124dd99777332776f7b7c7d0d0"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b57b768feb866f44eeed9f46975f3d6406380275c5ddfe22f531a2bf187eda27"},
|
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:204a8238afe787323a8b47d8be4df89772d5c1e4651b9ffa808552bdf20e1d50"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5915fcdec0e54ee229926868e9b08586376cae1f5faa9bbaf8faf3561b393d52"},
|
{file = "coverage-7.6.10-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e67926f51821b8e9deb6426ff3164870976fe414d033ad90ea75e7ed0c2e5022"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0b58c672d14f16ed92a48db984612f5ce3836ae7d72cdd161001cc54512571f2"},
|
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:e78b270eadb5702938c3dbe9367f878249b5ef9a2fcc5360ac7bff694310d17b"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:2fdef0d83a2d08d69b1f2210a93c416d54e14d9eb398f6ab2f0a209433db19e1"},
|
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:714f942b9c15c3a7a5fe6876ce30af831c2ad4ce902410b7466b662358c852c0"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:8cf717ee42012be8c0cb205dbbf18ffa9003c4cbf4ad078db47b95e10748eec5"},
|
{file = "coverage-7.6.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abb02e2f5a3187b2ac4cd46b8ced85a0858230b577ccb2c62c81482ca7d18852"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-win32.whl", hash = "sha256:7bb92c539a624cf86296dd0c68cd5cc286c9eef2d0c3b8b192b604ce9de20a17"},
|
{file = "coverage-7.6.10-cp312-cp312-win32.whl", hash = "sha256:55b201b97286cf61f5e76063f9e2a1d8d2972fc2fcfd2c1272530172fd28c359"},
|
||||||
{file = "coverage-7.6.4-cp312-cp312-win_amd64.whl", hash = "sha256:1032e178b76a4e2b5b32e19d0fd0abbce4b58e77a1ca695820d10e491fa32b08"},
|
{file = "coverage-7.6.10-cp312-cp312-win_amd64.whl", hash = "sha256:e4ae5ac5e0d1e4edfc9b4b57b4cbecd5bc266a6915c500f358817a8496739247"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:023bf8ee3ec6d35af9c1c6ccc1d18fa69afa1cb29eaac57cb064dbb262a517f9"},
|
{file = "coverage-7.6.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05fca8ba6a87aabdd2d30d0b6c838b50510b56cdcfc604d40760dae7153b73d9"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:b0ac3d42cb51c4b12df9c5f0dd2f13a4f24f01943627120ec4d293c9181219ba"},
|
{file = "coverage-7.6.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9e80eba8801c386f72e0712a0453431259c45c3249f0009aff537a517b52942b"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8fe4984b431f8621ca53d9380901f62bfb54ff759a1348cd140490ada7b693c"},
|
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a372c89c939d57abe09e08c0578c1d212e7a678135d53aa16eec4430adc5e690"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fbd612f8a091954a0c8dd4c0b571b973487277d26476f8480bfa4b2a65b5d06"},
|
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ec22b5e7fe7a0fa8509181c4aac1db48f3dd4d3a566131b313d1efc102892c18"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dacbc52de979f2823a819571f2e3a350a7e36b8cb7484cdb1e289bceaf35305f"},
|
{file = "coverage-7.6.10-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:26bcf5c4df41cad1b19c84af71c22cbc9ea9a547fc973f1f2cc9a290002c8b3c"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:dab4d16dfef34b185032580e2f2f89253d302facba093d5fa9dbe04f569c4f4b"},
|
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4e4630c26b6084c9b3cb53b15bd488f30ceb50b73c35c5ad7871b869cb7365fd"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:862264b12ebb65ad8d863d51f17758b1684560b66ab02770d4f0baf2ff75da21"},
|
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2396e8116db77789f819d2bc8a7e200232b7a282c66e0ae2d2cd84581a89757e"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:5beb1ee382ad32afe424097de57134175fea3faf847b9af002cc7895be4e2a5a"},
|
{file = "coverage-7.6.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:79109c70cc0882e4d2d002fe69a24aa504dec0cc17169b3c7f41a1d341a73694"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-win32.whl", hash = "sha256:bf20494da9653f6410213424f5f8ad0ed885e01f7e8e59811f572bdb20b8972e"},
|
{file = "coverage-7.6.10-cp313-cp313-win32.whl", hash = "sha256:9e1747bab246d6ff2c4f28b4d186b205adced9f7bd9dc362051cc37c4a0c7bd6"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313-win_amd64.whl", hash = "sha256:182e6cd5c040cec0a1c8d415a87b67ed01193ed9ad458ee427741c7d8513d963"},
|
{file = "coverage-7.6.10-cp313-cp313-win_amd64.whl", hash = "sha256:254f1a3b1eef5f7ed23ef265eaa89c65c8c5b6b257327c149db1ca9d4a35f25e"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:a181e99301a0ae128493a24cfe5cfb5b488c4e0bf2f8702091473d033494d04f"},
|
{file = "coverage-7.6.10-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2ccf240eb719789cedbb9fd1338055de2761088202a9a0b73032857e53f612fe"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:df57bdbeffe694e7842092c5e2e0bc80fff7f43379d465f932ef36f027179806"},
|
{file = "coverage-7.6.10-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:0c807ca74d5a5e64427c8805de15b9ca140bba13572d6d74e262f46f50b13273"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bcd1069e710600e8e4cf27f65c90c7843fa8edfb4520fb0ccb88894cad08b11"},
|
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2bcfa46d7709b5a7ffe089075799b902020b62e7ee56ebaed2f4bdac04c508d8"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99b41d18e6b2a48ba949418db48159d7a2e81c5cc290fc934b7d2380515bd0e3"},
|
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e0de1e902669dccbf80b0415fb6b43d27edca2fbd48c74da378923b05316098"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6b1e54712ba3474f34b7ef7a41e65bd9037ad47916ccb1cc78769bae324c01a"},
|
{file = "coverage-7.6.10-cp313-cp313t-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f7b444c42bbc533aaae6b5a2166fd1a797cdb5eb58ee51a92bee1eb94a1e1cb"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53d202fd109416ce011578f321460795abfe10bb901b883cafd9b3ef851bacfc"},
|
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b330368cb99ef72fcd2dc3ed260adf67b31499584dc8a20225e85bfe6f6cfed0"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:c48167910a8f644671de9f2083a23630fbf7a1cb70ce939440cd3328e0919f70"},
|
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_i686.whl", hash = "sha256:9a7cfb50515f87f7ed30bc882f68812fd98bc2852957df69f3003d22a2aa0abf"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:cc8ff50b50ce532de2fa7a7daae9dd12f0a699bfcd47f20945364e5c31799fef"},
|
{file = "coverage-7.6.10-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:6f93531882a5f68c28090f901b1d135de61b56331bba82028489bc51bdd818d2"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-win32.whl", hash = "sha256:b8d3a03d9bfcaf5b0141d07a88456bb6a4c3ce55c080712fec8418ef3610230e"},
|
{file = "coverage-7.6.10-cp313-cp313t-win32.whl", hash = "sha256:89d76815a26197c858f53c7f6a656686ec392b25991f9e409bcef020cd532312"},
|
||||||
{file = "coverage-7.6.4-cp313-cp313t-win_amd64.whl", hash = "sha256:f3ddf056d3ebcf6ce47bdaf56142af51bb7fad09e4af310241e9db7a3a8022e1"},
|
{file = "coverage-7.6.10-cp313-cp313t-win_amd64.whl", hash = "sha256:54a5f0f43950a36312155dae55c505a76cd7f2b12d26abeebbe7a0b36dbc868d"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9cb7fa111d21a6b55cbf633039f7bc2749e74932e3aa7cb7333f675a58a58bf3"},
|
{file = "coverage-7.6.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:656c82b8a0ead8bba147de9a89bda95064874c91a3ed43a00e687f23cc19d53a"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:11a223a14e91a4693d2d0755c7a043db43d96a7450b4f356d506c2562c48642c"},
|
{file = "coverage-7.6.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccc2b70a7ed475c68ceb548bf69cec1e27305c1c2606a5eb7c3afff56a1b3b27"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a413a096c4cbac202433c850ee43fa326d2e871b24554da8327b01632673a076"},
|
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5e37dc41d57ceba70956fa2fc5b63c26dba863c946ace9705f8eca99daecdc4"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00a1d69c112ff5149cabe60d2e2ee948752c975d95f1e1096742e6077affd376"},
|
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0aa9692b4fdd83a4647eeb7db46410ea1322b5ed94cd1715ef09d1d5922ba87f"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f76846299ba5c54d12c91d776d9605ae33f8ae2b9d1d3c3703cf2db1a67f2c0"},
|
{file = "coverage-7.6.10-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa744da1820678b475e4ba3dfd994c321c5b13381d1041fe9c608620e6676e25"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:fe439416eb6380de434886b00c859304338f8b19f6f54811984f3420a2e03858"},
|
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:c0b1818063dc9e9d838c09e3a473c1422f517889436dd980f5d721899e66f315"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:0294ca37f1ba500667b1aef631e48d875ced93ad5e06fa665a3295bdd1d95111"},
|
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:59af35558ba08b758aec4d56182b222976330ef8d2feacbb93964f576a7e7a90"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:6f01ba56b1c0e9d149f9ac85a2f999724895229eb36bd997b61e62999e9b0901"},
|
{file = "coverage-7.6.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7ed2f37cfce1ce101e6dffdfd1c99e729dd2ffc291d02d3e2d0af8b53d13840d"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-win32.whl", hash = "sha256:bc66f0bf1d7730a17430a50163bb264ba9ded56739112368ba985ddaa9c3bd09"},
|
{file = "coverage-7.6.10-cp39-cp39-win32.whl", hash = "sha256:4bcc276261505d82f0ad426870c3b12cb177752834a633e737ec5ee79bbdff18"},
|
||||||
{file = "coverage-7.6.4-cp39-cp39-win_amd64.whl", hash = "sha256:c481b47f6b5845064c65a7bc78bc0860e635a9b055af0df46fdf1c58cebf8e8f"},
|
{file = "coverage-7.6.10-cp39-cp39-win_amd64.whl", hash = "sha256:457574f4599d2b00f7f637a0700a6422243b3565509457b2dbd3f50703e11f59"},
|
||||||
{file = "coverage-7.6.4-pp39.pp310-none-any.whl", hash = "sha256:3c65d37f3a9ebb703e710befdc489a38683a5b152242664b973a7b7b22348a4e"},
|
{file = "coverage-7.6.10-pp39.pp310-none-any.whl", hash = "sha256:fd34e7b3405f0cc7ab03d54a334c17a9e802897580d964bd8c2001f4b9fd488f"},
|
||||||
{file = "coverage-7.6.4.tar.gz", hash = "sha256:29fc0f17b1d3fea332f8001d4558f8214af7f1d87a345f3a133c901d60347c73"},
|
{file = "coverage-7.6.10.tar.gz", hash = "sha256:7fb105327c8f8f0682e29843e2ff96af9dcbe5bab8eeb4b398c6a33a16d80a23"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -134,13 +134,13 @@ test = ["pytest (>=6)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fakeredis"
|
name = "fakeredis"
|
||||||
version = "2.26.1"
|
version = "2.26.2"
|
||||||
description = "Python implementation of redis API, can be used for testing purposes."
|
description = "Python implementation of redis API, can be used for testing purposes."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = "<4.0,>=3.7"
|
python-versions = "<4.0,>=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "fakeredis-2.26.1-py3-none-any.whl", hash = "sha256:68a5615d7ef2529094d6958677e30a6d30d544e203a5ab852985c19d7ad57e32"},
|
{file = "fakeredis-2.26.2-py3-none-any.whl", hash = "sha256:86d4129df001efc25793cb334008160fccc98425d9f94de47884a92b63988c14"},
|
||||||
{file = "fakeredis-2.26.1.tar.gz", hash = "sha256:69f4daafe763c8014a6dbf44a17559c46643c95447b3594b3975251a171b806d"},
|
{file = "fakeredis-2.26.2.tar.gz", hash = "sha256:3ee5003a314954032b96b1365290541346c9cc24aab071b52cc983bb99ecafbf"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -160,13 +160,13 @@ probabilistic = ["pyprobables (>=0.6,<0.7)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hypothesis"
|
name = "hypothesis"
|
||||||
version = "6.115.5"
|
version = "6.123.2"
|
||||||
description = "A library for property-based testing"
|
description = "A library for property-based testing"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.9"
|
python-versions = ">=3.9"
|
||||||
files = [
|
files = [
|
||||||
{file = "hypothesis-6.115.5-py3-none-any.whl", hash = "sha256:b7733459ae9a93020fac3b91b41473c9b85e975139a152a70d88f3a5caa3fa3f"},
|
{file = "hypothesis-6.123.2-py3-none-any.whl", hash = "sha256:0a8bf07753f1436f1b8697a13ea955f3fef3ef7b477c2972869b1d142bcdb30e"},
|
||||||
{file = "hypothesis-6.115.5.tar.gz", hash = "sha256:4768c5fb426b305462ed31032d6e216a31daaefb1dc3134fdf2795b7961d7cb3"},
|
{file = "hypothesis-6.123.2.tar.gz", hash = "sha256:02c25552783764146b191c69eef69d8375827b58a75074055705ab8fdbc95fc5"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -175,10 +175,10 @@ exceptiongroup = {version = ">=1.0.0", markers = "python_version < \"3.11\""}
|
||||||
sortedcontainers = ">=2.1.0,<3.0.0"
|
sortedcontainers = ">=2.1.0,<3.0.0"
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
all = ["black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.74)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.16)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.19.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"]
|
all = ["black (>=19.10b0)", "click (>=7.0)", "crosshair-tool (>=0.0.78)", "django (>=4.2)", "dpcontracts (>=0.4)", "hypothesis-crosshair (>=0.0.18)", "lark (>=0.10.1)", "libcst (>=0.3.16)", "numpy (>=1.19.3)", "pandas (>=1.1)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2024.2)"]
|
||||||
cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"]
|
cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"]
|
||||||
codemods = ["libcst (>=0.3.16)"]
|
codemods = ["libcst (>=0.3.16)"]
|
||||||
crosshair = ["crosshair-tool (>=0.0.74)", "hypothesis-crosshair (>=0.0.16)"]
|
crosshair = ["crosshair-tool (>=0.0.78)", "hypothesis-crosshair (>=0.0.18)"]
|
||||||
dateutil = ["python-dateutil (>=1.4)"]
|
dateutil = ["python-dateutil (>=1.4)"]
|
||||||
django = ["django (>=4.2)"]
|
django = ["django (>=4.2)"]
|
||||||
dpcontracts = ["dpcontracts (>=0.4)"]
|
dpcontracts = ["dpcontracts (>=0.4)"]
|
||||||
|
@ -204,13 +204,13 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jinja2"
|
name = "jinja2"
|
||||||
version = "3.1.4"
|
version = "3.1.5"
|
||||||
description = "A very fast and expressive template engine."
|
description = "A very fast and expressive template engine."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "jinja2-3.1.4-py3-none-any.whl", hash = "sha256:bc5dd2abb727a5319567b7a813e6a2e7318c39f4f487cfe6c89c6f9c7d25197d"},
|
{file = "jinja2-3.1.5-py3-none-any.whl", hash = "sha256:aba0f4dc9ed8013c424088f68a5c226f7d6097ed89b246d7749c2ec4175c6adb"},
|
||||||
{file = "jinja2-3.1.4.tar.gz", hash = "sha256:4a3aee7acbbe7303aede8e9648d13b8bf88a429282aa6122a993f0ac800cb369"},
|
{file = "jinja2-3.1.5.tar.gz", hash = "sha256:8fefff8dc3034e27bb80d67c671eb8a9bc424c0ef4c0826edbff304cceff43bb"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -227,6 +227,8 @@ optional = false
|
||||||
python-versions = "*"
|
python-versions = "*"
|
||||||
files = [
|
files = [
|
||||||
{file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"},
|
{file = "jsonpath-ng-1.7.0.tar.gz", hash = "sha256:f6f5f7fd4e5ff79c785f1573b394043b39849fb2bb47bcead935d12b00beab3c"},
|
||||||
|
{file = "jsonpath_ng-1.7.0-py2-none-any.whl", hash = "sha256:898c93fc173f0c336784a3fa63d7434297544b7198124a68f9a3ef9597b0ae6e"},
|
||||||
|
{file = "jsonpath_ng-1.7.0-py3-none-any.whl", hash = "sha256:f3d7f9e848cba1b6da28c55b1c26ff915dc9e0b1ba7e752a53d6da8d5cbd00b6"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -399,13 +401,13 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "24.1"
|
version = "24.2"
|
||||||
description = "Core utilities for Python packages"
|
description = "Core utilities for Python packages"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"},
|
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||||
{file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"},
|
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -436,24 +438,24 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyprobables"
|
name = "pyprobables"
|
||||||
version = "0.6.0"
|
version = "0.6.1"
|
||||||
description = "Probabilistic data structures in python"
|
description = "Probabilistic data structures in python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pyprobables-0.6.0-py3-none-any.whl", hash = "sha256:85a23655e2d5e87a99e01003be8b48e9dfcc0c45e65c02f84f777b99235347db"},
|
{file = "pyprobables-0.6.1-py3-none-any.whl", hash = "sha256:090d0c973f9e160f15927e8eb911dabf126285a7a1ecd478b7a9e04149e28392"},
|
||||||
{file = "pyprobables-0.6.0.tar.gz", hash = "sha256:a4e72bdb4d3513121b33377728c9eafd2ae8495d5201d6a90abc3d52d9a17901"},
|
{file = "pyprobables-0.6.1.tar.gz", hash = "sha256:64b4d165d51beff05e716c01231c8a5503297844e58adee8771e5e7af130321d"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pytest"
|
name = "pytest"
|
||||||
version = "8.3.3"
|
version = "8.3.4"
|
||||||
description = "pytest: simple powerful testing with Python"
|
description = "pytest: simple powerful testing with Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
|
{file = "pytest-8.3.4-py3-none-any.whl", hash = "sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6"},
|
||||||
{file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
|
{file = "pytest-8.3.4.tar.gz", hash = "sha256:965370d062bce11e73868e0335abac31b4d3de0e82f4007408d242b4f8610761"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -573,13 +575,13 @@ pytest = ">=7.0.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redis"
|
name = "redis"
|
||||||
version = "5.2.0"
|
version = "5.2.1"
|
||||||
description = "Python client for Redis database and key-value store"
|
description = "Python client for Redis database and key-value store"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "redis-5.2.0-py3-none-any.whl", hash = "sha256:ae174f2bb3b1bf2b09d54bf3e51fbc1469cf6c10aa03e21141f51969801a7897"},
|
{file = "redis-5.2.1-py3-none-any.whl", hash = "sha256:ee7e1056b9aea0f04c6c2ed59452947f34c4940ee025f5dd83e6a6418b6989e4"},
|
||||||
{file = "redis-5.2.0.tar.gz", hash = "sha256:0b1087665a771b1ff2e003aa5bdd354f15a70c9e25d5a7dbf9c722c16528a7b0"},
|
{file = "redis-5.2.1.tar.gz", hash = "sha256:16f2e22dff21d5125e8481515e386711a34cbec50f0e44413dd7d9c060a54e0f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
|
@ -602,13 +604,43 @@ files = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tomli"
|
name = "tomli"
|
||||||
version = "2.0.2"
|
version = "2.2.1"
|
||||||
description = "A lil' TOML parser"
|
description = "A lil' TOML parser"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "tomli-2.0.2-py3-none-any.whl", hash = "sha256:2ebe24485c53d303f690b0ec092806a085f07af5a5aa1464f3931eec36caaa38"},
|
{file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"},
|
||||||
{file = "tomli-2.0.2.tar.gz", hash = "sha256:d46d457a85337051c36524bc5349dd91b1877838e2979ac5ced3e710ed8a60ed"},
|
{file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ece47d672db52ac607a3d9599a9d48dcb2f2f735c6c2d1f34130085bb12b112a"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6972ca9c9cc9f0acaa56a8ca1ff51e7af152a9f87fb64623e31d5c83700080ee"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c954d2250168d28797dd4e3ac5cf812a406cd5a92674ee4c8f123c889786aa8e"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8dd28b3e155b80f4d54beb40a441d366adcfe740969820caf156c019fb5c7ec4"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:e59e304978767a54663af13c07b3d1af22ddee3bb2fb0618ca1593e4f593a106"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:33580bccab0338d00994d7f16f4c4ec25b776af3ffaac1ed74e0b3fc95e885a8"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-win32.whl", hash = "sha256:465af0e0875402f1d226519c9904f37254b3045fc5084697cefb9bdde1ff99ff"},
|
||||||
|
{file = "tomli-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:2d0f2fdd22b02c6d81637a3c95f8cd77f995846af7414c5c4b8d0545afa1bc4b"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:4a8f6e44de52d5e6c657c9fe83b562f5f4256d8ebbfe4ff922c495620a7f6cea"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8d57ca8095a641b8237d5b079147646153d22552f1c637fd3ba7f4b0b29167a8"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4e340144ad7ae1533cb897d406382b4b6fede8890a03738ff1683af800d54192"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db2b95f9de79181805df90bedc5a5ab4c165e6ec3fe99f970d0e302f384ad222"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40741994320b232529c802f8bc86da4e1aa9f413db394617b9a256ae0f9a7f77"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:400e720fe168c0f8521520190686ef8ef033fb19fc493da09779e592861b78c6"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:02abe224de6ae62c19f090f68da4e27b10af2b93213d36cf44e6e1c5abd19fdd"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:b82ebccc8c8a36f2094e969560a1b836758481f3dc360ce9a3277c65f374285e"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-win32.whl", hash = "sha256:889f80ef92701b9dbb224e49ec87c645ce5df3fa2cc548664eb8a25e03127a98"},
|
||||||
|
{file = "tomli-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:7fc04e92e1d624a4a63c76474610238576942d6b8950a2d7f908a340494e67e4"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f4039b9cbc3048b2416cc57ab3bda989a6fcf9b36cf8937f01a6e731b64f80d7"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:286f0ca2ffeeb5b9bd4fcc8d6c330534323ec51b2f52da063b11c502da16f30c"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a92ef1a44547e894e2a17d24e7557a5e85a9e1d0048b0b5e7541f76c5032cb13"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9316dc65bed1684c9a98ee68759ceaed29d229e985297003e494aa825ebb0281"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e85e99945e688e32d5a35c1ff38ed0b3f41f43fad8df0bdf79f72b2ba7bc5272"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:ac065718db92ca818f8d6141b5f66369833d4a80a9d74435a268c52bdfa73140"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:d920f33822747519673ee656a4b6ac33e382eca9d331c87770faa3eef562aeb2"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a198f10c4d1b1375d7687bc25294306e551bf1abfa4eace6650070a5c1ae2744"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-win32.whl", hash = "sha256:d3f5614314d758649ab2ab3a62d4f2004c825922f9e370b29416484086b264ec"},
|
||||||
|
{file = "tomli-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:a38aa0308e754b0e3c67e344754dff64999ff9b513e691d0e786265c93583c69"},
|
||||||
|
{file = "tomli-2.2.1-py3-none-any.whl", hash = "sha256:cb55c73c5f4408779d0cf3eef9f762b9c9f147a77de7b258bef0a5628adc85cc"},
|
||||||
|
{file = "tomli-2.2.1.tar.gz", hash = "sha256:cd45e1dc79c835ce60f7404ec8119f2eb06d38b1deba146f07ced3bbc44505ff"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -1,842 +0,0 @@
|
||||||
import functools
|
|
||||||
import math
|
|
||||||
import operator
|
|
||||||
import sys
|
|
||||||
from typing import Any, Tuple, Union
|
|
||||||
|
|
||||||
import hypothesis
|
|
||||||
import hypothesis.stateful
|
|
||||||
import hypothesis.strategies as st
|
|
||||||
import pytest
|
|
||||||
import redis
|
|
||||||
from hypothesis.stateful import rule, initialize, precondition
|
|
||||||
from hypothesis.strategies import SearchStrategy
|
|
||||||
|
|
||||||
import fakeredis
|
|
||||||
from fakeredis._server import _create_version
|
|
||||||
|
|
||||||
self_strategy = st.runner()
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def sample_attr(draw, name):
|
|
||||||
"""Strategy for sampling a specific attribute from a state machine"""
|
|
||||||
machine = draw(self_strategy)
|
|
||||||
values = getattr(machine, name)
|
|
||||||
position = draw(st.integers(min_value=0, max_value=len(values) - 1))
|
|
||||||
return values[position]
|
|
||||||
|
|
||||||
|
|
||||||
def server_info() -> Tuple[str, Union[None, Tuple[int, ...]]]:
|
|
||||||
"""Returns server's version or None if server is not running"""
|
|
||||||
client = None
|
|
||||||
try:
|
|
||||||
client = redis.Redis("localhost", port=6380, db=2)
|
|
||||||
client_info = client.info()
|
|
||||||
server_type = "dragonfly" if "dragonfly_version" in client_info else "redis"
|
|
||||||
server_version = (
|
|
||||||
client_info["redis_version"] if server_type != "dragonfly" else (7, 0)
|
|
||||||
)
|
|
||||||
server_version = _create_version(server_version) or (7,)
|
|
||||||
return server_type, server_version
|
|
||||||
except redis.ConnectionError as e:
|
|
||||||
print(e)
|
|
||||||
pytest.exit("Redis is not running")
|
|
||||||
return "redis", (6,)
|
|
||||||
finally:
|
|
||||||
if hasattr(client, "close"):
|
|
||||||
client.close() # Absent in older versions of redis-py
|
|
||||||
|
|
||||||
|
|
||||||
server_type, redis_ver = server_info()
|
|
||||||
|
|
||||||
keys = sample_attr("keys")
|
|
||||||
fields = sample_attr("fields")
|
|
||||||
values = sample_attr("values")
|
|
||||||
scores = sample_attr("scores")
|
|
||||||
|
|
||||||
int_as_bytes = st.builds(lambda x: str(default_normalize(x)).encode(), st.integers())
|
|
||||||
float_as_bytes = st.builds(
|
|
||||||
lambda x: repr(default_normalize(x)).encode(), st.floats(width=32)
|
|
||||||
)
|
|
||||||
counts = st.integers(min_value=-3, max_value=3) | st.integers()
|
|
||||||
limits = st.just(()) | st.tuples(st.just("limit"), counts, counts)
|
|
||||||
# Redis has an integer overflow bug in swapdb, so we confine the numbers to
|
|
||||||
# a limited range (https://github.com/antirez/redis/issues/5737).
|
|
||||||
dbnums = st.integers(min_value=0, max_value=3) | st.integers(
|
|
||||||
min_value=-1000, max_value=1000
|
|
||||||
)
|
|
||||||
# The filter is to work around https://github.com/antirez/redis/issues/5632
|
|
||||||
patterns = st.text(
|
|
||||||
alphabet=st.sampled_from("[]^$*.?-azAZ\\\r\n\t")
|
|
||||||
) | st.binary().filter(lambda x: b"\0" not in x)
|
|
||||||
score_tests = scores | st.builds(lambda x: b"(" + repr(x).encode(), scores)
|
|
||||||
string_tests = st.sampled_from([b"+", b"-"]) | st.builds(
|
|
||||||
operator.add, st.sampled_from([b"(", b"["]), fields
|
|
||||||
)
|
|
||||||
# Redis has integer overflow bugs in time computations, which is why we set a maximum.
|
|
||||||
expires_seconds = st.integers(min_value=100000, max_value=10000000000)
|
|
||||||
expires_ms = st.integers(min_value=100000000, max_value=10000000000000)
|
|
||||||
|
|
||||||
|
|
||||||
class WrappedException:
|
|
||||||
"""Wraps an exception for the purposes of comparison."""
|
|
||||||
|
|
||||||
def __init__(self, exc):
|
|
||||||
self.wrapped = exc
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return str(self.wrapped)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return "WrappedException({!r})".format(self.wrapped)
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, WrappedException):
|
|
||||||
return NotImplemented
|
|
||||||
if type(self.wrapped) != type(other.wrapped): # noqa: E721
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
# return self.wrapped.args == other.wrapped.args
|
|
||||||
|
|
||||||
def __ne__(self, other):
|
|
||||||
if not isinstance(other, WrappedException):
|
|
||||||
return NotImplemented
|
|
||||||
return not self == other
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_exceptions(obj):
|
|
||||||
if isinstance(obj, list):
|
|
||||||
return [wrap_exceptions(item) for item in obj]
|
|
||||||
elif isinstance(obj, Exception):
|
|
||||||
return WrappedException(obj)
|
|
||||||
else:
|
|
||||||
return obj
|
|
||||||
|
|
||||||
|
|
||||||
def sort_list(lst):
|
|
||||||
if isinstance(lst, list):
|
|
||||||
return sorted(lst)
|
|
||||||
else:
|
|
||||||
return lst
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_if_number(x):
|
|
||||||
try:
|
|
||||||
res = float(x)
|
|
||||||
return x if math.isnan(res) else res
|
|
||||||
except ValueError:
|
|
||||||
return x
|
|
||||||
|
|
||||||
|
|
||||||
def flatten(args):
|
|
||||||
if isinstance(args, (list, tuple)):
|
|
||||||
for arg in args:
|
|
||||||
yield from flatten(arg)
|
|
||||||
elif args is not None:
|
|
||||||
yield args
|
|
||||||
|
|
||||||
|
|
||||||
def default_normalize(x: Any) -> Any:
|
|
||||||
if redis_ver >= (7,) and (isinstance(x, float) or isinstance(x, int)):
|
|
||||||
return 0 + x
|
|
||||||
|
|
||||||
return x
|
|
||||||
|
|
||||||
|
|
||||||
class Command:
|
|
||||||
def __init__(self, *args):
|
|
||||||
args = list(flatten(args))
|
|
||||||
args = [default_normalize(x) for x in args]
|
|
||||||
self.args = tuple(args)
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
parts = [repr(arg) for arg in self.args]
|
|
||||||
return "Command({})".format(", ".join(parts))
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def encode(arg):
|
|
||||||
encoder = redis.connection.Encoder("utf-8", "replace", False)
|
|
||||||
return encoder.encode(arg)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def normalize(self):
|
|
||||||
command = self.encode(self.args[0]).lower() if self.args else None
|
|
||||||
# Functions that return a list in arbitrary order
|
|
||||||
unordered = {
|
|
||||||
b"keys",
|
|
||||||
b"sort",
|
|
||||||
b"hgetall",
|
|
||||||
b"hkeys",
|
|
||||||
b"hvals",
|
|
||||||
b"sdiff",
|
|
||||||
b"sinter",
|
|
||||||
b"sunion",
|
|
||||||
b"smembers",
|
|
||||||
}
|
|
||||||
if command in unordered:
|
|
||||||
return sort_list
|
|
||||||
else:
|
|
||||||
return normalize_if_number
|
|
||||||
|
|
||||||
@property
|
|
||||||
def testable(self):
|
|
||||||
"""Whether this command is suitable for a test.
|
|
||||||
|
|
||||||
The fuzzer can create commands with behaviour that is
|
|
||||||
non-deterministic, not supported, or which hits redis bugs.
|
|
||||||
"""
|
|
||||||
N = len(self.args)
|
|
||||||
if N == 0:
|
|
||||||
return False
|
|
||||||
command = self.encode(self.args[0]).lower()
|
|
||||||
if not command.split():
|
|
||||||
return False
|
|
||||||
if command == b"keys" and N == 2 and self.args[1] != b"*":
|
|
||||||
return False
|
|
||||||
# Redis will ignore a NULL character in some commands but not others,
|
|
||||||
# e.g., it recognises EXEC\0 but not MULTI\00.
|
|
||||||
# Rather than try to reproduce this quirky behavior, just skip these tests.
|
|
||||||
if b"\0" in command:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def commands(*args, **kwargs):
|
|
||||||
return st.builds(functools.partial(Command, **kwargs), *args)
|
|
||||||
|
|
||||||
|
|
||||||
# # TODO: all expiry-related commands
|
|
||||||
common_commands = (
|
|
||||||
commands(st.sampled_from(["del", "persist", "type", "unlink"]), keys)
|
|
||||||
| commands(st.just("exists"), st.lists(keys))
|
|
||||||
| commands(st.just("keys"), st.just("*"))
|
|
||||||
# Disabled for now due to redis giving wrong answers
|
|
||||||
# (https://github.com/antirez/redis/issues/5632)
|
|
||||||
# | commands(st.just('keys'), patterns)
|
|
||||||
| commands(st.just("move"), keys, dbnums)
|
|
||||||
| commands(st.sampled_from(["rename", "renamenx"]), keys, keys)
|
|
||||||
# TODO: find a better solution to sort instability than throwing
|
|
||||||
# away the sort entirely with normalize. This also prevents us
|
|
||||||
# using LIMIT.
|
|
||||||
| commands(
|
|
||||||
st.just("sort"),
|
|
||||||
keys,
|
|
||||||
st.none() | st.just("asc"),
|
|
||||||
st.none() | st.just("desc"),
|
|
||||||
st.none() | st.just("alpha"),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def build_zstore(command, dest, sources, weights, aggregate):
|
|
||||||
args = [command, dest, len(sources)]
|
|
||||||
args += [source[0] for source in sources]
|
|
||||||
if weights:
|
|
||||||
args.append("weights")
|
|
||||||
args += [source[1] for source in sources]
|
|
||||||
if aggregate:
|
|
||||||
args += ["aggregate", aggregate]
|
|
||||||
return Command(args)
|
|
||||||
|
|
||||||
|
|
||||||
zset_no_score_create_commands = commands(
|
|
||||||
st.just("zadd"), keys, st.lists(st.tuples(st.just(0), fields), min_size=1)
|
|
||||||
)
|
|
||||||
zset_no_score_commands = ( # TODO: test incr
|
|
||||||
commands(
|
|
||||||
st.just("zadd"),
|
|
||||||
keys,
|
|
||||||
st.none() | st.just("nx"),
|
|
||||||
st.none() | st.just("xx"),
|
|
||||||
st.none() | st.just("ch"),
|
|
||||||
st.none() | st.just("incr"),
|
|
||||||
st.lists(st.tuples(st.just(0), fields)),
|
|
||||||
)
|
|
||||||
| commands(st.just("zlexcount"), keys, string_tests, string_tests)
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["zrangebylex", "zrevrangebylex"]),
|
|
||||||
keys,
|
|
||||||
string_tests,
|
|
||||||
string_tests,
|
|
||||||
limits,
|
|
||||||
)
|
|
||||||
| commands(st.just("zremrangebylex"), keys, string_tests, string_tests)
|
|
||||||
)
|
|
||||||
|
|
||||||
bad_commands = (
|
|
||||||
# redis-py splits the command on spaces, and hangs if that ends up
|
|
||||||
# being an empty list
|
|
||||||
commands(
|
|
||||||
st.text().filter(lambda x: bool(x.split())), st.lists(st.binary() | st.text())
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
attrs = st.fixed_dictionaries(
|
|
||||||
{
|
|
||||||
"keys": st.lists(st.binary(), min_size=2, max_size=5, unique=True),
|
|
||||||
"fields": st.lists(st.binary(), min_size=2, max_size=5, unique=True),
|
|
||||||
"values": st.lists(
|
|
||||||
st.binary() | int_as_bytes | float_as_bytes,
|
|
||||||
min_size=2,
|
|
||||||
max_size=5,
|
|
||||||
unique=True,
|
|
||||||
),
|
|
||||||
"scores": st.lists(st.floats(width=32), min_size=2, max_size=5, unique=True),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@hypothesis.settings(max_examples=1000)
|
|
||||||
class CommonMachine(hypothesis.stateful.RuleBasedStateMachine):
|
|
||||||
create_command_strategy = st.nothing()
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
try:
|
|
||||||
self.real = redis.StrictRedis("localhost", port=6380, db=2)
|
|
||||||
self.real.ping()
|
|
||||||
except redis.ConnectionError:
|
|
||||||
pytest.skip("redis is not running")
|
|
||||||
if self.real.info("server").get("arch_bits") != 64:
|
|
||||||
self.real.connection_pool.disconnect()
|
|
||||||
pytest.skip("redis server is not 64-bit")
|
|
||||||
self.fake = fakeredis.FakeStrictRedis(
|
|
||||||
server=fakeredis.FakeServer(version=redis_ver), port=6380, db=2
|
|
||||||
)
|
|
||||||
# Disable the response parsing so that we can check the raw values returned
|
|
||||||
self.fake.response_callbacks.clear()
|
|
||||||
self.real.response_callbacks.clear()
|
|
||||||
self.transaction_normalize = []
|
|
||||||
self.keys = []
|
|
||||||
self.fields = []
|
|
||||||
self.values = []
|
|
||||||
self.scores = []
|
|
||||||
self.initialized_data = False
|
|
||||||
try:
|
|
||||||
self.real.execute_command("discard")
|
|
||||||
except redis.ResponseError:
|
|
||||||
pass
|
|
||||||
self.real.flushall()
|
|
||||||
|
|
||||||
def teardown(self):
|
|
||||||
self.real.connection_pool.disconnect()
|
|
||||||
self.fake.connection_pool.disconnect()
|
|
||||||
super().teardown()
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _evaluate(client, command):
|
|
||||||
try:
|
|
||||||
result = client.execute_command(*command.args)
|
|
||||||
if result != "QUEUED":
|
|
||||||
result = command.normalize(result)
|
|
||||||
exc = None
|
|
||||||
except Exception as e:
|
|
||||||
result = exc = e
|
|
||||||
return wrap_exceptions(result), exc
|
|
||||||
|
|
||||||
def _compare(self, command):
|
|
||||||
fake_result, fake_exc = self._evaluate(self.fake, command)
|
|
||||||
real_result, real_exc = self._evaluate(self.real, command)
|
|
||||||
|
|
||||||
if fake_exc is not None and real_exc is None:
|
|
||||||
print(
|
|
||||||
"{} raised on only on fake when running {}".format(fake_exc, command),
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
raise fake_exc
|
|
||||||
elif real_exc is not None and fake_exc is None:
|
|
||||||
assert real_exc == fake_exc, "Expected exception {} not raised".format(
|
|
||||||
real_exc
|
|
||||||
)
|
|
||||||
elif (
|
|
||||||
real_exc is None
|
|
||||||
and isinstance(real_result, list)
|
|
||||||
and command.args
|
|
||||||
and command.args[0].lower() == "exec"
|
|
||||||
):
|
|
||||||
assert fake_result is not None
|
|
||||||
# Transactions need to use the normalize functions of the
|
|
||||||
# component commands.
|
|
||||||
assert len(self.transaction_normalize) == len(real_result)
|
|
||||||
assert len(self.transaction_normalize) == len(fake_result)
|
|
||||||
for n, r, f in zip(self.transaction_normalize, real_result, fake_result):
|
|
||||||
assert n(f) == n(r)
|
|
||||||
self.transaction_normalize = []
|
|
||||||
else:
|
|
||||||
assert fake_result == real_result or (
|
|
||||||
type(fake_result) is float and fake_result == pytest.approx(real_result)
|
|
||||||
), "Discrepancy when running command {}, fake({}) != real({})".format(
|
|
||||||
command, fake_result, real_result
|
|
||||||
)
|
|
||||||
if real_result == b"QUEUED":
|
|
||||||
# Since redis removes the distinction between simple strings and
|
|
||||||
# bulk strings, this might not actually indicate that we're in a
|
|
||||||
# transaction. But it is extremely unlikely that hypothesis will
|
|
||||||
# find such examples.
|
|
||||||
self.transaction_normalize.append(command.normalize)
|
|
||||||
if len(command.args) == 1 and Command.encode(command.args[0]).lower() in (
|
|
||||||
b"discard",
|
|
||||||
b"exec",
|
|
||||||
):
|
|
||||||
self.transaction_normalize = []
|
|
||||||
|
|
||||||
@initialize(attrs=attrs)
|
|
||||||
def init_attrs(self, attrs):
|
|
||||||
for key, value in attrs.items():
|
|
||||||
setattr(self, key, value)
|
|
||||||
|
|
||||||
# hypothesis doesn't allow ordering of @initialize, so we have to put
|
|
||||||
# preconditions on rules to ensure we call init_data exactly once and
|
|
||||||
# after init_attrs.
|
|
||||||
@precondition(lambda self: not self.initialized_data)
|
|
||||||
@rule(
|
|
||||||
commands=self_strategy.flatmap(
|
|
||||||
lambda self: st.lists(self.create_command_strategy)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
def init_data(self, commands):
|
|
||||||
for command in commands:
|
|
||||||
self._compare(command)
|
|
||||||
self.initialized_data = True
|
|
||||||
|
|
||||||
@precondition(lambda self: self.initialized_data)
|
|
||||||
@rule(command=self_strategy.flatmap(lambda self: self.command_strategy))
|
|
||||||
def one_command(self, command):
|
|
||||||
self._compare(command)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseTest:
|
|
||||||
"""Base class for test classes."""
|
|
||||||
|
|
||||||
command_strategy: SearchStrategy
|
|
||||||
create_command_strategy = st.nothing()
|
|
||||||
command_strategy_redis7 = st.nothing()
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
|
||||||
def test(self):
|
|
||||||
class Machine(CommonMachine):
|
|
||||||
create_command_strategy = self.create_command_strategy
|
|
||||||
command_strategy = (
|
|
||||||
self.command_strategy | self.command_strategy_redis7
|
|
||||||
if redis_ver >= (7,)
|
|
||||||
else self.command_strategy
|
|
||||||
)
|
|
||||||
|
|
||||||
hypothesis.settings.register_profile(
|
|
||||||
"debug", max_examples=10, verbosity=hypothesis.Verbosity.debug
|
|
||||||
)
|
|
||||||
hypothesis.settings.load_profile("debug")
|
|
||||||
hypothesis.stateful.run_state_machine_as_test(Machine)
|
|
||||||
|
|
||||||
|
|
||||||
class TestConnection(BaseTest):
|
|
||||||
# TODO: tests for select
|
|
||||||
connection_commands = (
|
|
||||||
commands(st.just("echo"), values)
|
|
||||||
| commands(st.just("ping"), st.lists(values, max_size=2))
|
|
||||||
| commands(st.just("swapdb"), dbnums, dbnums)
|
|
||||||
)
|
|
||||||
command_strategy = connection_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestString(BaseTest):
|
|
||||||
string_commands = (
|
|
||||||
commands(st.just("append"), keys, values)
|
|
||||||
| commands(st.just("bitcount"), keys)
|
|
||||||
| commands(st.just("bitcount"), keys, values, values)
|
|
||||||
| commands(st.sampled_from(["incr", "decr"]), keys)
|
|
||||||
| commands(st.sampled_from(["incrby", "decrby"]), keys, values)
|
|
||||||
| commands(st.just("get"), keys)
|
|
||||||
| commands(st.just("getbit"), keys, counts)
|
|
||||||
| commands(
|
|
||||||
st.just("setbit"),
|
|
||||||
keys,
|
|
||||||
counts,
|
|
||||||
st.integers(min_value=0, max_value=1) | st.integers(),
|
|
||||||
)
|
|
||||||
| commands(st.sampled_from(["substr", "getrange"]), keys, counts, counts)
|
|
||||||
| commands(st.just("getset"), keys, values)
|
|
||||||
| commands(st.just("mget"), st.lists(keys))
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["mset", "msetnx"]), st.lists(st.tuples(keys, values))
|
|
||||||
)
|
|
||||||
| commands(
|
|
||||||
st.just("set"),
|
|
||||||
keys,
|
|
||||||
values,
|
|
||||||
st.none() | st.just("nx"),
|
|
||||||
st.none() | st.just("xx"),
|
|
||||||
st.none() | st.just("keepttl"),
|
|
||||||
)
|
|
||||||
| commands(st.just("setex"), keys, expires_seconds, values)
|
|
||||||
| commands(st.just("psetex"), keys, expires_ms, values)
|
|
||||||
| commands(st.just("setnx"), keys, values)
|
|
||||||
| commands(st.just("setrange"), keys, counts, values)
|
|
||||||
| commands(st.just("strlen"), keys)
|
|
||||||
)
|
|
||||||
create_command_strategy = commands(st.just("set"), keys, values)
|
|
||||||
command_strategy = string_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestHash(BaseTest):
|
|
||||||
hash_commands = (
|
|
||||||
commands(st.just("hset"), keys, st.lists(st.tuples(fields, values)))
|
|
||||||
| commands(st.just("hdel"), keys, st.lists(fields))
|
|
||||||
| commands(st.just("hexists"), keys, fields)
|
|
||||||
| commands(st.just("hget"), keys, fields)
|
|
||||||
| commands(st.sampled_from(["hgetall", "hkeys", "hvals"]), keys)
|
|
||||||
| commands(st.just("hincrby"), keys, fields, st.integers())
|
|
||||||
| commands(st.just("hlen"), keys)
|
|
||||||
| commands(st.just("hmget"), keys, st.lists(fields))
|
|
||||||
| commands(st.just("hset"), keys, st.lists(st.tuples(fields, values)))
|
|
||||||
| commands(st.just("hsetnx"), keys, fields, values)
|
|
||||||
| commands(st.just("hstrlen"), keys, fields)
|
|
||||||
)
|
|
||||||
command_strategy_redis7 = (
|
|
||||||
commands(
|
|
||||||
st.just("hpersist"),
|
|
||||||
st.just("fields"),
|
|
||||||
st.just(2),
|
|
||||||
st.lists(fields, min_size=2, max_size=2),
|
|
||||||
)
|
|
||||||
| commands(
|
|
||||||
st.just("hexpiretime"),
|
|
||||||
st.just("fields"),
|
|
||||||
st.just(2),
|
|
||||||
st.lists(fields, min_size=2, max_size=2),
|
|
||||||
)
|
|
||||||
| commands(
|
|
||||||
st.just("hpexpiretime"),
|
|
||||||
st.just("fields"),
|
|
||||||
st.just(2),
|
|
||||||
st.lists(fields, min_size=2, max_size=2),
|
|
||||||
)
|
|
||||||
| commands(
|
|
||||||
st.just("hexpire"),
|
|
||||||
keys,
|
|
||||||
expires_seconds,
|
|
||||||
st.none() | st.just("nx"),
|
|
||||||
st.none() | st.just("xx"),
|
|
||||||
st.none() | st.just("gt"),
|
|
||||||
st.none() | st.just("lt"),
|
|
||||||
st.just("fields"),
|
|
||||||
st.just(2),
|
|
||||||
st.lists(fields, min_size=2, max_size=2),
|
|
||||||
)
|
|
||||||
| commands(
|
|
||||||
st.just("hpexpire"),
|
|
||||||
keys,
|
|
||||||
expires_ms,
|
|
||||||
st.none() | st.just("nx"),
|
|
||||||
st.none() | st.just("xx"),
|
|
||||||
st.none() | st.just("gt"),
|
|
||||||
st.none() | st.just("lt"),
|
|
||||||
st.just("fields"),
|
|
||||||
st.just(2),
|
|
||||||
st.lists(fields, min_size=2, max_size=2),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
create_command_strategy = commands(
|
|
||||||
st.just("hset"), keys, st.lists(st.tuples(fields, values), min_size=1)
|
|
||||||
)
|
|
||||||
command_strategy = hash_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestList(BaseTest):
|
|
||||||
# TODO: blocking commands
|
|
||||||
list_commands = (
|
|
||||||
commands(st.just("lindex"), keys, counts)
|
|
||||||
| commands(
|
|
||||||
st.just("linsert"),
|
|
||||||
keys,
|
|
||||||
st.sampled_from(["before", "after", "BEFORE", "AFTER"]) | st.binary(),
|
|
||||||
values,
|
|
||||||
values,
|
|
||||||
)
|
|
||||||
| commands(st.just("llen"), keys)
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["lpop", "rpop"]),
|
|
||||||
keys,
|
|
||||||
st.just(None) | st.just([]) | st.integers(),
|
|
||||||
)
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["lpush", "lpushx", "rpush", "rpushx"]),
|
|
||||||
keys,
|
|
||||||
st.lists(values),
|
|
||||||
)
|
|
||||||
| commands(st.just("lrange"), keys, counts, counts)
|
|
||||||
| commands(st.just("lrem"), keys, counts, values)
|
|
||||||
| commands(st.just("lset"), keys, counts, values)
|
|
||||||
| commands(st.just("ltrim"), keys, counts, counts)
|
|
||||||
| commands(st.just("rpoplpush"), keys, keys)
|
|
||||||
)
|
|
||||||
create_command_strategy = commands(
|
|
||||||
st.just("rpush"), keys, st.lists(values, min_size=1)
|
|
||||||
)
|
|
||||||
command_strategy = list_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestSet(BaseTest):
|
|
||||||
set_commands = (
|
|
||||||
commands(
|
|
||||||
st.just("sadd"),
|
|
||||||
keys,
|
|
||||||
st.lists(
|
|
||||||
fields,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
| commands(st.just("scard"), keys)
|
|
||||||
| commands(st.sampled_from(["sdiff", "sinter", "sunion"]), st.lists(keys))
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["sdiffstore", "sinterstore", "sunionstore"]),
|
|
||||||
keys,
|
|
||||||
st.lists(keys),
|
|
||||||
)
|
|
||||||
| commands(st.just("sismember"), keys, fields)
|
|
||||||
| commands(st.just("smembers"), keys)
|
|
||||||
| commands(st.just("smove"), keys, keys, fields)
|
|
||||||
| commands(st.just("srem"), keys, st.lists(fields))
|
|
||||||
)
|
|
||||||
# TODO:
|
|
||||||
# - find a way to test srandmember, spop which are random
|
|
||||||
# - sscan
|
|
||||||
create_command_strategy = commands(
|
|
||||||
st.just("sadd"), keys, st.lists(fields, min_size=1)
|
|
||||||
)
|
|
||||||
command_strategy = set_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestZSet(BaseTest):
|
|
||||||
zset_commands = (
|
|
||||||
commands(
|
|
||||||
st.just("zadd"),
|
|
||||||
keys,
|
|
||||||
st.none() | st.just("nx"),
|
|
||||||
st.none() | st.just("xx"),
|
|
||||||
st.none() | st.just("ch"),
|
|
||||||
st.none() | st.just("incr"),
|
|
||||||
st.lists(st.tuples(scores, fields)),
|
|
||||||
)
|
|
||||||
| commands(st.just("zcard"), keys)
|
|
||||||
| commands(st.just("zcount"), keys, score_tests, score_tests)
|
|
||||||
| commands(st.just("zincrby"), keys, scores, fields)
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["zrange", "zrevrange"]),
|
|
||||||
keys,
|
|
||||||
counts,
|
|
||||||
counts,
|
|
||||||
st.none() | st.just("withscores"),
|
|
||||||
)
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["zrangebyscore", "zrevrangebyscore"]),
|
|
||||||
keys,
|
|
||||||
score_tests,
|
|
||||||
score_tests,
|
|
||||||
limits,
|
|
||||||
st.none() | st.just("withscores"),
|
|
||||||
)
|
|
||||||
| commands(st.sampled_from(["zrank", "zrevrank"]), keys, fields)
|
|
||||||
| commands(st.just("zrem"), keys, st.lists(fields))
|
|
||||||
| commands(st.just("zremrangebyrank"), keys, counts, counts)
|
|
||||||
| commands(st.just("zremrangebyscore"), keys, score_tests, score_tests)
|
|
||||||
| commands(st.just("zscore"), keys, fields)
|
|
||||||
| st.builds(
|
|
||||||
build_zstore,
|
|
||||||
command=st.sampled_from(["zunionstore", "zinterstore"]),
|
|
||||||
dest=keys,
|
|
||||||
sources=st.lists(st.tuples(keys, float_as_bytes)),
|
|
||||||
weights=st.booleans(),
|
|
||||||
aggregate=st.sampled_from([None, "sum", "min", "max"]),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
# TODO: zscan, zpopmin/zpopmax, bzpopmin/bzpopmax, probably more
|
|
||||||
create_command_strategy = commands(
|
|
||||||
st.just("zadd"), keys, st.lists(st.tuples(scores, fields), min_size=1)
|
|
||||||
)
|
|
||||||
command_strategy = zset_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestZSetNoScores(BaseTest):
|
|
||||||
create_command_strategy = zset_no_score_create_commands
|
|
||||||
command_strategy = zset_no_score_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestTransaction(BaseTest):
|
|
||||||
transaction_commands = (
|
|
||||||
commands(st.sampled_from(["multi", "discard", "exec", "unwatch"]))
|
|
||||||
| commands(st.just("watch"), keys)
|
|
||||||
| commands(st.just("append"), keys, values)
|
|
||||||
| commands(st.just("bitcount"), keys)
|
|
||||||
| commands(st.just("bitcount"), keys, values, values)
|
|
||||||
| commands(st.sampled_from(["incr", "decr"]), keys)
|
|
||||||
| commands(st.sampled_from(["incrby", "decrby"]), keys, values)
|
|
||||||
| commands(st.just("get"), keys)
|
|
||||||
| commands(st.just("getbit"), keys, counts)
|
|
||||||
| commands(
|
|
||||||
st.just("setbit"),
|
|
||||||
keys,
|
|
||||||
counts,
|
|
||||||
st.integers(min_value=0, max_value=1) | st.integers(),
|
|
||||||
)
|
|
||||||
| commands(st.sampled_from(["substr", "getrange"]), keys, counts, counts)
|
|
||||||
| commands(st.just("getset"), keys, values)
|
|
||||||
| commands(st.just("mget"), st.lists(keys))
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["mset", "msetnx"]), st.lists(st.tuples(keys, values))
|
|
||||||
)
|
|
||||||
| commands(
|
|
||||||
st.just("set"),
|
|
||||||
keys,
|
|
||||||
values,
|
|
||||||
st.none() | st.just("nx"),
|
|
||||||
st.none() | st.just("xx"),
|
|
||||||
st.none() | st.just("keepttl"),
|
|
||||||
)
|
|
||||||
| commands(st.just("setex"), keys, expires_seconds, values)
|
|
||||||
| commands(st.just("psetex"), keys, expires_ms, values)
|
|
||||||
| commands(st.just("setnx"), keys, values)
|
|
||||||
| commands(st.just("setrange"), keys, counts, values)
|
|
||||||
| commands(st.just("strlen"), keys)
|
|
||||||
)
|
|
||||||
create_command_strategy = TestString.create_command_strategy
|
|
||||||
command_strategy = transaction_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestServer(BaseTest):
|
|
||||||
# TODO: real redis raises an error if there is a save already in progress.
|
|
||||||
# Find a better way to test this. commands(st.just('bgsave'))
|
|
||||||
server_commands = (
|
|
||||||
commands(st.just("dbsize"))
|
|
||||||
| commands(
|
|
||||||
st.sampled_from(["flushdb", "flushall"]), st.sampled_from([[], "async"])
|
|
||||||
)
|
|
||||||
# TODO: result is non-deterministic
|
|
||||||
# | commands(st.just('lastsave'))
|
|
||||||
| commands(st.just("save"))
|
|
||||||
)
|
|
||||||
create_command_strategy = TestString.create_command_strategy
|
|
||||||
command_strategy = server_commands | TestString.string_commands | common_commands
|
|
||||||
|
|
||||||
|
|
||||||
class TestJoint(BaseTest):
|
|
||||||
create_command_strategy = (
|
|
||||||
TestString.create_command_strategy
|
|
||||||
| TestHash.create_command_strategy
|
|
||||||
| TestList.create_command_strategy
|
|
||||||
| TestSet.create_command_strategy
|
|
||||||
| TestZSet.create_command_strategy
|
|
||||||
)
|
|
||||||
command_strategy = (
|
|
||||||
TestServer.server_commands
|
|
||||||
| TestConnection.connection_commands
|
|
||||||
| TestString.string_commands
|
|
||||||
| TestHash.hash_commands
|
|
||||||
| TestList.list_commands
|
|
||||||
| TestSet.set_commands
|
|
||||||
| TestZSet.zset_commands
|
|
||||||
| common_commands
|
|
||||||
| bad_commands
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def delete_arg(draw, commands):
|
|
||||||
command = draw(commands)
|
|
||||||
if command.args:
|
|
||||||
pos = draw(st.integers(min_value=0, max_value=len(command.args) - 1))
|
|
||||||
command.args = command.args[:pos] + command.args[pos + 1 :]
|
|
||||||
return command
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def command_args(draw, commands):
|
|
||||||
"""Generate an argument from some command"""
|
|
||||||
command = draw(commands)
|
|
||||||
hypothesis.assume(len(command.args))
|
|
||||||
return draw(st.sampled_from(command.args))
|
|
||||||
|
|
||||||
|
|
||||||
def mutate_arg(draw, commands, mutate):
|
|
||||||
command = draw(commands)
|
|
||||||
if command.args:
|
|
||||||
pos = draw(st.integers(min_value=0, max_value=len(command.args) - 1))
|
|
||||||
arg = mutate(Command.encode(command.args[pos]))
|
|
||||||
command.args = command.args[:pos] + (arg,) + command.args[pos + 1 :]
|
|
||||||
return command
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def replace_arg(draw, commands, replacements):
|
|
||||||
return mutate_arg(draw, commands, lambda arg: draw(replacements))
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def uppercase_arg(draw, commands):
|
|
||||||
return mutate_arg(draw, commands, lambda arg: arg.upper())
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def prefix_arg(draw, commands, prefixes):
|
|
||||||
return mutate_arg(draw, commands, lambda arg: draw(prefixes) + arg)
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def suffix_arg(draw, commands, suffixes):
|
|
||||||
return mutate_arg(draw, commands, lambda arg: arg + draw(suffixes))
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def add_arg(draw, commands, arguments):
|
|
||||||
command = draw(commands)
|
|
||||||
arg = draw(arguments)
|
|
||||||
pos = draw(st.integers(min_value=0, max_value=len(command.args)))
|
|
||||||
command.args = command.args[:pos] + (arg,) + command.args[pos:]
|
|
||||||
return command
|
|
||||||
|
|
||||||
|
|
||||||
@st.composite
|
|
||||||
def swap_args(draw, commands):
|
|
||||||
command = draw(commands)
|
|
||||||
if len(command.args) >= 2:
|
|
||||||
pos1 = draw(st.integers(min_value=0, max_value=len(command.args) - 1))
|
|
||||||
pos2 = draw(st.integers(min_value=0, max_value=len(command.args) - 1))
|
|
||||||
hypothesis.assume(pos1 != pos2)
|
|
||||||
args = list(command.args)
|
|
||||||
arg1 = args[pos1]
|
|
||||||
arg2 = args[pos2]
|
|
||||||
args[pos1] = arg2
|
|
||||||
args[pos2] = arg1
|
|
||||||
command.args = tuple(args)
|
|
||||||
return command
|
|
||||||
|
|
||||||
|
|
||||||
def mutated_commands(commands):
|
|
||||||
args = st.sampled_from(
|
|
||||||
[
|
|
||||||
b"withscores",
|
|
||||||
b"xx",
|
|
||||||
b"nx",
|
|
||||||
b"ex",
|
|
||||||
b"px",
|
|
||||||
b"weights",
|
|
||||||
b"aggregate",
|
|
||||||
b"",
|
|
||||||
b"0",
|
|
||||||
b"-1",
|
|
||||||
b"nan",
|
|
||||||
b"inf",
|
|
||||||
b"-inf",
|
|
||||||
]
|
|
||||||
) | command_args(commands)
|
|
||||||
affixes = st.sampled_from([b"\0", b"-", b"+", b"\t", b"\n", b"0000"]) | st.binary()
|
|
||||||
return st.recursive(
|
|
||||||
commands,
|
|
||||||
lambda x: delete_arg(x)
|
|
||||||
| replace_arg(x, args)
|
|
||||||
| uppercase_arg(x)
|
|
||||||
| prefix_arg(x, affixes)
|
|
||||||
| suffix_arg(x, affixes)
|
|
||||||
| add_arg(x, args)
|
|
||||||
| swap_args(x),
|
|
||||||
)
|
|
0
tests/fakeredis/test/test_hypothesis/__init__.py
Normal file
0
tests/fakeredis/test/test_hypothesis/__init__.py
Normal file
25
tests/fakeredis/test/test_hypothesis/_server_info.py
Normal file
25
tests/fakeredis/test/test_hypothesis/_server_info.py
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
from typing import Tuple, Union
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
import redis
|
||||||
|
|
||||||
|
|
||||||
|
def server_info() -> Tuple[str, Union[None, Tuple[int, ...]]]:
|
||||||
|
"""Returns server's version or None if server is not running"""
|
||||||
|
client = None
|
||||||
|
try:
|
||||||
|
client = redis.Redis("localhost", port=6380, db=2)
|
||||||
|
client_info = client.info()
|
||||||
|
server_type = "dragonfly" if "dragonfly_version" in client_info else "redis"
|
||||||
|
server_version = (7, 0)
|
||||||
|
return server_type, server_version
|
||||||
|
except redis.ConnectionError as e:
|
||||||
|
print(e)
|
||||||
|
pytest.exit("Redis is not running")
|
||||||
|
return "redis", (6,)
|
||||||
|
finally:
|
||||||
|
if hasattr(client, "close"):
|
||||||
|
client.close() # Absent in older versions of redis-py
|
||||||
|
|
||||||
|
|
||||||
|
server_type, redis_ver = server_info()
|
354
tests/fakeredis/test/test_hypothesis/base.py
Normal file
354
tests/fakeredis/test/test_hypothesis/base.py
Normal file
|
@ -0,0 +1,354 @@
|
||||||
|
import functools
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
from typing import Any, List, Tuple, Type, Optional
|
||||||
|
|
||||||
|
import fakeredis
|
||||||
|
import hypothesis
|
||||||
|
import hypothesis.stateful
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
import pytest
|
||||||
|
import redis
|
||||||
|
from hypothesis.stateful import rule, initialize, precondition
|
||||||
|
from hypothesis.strategies import SearchStrategy
|
||||||
|
|
||||||
|
from ._server_info import redis_ver
|
||||||
|
|
||||||
|
self_strategy = st.runner()
|
||||||
|
|
||||||
|
MAX_INT = 2_147_483_647
|
||||||
|
MIN_INT = -2_147_483_648
|
||||||
|
|
||||||
|
|
||||||
|
@st.composite
|
||||||
|
def sample_attr(draw, name):
|
||||||
|
"""Strategy for sampling a specific attribute from a state machine"""
|
||||||
|
machine = draw(self_strategy)
|
||||||
|
values = getattr(machine, name)
|
||||||
|
position = draw(st.integers(min_value=0, max_value=len(values) - 1))
|
||||||
|
return values[position]
|
||||||
|
|
||||||
|
|
||||||
|
keys = sample_attr("keys")
|
||||||
|
fields = sample_attr("fields")
|
||||||
|
values = sample_attr("values")
|
||||||
|
scores = sample_attr("scores")
|
||||||
|
|
||||||
|
ints = st.integers(min_value=MIN_INT, max_value=MAX_INT)
|
||||||
|
int_as_bytes = st.builds(lambda x: str(_default_normalize(x)).encode(), ints)
|
||||||
|
float_as_bytes = st.builds(
|
||||||
|
lambda x: repr(_default_normalize(x)).encode(), st.floats(width=32)
|
||||||
|
)
|
||||||
|
counts = st.integers(min_value=-3, max_value=3) | ints
|
||||||
|
# Redis has an integer overflow bug in swapdb, so we confine the numbers to
|
||||||
|
# a limited range (https://github.com/antirez/redis/issues/5737).
|
||||||
|
dbnums = st.integers(min_value=0, max_value=3) | st.integers(
|
||||||
|
min_value=-1000, max_value=1000
|
||||||
|
)
|
||||||
|
# The filter is to work around https://github.com/antirez/redis/issues/5632
|
||||||
|
patterns = st.text(
|
||||||
|
alphabet=st.sampled_from("[]^$*.?-azAZ\\\r\n\t")
|
||||||
|
) | st.binary().filter(lambda x: b"\0" not in x)
|
||||||
|
|
||||||
|
# Redis has integer overflow bugs in time computations, which is why we set a maximum.
|
||||||
|
expires_seconds = st.integers(min_value=100000, max_value=MAX_INT)
|
||||||
|
expires_ms = st.integers(min_value=100000000, max_value=MAX_INT)
|
||||||
|
|
||||||
|
|
||||||
|
class WrappedException:
|
||||||
|
"""Wraps an exception for the purposes of comparison."""
|
||||||
|
|
||||||
|
def __init__(self, exc):
|
||||||
|
self.wrapped = exc
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return str(self.wrapped)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "WrappedException({!r})".format(self.wrapped)
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, WrappedException):
|
||||||
|
return NotImplemented
|
||||||
|
if type(self.wrapped) != type(other.wrapped): # noqa: E721
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
# return self.wrapped.args == other.wrapped.args
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if not isinstance(other, WrappedException):
|
||||||
|
return NotImplemented
|
||||||
|
return not self == other
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap_exceptions(obj):
|
||||||
|
if isinstance(obj, list):
|
||||||
|
return [_wrap_exceptions(item) for item in obj]
|
||||||
|
elif isinstance(obj, Exception):
|
||||||
|
return WrappedException(obj)
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def _sort_list(lst):
|
||||||
|
if isinstance(lst, list):
|
||||||
|
return sorted(lst)
|
||||||
|
else:
|
||||||
|
return lst
|
||||||
|
|
||||||
|
|
||||||
|
def _normalize_if_number(x):
|
||||||
|
try:
|
||||||
|
res = float(x)
|
||||||
|
return x if math.isnan(res) else res
|
||||||
|
except ValueError:
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
def _flatten(args):
|
||||||
|
if isinstance(args, (list, tuple)):
|
||||||
|
for arg in args:
|
||||||
|
yield from _flatten(arg)
|
||||||
|
elif args is not None:
|
||||||
|
yield args
|
||||||
|
|
||||||
|
|
||||||
|
def _default_normalize(x: Any) -> Any:
|
||||||
|
if redis_ver >= (7,) and (isinstance(x, float) or isinstance(x, int)):
|
||||||
|
return 0 + x
|
||||||
|
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class Command:
|
||||||
|
def __init__(self, *args):
|
||||||
|
args = list(_flatten(args))
|
||||||
|
args = [_default_normalize(x) for x in args]
|
||||||
|
self.args = tuple(args)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
parts = [repr(arg) for arg in self.args]
|
||||||
|
return "Command({})".format(", ".join(parts))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def encode(arg):
|
||||||
|
encoder = redis.connection.Encoder("utf-8", "replace", False)
|
||||||
|
return encoder.encode(arg)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def normalize(self):
|
||||||
|
command = self.encode(self.args[0]).lower() if self.args else None
|
||||||
|
# Functions that return a list in arbitrary order
|
||||||
|
unordered = {
|
||||||
|
b"keys",
|
||||||
|
b"sort",
|
||||||
|
b"hgetall",
|
||||||
|
b"hkeys",
|
||||||
|
b"hvals",
|
||||||
|
b"sdiff",
|
||||||
|
b"sinter",
|
||||||
|
b"sunion",
|
||||||
|
b"smembers",
|
||||||
|
}
|
||||||
|
if command in unordered:
|
||||||
|
return _sort_list
|
||||||
|
else:
|
||||||
|
return _normalize_if_number
|
||||||
|
|
||||||
|
@property
|
||||||
|
def testable(self) -> bool:
|
||||||
|
"""Whether this command is suitable for a test.
|
||||||
|
|
||||||
|
The fuzzer can create commands with behaviour that is non-deterministic, not supported, or which hits redis bugs.
|
||||||
|
"""
|
||||||
|
N = len(self.args)
|
||||||
|
if N == 0:
|
||||||
|
return False
|
||||||
|
command = self.encode(self.args[0]).lower()
|
||||||
|
if not command.split():
|
||||||
|
return False
|
||||||
|
if command == b"keys" and N == 2 and self.args[1] != b"*":
|
||||||
|
return False
|
||||||
|
# Redis will ignore a NULL character in some commands but not others,
|
||||||
|
# e.g., it recognises EXEC\0 but not MULTI\00.
|
||||||
|
# Rather than try to reproduce this quirky behavior, just skip these tests.
|
||||||
|
if b"\0" in command:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def zero_or_more(*args) -> List[SearchStrategy]:
|
||||||
|
return [st.none() | st.just(arg) for arg in args]
|
||||||
|
|
||||||
|
|
||||||
|
def commands(*args, **kwargs):
|
||||||
|
return st.builds(functools.partial(Command, **kwargs), *args)
|
||||||
|
|
||||||
|
|
||||||
|
# # TODO: all expiry-related commands
|
||||||
|
common_commands = (
|
||||||
|
commands(st.sampled_from(["del", "persist", "type", "unlink"]), keys)
|
||||||
|
| commands(st.just("exists"), st.lists(keys))
|
||||||
|
| commands(st.just("keys"), st.just("*"))
|
||||||
|
# Disabled for now due to redis giving wrong answers
|
||||||
|
# (https://github.com/antirez/redis/issues/5632)
|
||||||
|
# | commands(st.just('keys'), patterns)
|
||||||
|
| commands(st.just("move"), keys, dbnums)
|
||||||
|
| commands(st.sampled_from(["rename", "renamenx"]), keys, keys)
|
||||||
|
# TODO: find a better solution to sort instability than throwing
|
||||||
|
# away the sort entirely with normalize. This also prevents us
|
||||||
|
# using LIMIT.
|
||||||
|
| commands(st.just("sort"), keys, *zero_or_more("asc", "desc", "alpha"))
|
||||||
|
)
|
||||||
|
|
||||||
|
attrs = st.fixed_dictionaries(
|
||||||
|
{
|
||||||
|
"keys": st.lists(st.binary(), min_size=2, max_size=5, unique=True),
|
||||||
|
"fields": st.lists(st.binary(), min_size=2, max_size=5, unique=True),
|
||||||
|
"values": st.lists(
|
||||||
|
st.binary() | int_as_bytes | float_as_bytes,
|
||||||
|
min_size=2,
|
||||||
|
max_size=5,
|
||||||
|
unique=True,
|
||||||
|
),
|
||||||
|
"scores": st.lists(st.floats(width=32), min_size=2, max_size=5, unique=True),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@hypothesis.settings(max_examples=1000)
|
||||||
|
class CommonMachine(hypothesis.stateful.RuleBasedStateMachine):
|
||||||
|
create_command_strategy = st.nothing()
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
try:
|
||||||
|
self.real = redis.StrictRedis("localhost", port=6380, db=2)
|
||||||
|
self.real.ping()
|
||||||
|
except redis.ConnectionError:
|
||||||
|
pytest.skip("redis is not running")
|
||||||
|
if self.real.info("server").get("arch_bits") != 64:
|
||||||
|
self.real.connection_pool.disconnect()
|
||||||
|
pytest.skip("redis server is not 64-bit")
|
||||||
|
self.fake = fakeredis.FakeStrictRedis(
|
||||||
|
server=fakeredis.FakeServer(version=redis_ver), port=6380, db=2
|
||||||
|
)
|
||||||
|
# Disable the response parsing so that we can check the raw values returned
|
||||||
|
self.fake.response_callbacks.clear()
|
||||||
|
self.real.response_callbacks.clear()
|
||||||
|
self.transaction_normalize = []
|
||||||
|
self.keys = []
|
||||||
|
self.fields = []
|
||||||
|
self.values = []
|
||||||
|
self.scores = []
|
||||||
|
self.initialized_data = False
|
||||||
|
try:
|
||||||
|
self.real.execute_command("discard")
|
||||||
|
except redis.ResponseError:
|
||||||
|
pass
|
||||||
|
self.real.flushall()
|
||||||
|
|
||||||
|
def teardown(self) -> None:
|
||||||
|
self.real.connection_pool.disconnect()
|
||||||
|
self.fake.connection_pool.disconnect()
|
||||||
|
super().teardown()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _evaluate(
|
||||||
|
client: redis.Redis, command
|
||||||
|
) -> Tuple[Any, Optional[Type[Exception]]]:
|
||||||
|
try:
|
||||||
|
result = client.execute_command(*command.args)
|
||||||
|
if result != "QUEUED":
|
||||||
|
result = command.normalize(result)
|
||||||
|
exc = None
|
||||||
|
except Exception as e:
|
||||||
|
result = exc = e
|
||||||
|
return _wrap_exceptions(result), exc
|
||||||
|
|
||||||
|
def _compare(self, command: Command) -> None:
|
||||||
|
fake_result, fake_exc = self._evaluate(self.fake, command)
|
||||||
|
real_result, real_exc = self._evaluate(self.real, command)
|
||||||
|
|
||||||
|
if fake_exc is not None and real_exc is None:
|
||||||
|
print(
|
||||||
|
f"{fake_exc} raised on only on fake when running {command}",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
raise fake_exc
|
||||||
|
elif real_exc is not None and fake_exc is None:
|
||||||
|
assert real_exc == fake_exc, f"Expected exception {real_exc} not raised"
|
||||||
|
elif (
|
||||||
|
real_exc is None
|
||||||
|
and isinstance(real_result, list)
|
||||||
|
and command.args
|
||||||
|
and command.args[0].lower() == "exec"
|
||||||
|
):
|
||||||
|
assert fake_result is not None
|
||||||
|
# Transactions need to use the normalize functions of the component commands.
|
||||||
|
assert len(self.transaction_normalize) == len(real_result)
|
||||||
|
assert len(self.transaction_normalize) == len(fake_result)
|
||||||
|
for n, r, f in zip(self.transaction_normalize, real_result, fake_result):
|
||||||
|
assert n(f) == n(r)
|
||||||
|
self.transaction_normalize = []
|
||||||
|
else:
|
||||||
|
assert fake_result == real_result or (
|
||||||
|
type(fake_result) is float and fake_result == pytest.approx(real_result)
|
||||||
|
), f"Discrepancy when running command {command}, fake({fake_result}) != real({real_result})"
|
||||||
|
if real_result == b"QUEUED":
|
||||||
|
# Since redis removes the distinction between simple strings and
|
||||||
|
# bulk strings, this might not actually indicate that we're in a
|
||||||
|
# transaction. But it is extremely unlikely that hypothesis will
|
||||||
|
# find such examples.
|
||||||
|
self.transaction_normalize.append(command.normalize)
|
||||||
|
if len(command.args) == 1 and Command.encode(command.args[0]).lower() in (
|
||||||
|
b"discard",
|
||||||
|
b"exec",
|
||||||
|
):
|
||||||
|
self.transaction_normalize = []
|
||||||
|
|
||||||
|
@initialize(attrs=attrs)
|
||||||
|
def init_attrs(self, attrs):
|
||||||
|
for key, value in attrs.items():
|
||||||
|
setattr(self, key, value)
|
||||||
|
|
||||||
|
# hypothesis doesn't allow ordering of @initialize, so we have to put
|
||||||
|
# preconditions on rules to ensure we call init_data exactly once and
|
||||||
|
# after init_attrs.
|
||||||
|
@precondition(lambda self: not self.initialized_data)
|
||||||
|
@rule(
|
||||||
|
commands=self_strategy.flatmap(
|
||||||
|
lambda self: st.lists(self.create_command_strategy)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
def init_data(self, commands) -> None:
|
||||||
|
for command in commands:
|
||||||
|
self._compare(command)
|
||||||
|
self.initialized_data = True
|
||||||
|
|
||||||
|
@precondition(lambda self: self.initialized_data)
|
||||||
|
@rule(command=self_strategy.flatmap(lambda self: self.command_strategy))
|
||||||
|
def one_command(self, command: Command) -> None:
|
||||||
|
self._compare(command)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTest:
|
||||||
|
"""Base class for test classes."""
|
||||||
|
|
||||||
|
command_strategy: SearchStrategy
|
||||||
|
create_command_strategy = st.nothing()
|
||||||
|
|
||||||
|
@pytest.mark.slow
|
||||||
|
def test(self):
|
||||||
|
class Machine(CommonMachine):
|
||||||
|
create_command_strategy = self.create_command_strategy
|
||||||
|
command_strategy = self.command_strategy
|
||||||
|
|
||||||
|
# hypothesis.settings.register_profile(
|
||||||
|
# "debug", max_examples=10, verbosity=hypothesis.Verbosity.debug
|
||||||
|
# )
|
||||||
|
hypothesis.settings.register_profile(
|
||||||
|
"debug", verbosity=hypothesis.Verbosity.debug
|
||||||
|
)
|
||||||
|
hypothesis.settings.load_profile("debug")
|
||||||
|
hypothesis.stateful.run_state_machine_as_test(Machine)
|
13
tests/fakeredis/test/test_hypothesis/test_connection.py
Normal file
13
tests/fakeredis/test/test_hypothesis/test_connection.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import BaseTest, commands, values, common_commands
|
||||||
|
|
||||||
|
|
||||||
|
class TestConnection(BaseTest):
|
||||||
|
# TODO: tests for select
|
||||||
|
connection_commands = (
|
||||||
|
commands(st.just("echo"), values)
|
||||||
|
| commands(st.just("ping"), st.lists(values, max_size=2))
|
||||||
|
# | commands(st.just("swapdb"), dbnums, dbnums)
|
||||||
|
)
|
||||||
|
command_strategy = connection_commands | common_commands
|
46
tests/fakeredis/test/test_hypothesis/test_hash.py
Normal file
46
tests/fakeredis/test/test_hypothesis/test_hash.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import (
|
||||||
|
BaseTest,
|
||||||
|
commands,
|
||||||
|
values,
|
||||||
|
keys,
|
||||||
|
common_commands,
|
||||||
|
fields,
|
||||||
|
ints,
|
||||||
|
expires_seconds,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestHash(BaseTest):
|
||||||
|
hash_commands = (
|
||||||
|
commands(st.just("hset"), keys, st.lists(st.tuples(fields, values)))
|
||||||
|
| commands(st.just("hdel"), keys, st.lists(fields))
|
||||||
|
| commands(st.just("hexists"), keys, fields)
|
||||||
|
| commands(st.just("hget"), keys, fields)
|
||||||
|
| commands(st.sampled_from(["hgetall", "hkeys", "hvals"]), keys)
|
||||||
|
| commands(st.just("hincrby"), keys, fields, ints)
|
||||||
|
| commands(st.just("hlen"), keys)
|
||||||
|
| commands(st.just("hmget"), keys, st.lists(fields))
|
||||||
|
| commands(st.just("hset"), keys, st.lists(st.tuples(fields, values)))
|
||||||
|
| commands(st.just("hsetnx"), keys, fields, values)
|
||||||
|
| commands(st.just("hstrlen"), keys, fields)
|
||||||
|
| commands(
|
||||||
|
st.just("hpersist"),
|
||||||
|
st.just("fields"),
|
||||||
|
st.just(2),
|
||||||
|
st.lists(fields, min_size=2, max_size=2),
|
||||||
|
)
|
||||||
|
| commands(
|
||||||
|
st.just("hexpire"),
|
||||||
|
keys,
|
||||||
|
expires_seconds,
|
||||||
|
st.just("fields"),
|
||||||
|
st.just(2),
|
||||||
|
st.lists(fields, min_size=2, max_size=2),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
create_command_strategy = commands(
|
||||||
|
st.just("hset"), keys, st.lists(st.tuples(fields, values), min_size=1)
|
||||||
|
)
|
||||||
|
command_strategy = hash_commands | common_commands
|
38
tests/fakeredis/test/test_hypothesis/test_joint.py
Normal file
38
tests/fakeredis/test/test_hypothesis/test_joint.py
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import BaseTest, common_commands, commands
|
||||||
|
from test.test_hypothesis.test_connection import TestConnection
|
||||||
|
from test.test_hypothesis.test_hash import TestHash
|
||||||
|
from test.test_hypothesis.test_list import TestList
|
||||||
|
from test.test_hypothesis.test_server import TestServer
|
||||||
|
from test.test_hypothesis.test_set import TestSet
|
||||||
|
from test.test_hypothesis.test_string import TestString, string_commands
|
||||||
|
from test.test_hypothesis.test_zset import TestZSet
|
||||||
|
|
||||||
|
bad_commands = (
|
||||||
|
# redis-py splits the command on spaces, and hangs if that ends up being an empty list
|
||||||
|
commands(
|
||||||
|
st.text().filter(lambda x: bool(x.split())), st.lists(st.binary() | st.text())
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestJoint(BaseTest):
|
||||||
|
create_command_strategy = (
|
||||||
|
TestString.create_command_strategy
|
||||||
|
| TestHash.create_command_strategy
|
||||||
|
| TestList.create_command_strategy
|
||||||
|
| TestSet.create_command_strategy
|
||||||
|
| TestZSet.create_command_strategy
|
||||||
|
)
|
||||||
|
command_strategy = (
|
||||||
|
TestServer.server_commands
|
||||||
|
| TestConnection.connection_commands
|
||||||
|
| string_commands
|
||||||
|
| TestHash.hash_commands
|
||||||
|
| TestList.list_commands
|
||||||
|
| TestSet.set_commands
|
||||||
|
| TestZSet.zset_commands
|
||||||
|
| common_commands
|
||||||
|
| bad_commands
|
||||||
|
)
|
45
tests/fakeredis/test/test_hypothesis/test_list.py
Normal file
45
tests/fakeredis/test/test_hypothesis/test_list.py
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import (
|
||||||
|
BaseTest,
|
||||||
|
commands,
|
||||||
|
values,
|
||||||
|
keys,
|
||||||
|
common_commands,
|
||||||
|
counts,
|
||||||
|
ints,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestList(BaseTest):
|
||||||
|
# TODO: blocking commands
|
||||||
|
list_commands = (
|
||||||
|
commands(st.just("lindex"), keys, counts)
|
||||||
|
| commands(
|
||||||
|
st.just("linsert"),
|
||||||
|
keys,
|
||||||
|
st.sampled_from(["before", "after", "BEFORE", "AFTER"]) | st.binary(),
|
||||||
|
values,
|
||||||
|
values,
|
||||||
|
)
|
||||||
|
| commands(st.just("llen"), keys)
|
||||||
|
| commands(
|
||||||
|
st.sampled_from(["lpop", "rpop"]),
|
||||||
|
keys,
|
||||||
|
st.just(None) | st.just([]) | ints,
|
||||||
|
)
|
||||||
|
| commands(
|
||||||
|
st.sampled_from(["lpush", "lpushx", "rpush", "rpushx"]),
|
||||||
|
keys,
|
||||||
|
st.lists(values),
|
||||||
|
)
|
||||||
|
| commands(st.just("lrange"), keys, counts, counts)
|
||||||
|
| commands(st.just("lrem"), keys, counts, values)
|
||||||
|
| commands(st.just("lset"), keys, counts, values)
|
||||||
|
| commands(st.just("ltrim"), keys, counts, counts)
|
||||||
|
| commands(st.just("rpoplpush"), keys, keys)
|
||||||
|
)
|
||||||
|
create_command_strategy = commands(
|
||||||
|
st.just("rpush"), keys, st.lists(values, min_size=1)
|
||||||
|
)
|
||||||
|
command_strategy = list_commands | common_commands
|
24
tests/fakeredis/test/test_hypothesis/test_server.py
Normal file
24
tests/fakeredis/test/test_hypothesis/test_server.py
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import (
|
||||||
|
BaseTest,
|
||||||
|
commands,
|
||||||
|
common_commands,
|
||||||
|
keys,
|
||||||
|
values,
|
||||||
|
)
|
||||||
|
from test.test_hypothesis.test_string import string_commands
|
||||||
|
|
||||||
|
|
||||||
|
class TestServer(BaseTest):
|
||||||
|
# TODO: real redis raises an error if there is a save already in progress.
|
||||||
|
# Find a better way to test this. commands(st.just('bgsave'))
|
||||||
|
server_commands = (
|
||||||
|
commands(st.just("dbsize"))
|
||||||
|
| commands(st.sampled_from(["flushdb", "flushall"]))
|
||||||
|
# TODO: result is non-deterministic
|
||||||
|
# | commands(st.just('lastsave'))
|
||||||
|
| commands(st.just("save"))
|
||||||
|
)
|
||||||
|
create_command_strategy = commands(st.just("set"), keys, values)
|
||||||
|
command_strategy = server_commands | string_commands | common_commands
|
30
tests/fakeredis/test/test_hypothesis/test_set.py
Normal file
30
tests/fakeredis/test/test_hypothesis/test_set.py
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import (
|
||||||
|
BaseTest,
|
||||||
|
commands,
|
||||||
|
keys,
|
||||||
|
common_commands,
|
||||||
|
fields,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSet(BaseTest):
|
||||||
|
set_commands = (
|
||||||
|
commands(st.just("sadd"), keys, st.lists(fields))
|
||||||
|
| commands(st.just("scard"), keys)
|
||||||
|
| commands(st.sampled_from(["sdiff", "sinter", "sunion"]), st.lists(keys))
|
||||||
|
| commands(
|
||||||
|
st.sampled_from(["sdiffstore", "sinterstore", "sunionstore"]),
|
||||||
|
keys,
|
||||||
|
st.lists(keys),
|
||||||
|
)
|
||||||
|
| commands(st.just("sismember"), keys, fields)
|
||||||
|
| commands(st.just("smembers"), keys)
|
||||||
|
| commands(st.just("smove"), keys, keys, fields)
|
||||||
|
| commands(st.just("srem"), keys, st.lists(fields))
|
||||||
|
)
|
||||||
|
create_command_strategy = commands(
|
||||||
|
st.just("sadd"), keys, st.lists(fields, min_size=1)
|
||||||
|
)
|
||||||
|
command_strategy = set_commands | common_commands
|
59
tests/fakeredis/test/test_hypothesis/test_string.py
Normal file
59
tests/fakeredis/test/test_hypothesis/test_string.py
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import (
|
||||||
|
BaseTest,
|
||||||
|
commands,
|
||||||
|
values,
|
||||||
|
keys,
|
||||||
|
common_commands,
|
||||||
|
counts,
|
||||||
|
int_as_bytes,
|
||||||
|
zero_or_more,
|
||||||
|
ints,
|
||||||
|
expires_seconds,
|
||||||
|
expires_ms,
|
||||||
|
)
|
||||||
|
|
||||||
|
optional_bitcount_range = st.just(()) | st.tuples(int_as_bytes, int_as_bytes)
|
||||||
|
# todo: Should be addressed
|
||||||
|
# str_len = st.integers(min_value=-3, max_value=3) | st.integers(
|
||||||
|
# min_value=-2147483647, max_value=2147483648
|
||||||
|
# )
|
||||||
|
str_len = st.integers(min_value=-3, max_value=3) | st.integers(
|
||||||
|
min_value=-3000, max_value=3000
|
||||||
|
)
|
||||||
|
|
||||||
|
string_commands = (
|
||||||
|
commands(st.just("append"), keys, values)
|
||||||
|
| commands(st.just("bitcount"), keys, optional_bitcount_range)
|
||||||
|
| commands(st.sampled_from(["incr", "decr"]), keys)
|
||||||
|
| commands(st.sampled_from(["incrby", "decrby"]), keys, values)
|
||||||
|
| commands(st.just("get"), keys)
|
||||||
|
| commands(st.just("getbit"), keys, counts)
|
||||||
|
| commands(
|
||||||
|
st.just("setbit"),
|
||||||
|
keys,
|
||||||
|
counts,
|
||||||
|
st.integers(min_value=0, max_value=1) | ints,
|
||||||
|
)
|
||||||
|
| commands(st.sampled_from(["substr", "getrange"]), keys, str_len, counts)
|
||||||
|
| commands(st.just("getset"), keys, values)
|
||||||
|
| commands(st.just("mget"), st.lists(keys))
|
||||||
|
| commands(st.sampled_from(["mset", "msetnx"]), st.lists(st.tuples(keys, values)))
|
||||||
|
| commands(
|
||||||
|
st.just("set"),
|
||||||
|
keys,
|
||||||
|
values,
|
||||||
|
*zero_or_more("nx", "xx", "keepttl"),
|
||||||
|
)
|
||||||
|
| commands(st.just("setex"), keys, expires_seconds, values)
|
||||||
|
| commands(st.just("psetex"), keys, expires_ms, values)
|
||||||
|
| commands(st.just("setnx"), keys, values)
|
||||||
|
| commands(st.just("setrange"), keys, str_len, values)
|
||||||
|
| commands(st.just("strlen"), keys)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestString(BaseTest):
|
||||||
|
create_command_strategy = commands(st.just("set"), keys, values)
|
||||||
|
command_strategy = string_commands | common_commands
|
54
tests/fakeredis/test/test_hypothesis/test_transaction.py
Normal file
54
tests/fakeredis/test/test_hypothesis/test_transaction.py
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import (
|
||||||
|
BaseTest,
|
||||||
|
commands,
|
||||||
|
values,
|
||||||
|
keys,
|
||||||
|
common_commands,
|
||||||
|
counts,
|
||||||
|
zero_or_more,
|
||||||
|
ints,
|
||||||
|
expires_seconds,
|
||||||
|
expires_ms,
|
||||||
|
)
|
||||||
|
from test.test_hypothesis.test_string import TestString
|
||||||
|
|
||||||
|
|
||||||
|
class TestTransaction(BaseTest):
|
||||||
|
transaction_commands = (
|
||||||
|
commands(st.sampled_from(["multi", "discard", "exec", "unwatch"]))
|
||||||
|
| commands(st.just("watch"), keys)
|
||||||
|
| commands(st.just("append"), keys, values)
|
||||||
|
| commands(st.just("bitcount"), keys)
|
||||||
|
| commands(st.just("bitcount"), keys, values, values)
|
||||||
|
| commands(st.sampled_from(["incr", "decr"]), keys)
|
||||||
|
| commands(st.sampled_from(["incrby", "decrby"]), keys, values)
|
||||||
|
| commands(st.just("get"), keys)
|
||||||
|
| commands(st.just("getbit"), keys, counts)
|
||||||
|
| commands(
|
||||||
|
st.just("setbit"),
|
||||||
|
keys,
|
||||||
|
counts,
|
||||||
|
st.integers(min_value=0, max_value=1) | ints,
|
||||||
|
)
|
||||||
|
| commands(st.sampled_from(["substr", "getrange"]), keys, counts, counts)
|
||||||
|
| commands(st.just("getset"), keys, values)
|
||||||
|
| commands(st.just("mget"), st.lists(keys))
|
||||||
|
| commands(
|
||||||
|
st.sampled_from(["mset", "msetnx"]), st.lists(st.tuples(keys, values))
|
||||||
|
)
|
||||||
|
| commands(
|
||||||
|
st.just("set"),
|
||||||
|
keys,
|
||||||
|
values,
|
||||||
|
*zero_or_more("nx", "xx", "keepttl"),
|
||||||
|
)
|
||||||
|
| commands(st.just("setex"), keys, expires_seconds, values)
|
||||||
|
| commands(st.just("psetex"), keys, expires_ms, values)
|
||||||
|
| commands(st.just("setnx"), keys, values)
|
||||||
|
| commands(st.just("setrange"), keys, counts, values)
|
||||||
|
| commands(st.just("strlen"), keys)
|
||||||
|
)
|
||||||
|
create_command_strategy = TestString.create_command_strategy
|
||||||
|
command_strategy = transaction_commands | common_commands
|
109
tests/fakeredis/test/test_hypothesis/test_zset.py
Normal file
109
tests/fakeredis/test/test_hypothesis/test_zset.py
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
import operator
|
||||||
|
|
||||||
|
import hypothesis.strategies as st
|
||||||
|
|
||||||
|
from test.test_hypothesis.base import (
|
||||||
|
BaseTest,
|
||||||
|
commands,
|
||||||
|
keys,
|
||||||
|
common_commands,
|
||||||
|
counts,
|
||||||
|
fields,
|
||||||
|
zero_or_more,
|
||||||
|
scores,
|
||||||
|
Command,
|
||||||
|
float_as_bytes,
|
||||||
|
)
|
||||||
|
|
||||||
|
score_tests = scores | st.builds(lambda x: b"(" + repr(x).encode(), scores)
|
||||||
|
limits = st.just(()) | st.tuples(st.just("limit"), counts, counts)
|
||||||
|
string_tests = st.sampled_from([b"+", b"-"]) | st.builds(
|
||||||
|
operator.add, st.sampled_from([b"(", b"["]), fields
|
||||||
|
)
|
||||||
|
zset_no_score_create_commands = commands(
|
||||||
|
st.just("zadd"), keys, st.lists(st.tuples(st.just(0), fields), min_size=1)
|
||||||
|
)
|
||||||
|
zset_no_score_commands = ( # TODO: test incr
|
||||||
|
commands(
|
||||||
|
st.just("zadd"),
|
||||||
|
keys,
|
||||||
|
*zero_or_more("nx", "xx", "ch", "incr"),
|
||||||
|
st.lists(st.tuples(st.just(0), fields)),
|
||||||
|
)
|
||||||
|
| commands(st.just("zlexcount"), keys, string_tests, string_tests)
|
||||||
|
| commands(
|
||||||
|
st.sampled_from(["zrangebylex", "zrevrangebylex"]),
|
||||||
|
keys,
|
||||||
|
string_tests,
|
||||||
|
string_tests,
|
||||||
|
limits,
|
||||||
|
)
|
||||||
|
| commands(st.just("zremrangebylex"), keys, string_tests, string_tests)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def optional(arg):
|
||||||
|
return st.none() | st.just(arg)
|
||||||
|
|
||||||
|
|
||||||
|
def build_zstore(command, dest, sources, weights, aggregate) -> Command:
|
||||||
|
args = [command, dest, len(sources)]
|
||||||
|
args += [source[0] for source in sources]
|
||||||
|
if weights:
|
||||||
|
args.append("weights")
|
||||||
|
args += [source[1] for source in sources]
|
||||||
|
if aggregate:
|
||||||
|
args += ["aggregate", aggregate]
|
||||||
|
return Command(args)
|
||||||
|
|
||||||
|
|
||||||
|
class TestZSet(BaseTest):
|
||||||
|
zset_commands = (
|
||||||
|
commands(
|
||||||
|
st.just("zadd"),
|
||||||
|
keys,
|
||||||
|
*zero_or_more("nx", "xx", "ch", "incr"),
|
||||||
|
st.lists(st.tuples(scores, fields)),
|
||||||
|
)
|
||||||
|
| commands(st.just("zcard"), keys)
|
||||||
|
| commands(st.just("zcount"), keys, score_tests, score_tests)
|
||||||
|
| commands(st.just("zincrby"), keys, scores, fields)
|
||||||
|
| commands(
|
||||||
|
st.sampled_from(["zrange", "zrevrange"]),
|
||||||
|
keys,
|
||||||
|
counts,
|
||||||
|
counts,
|
||||||
|
optional("withscores"),
|
||||||
|
)
|
||||||
|
| commands(
|
||||||
|
st.sampled_from(["zrangebyscore", "zrevrangebyscore"]),
|
||||||
|
keys,
|
||||||
|
score_tests,
|
||||||
|
score_tests,
|
||||||
|
limits,
|
||||||
|
optional("withscores"),
|
||||||
|
)
|
||||||
|
| commands(st.sampled_from(["zrank", "zrevrank"]), keys, fields)
|
||||||
|
| commands(st.just("zrem"), keys, st.lists(fields))
|
||||||
|
| commands(st.just("zremrangebyrank"), keys, counts, counts)
|
||||||
|
| commands(st.just("zremrangebyscore"), keys, score_tests, score_tests)
|
||||||
|
| commands(st.just("zscore"), keys, fields)
|
||||||
|
| st.builds(
|
||||||
|
build_zstore,
|
||||||
|
command=st.sampled_from(["zunionstore", "zinterstore"]),
|
||||||
|
dest=keys,
|
||||||
|
sources=st.lists(st.tuples(keys, float_as_bytes)),
|
||||||
|
weights=st.booleans(),
|
||||||
|
aggregate=st.sampled_from([None, "sum", "min", "max"]),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# TODO: zscan, zpopmin/zpopmax, bzpopmin/bzpopmax, probably more
|
||||||
|
create_command_strategy = commands(
|
||||||
|
st.just("zadd"), keys, st.lists(st.tuples(scores, fields), min_size=1)
|
||||||
|
)
|
||||||
|
command_strategy = zset_commands | common_commands
|
||||||
|
|
||||||
|
|
||||||
|
class TestZSetNoScores(BaseTest):
|
||||||
|
create_command_strategy = zset_no_score_create_commands
|
||||||
|
command_strategy = zset_no_score_commands | common_commands
|
141
tests/fakeredis/test/test_issues.py
Normal file
141
tests/fakeredis/test/test_issues.py
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import pytest
|
||||||
|
import redis.client
|
||||||
|
|
||||||
|
|
||||||
|
def test_causes_crash(r: redis.Redis):
|
||||||
|
key = b"}W\xfa\x87\xf4"
|
||||||
|
key2 = b"\xf3\xba\x00\xa1\x1c\xac\x01A\x8b\xc4\xe9\xe2\xa8"
|
||||||
|
r.rpush(key, b"!\xef\x9e\xd2", b"1175417134")
|
||||||
|
r.rpoplpush(key, key)
|
||||||
|
r.lrange(key, -1, 14795)
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.rename(key2, key2)
|
||||||
|
r.lrange(key, 2, 0)
|
||||||
|
r.sort(key, alpha=True)
|
||||||
|
r.llen(key)
|
||||||
|
r.keys("*")
|
||||||
|
r.keys("*")
|
||||||
|
r.lindex(key, 1)
|
||||||
|
r.exists(key, key2, key, key2, key2, key2, key2, key)
|
||||||
|
r.linsert(key, "AFTER", b"inf", b"!\xef\x9e\xd2")
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.linsert(
|
||||||
|
key,
|
||||||
|
b"W8\xe9&",
|
||||||
|
b"-43950",
|
||||||
|
b"-43950",
|
||||||
|
)
|
||||||
|
r.rpoplpush(key, key)
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.exists()
|
||||||
|
r.lrem(key2, -56700, b"-6.816602725023744e+16")
|
||||||
|
r.lrem(key, -3, b"1175417134")
|
||||||
|
r.llen(key2)
|
||||||
|
r.lrem(key, -3, b"!\xef\x9e\xd2")
|
||||||
|
|
||||||
|
|
||||||
|
def test_another_test_causes_crash(r: redis.Redis):
|
||||||
|
key1 = b"\xc2\xdb"
|
||||||
|
key2 = b"z`\xf8,\xe2\x02\xb3\x85\xc5"
|
||||||
|
key3 = b"\xf4<\xe1\xb6\xcb\xde\xaf"
|
||||||
|
key4 = b"\xad"
|
||||||
|
r.rpush(key1, b"i\x05\x0b\xb1")
|
||||||
|
r.rpush(
|
||||||
|
key2,
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"\\h\xf2",
|
||||||
|
)
|
||||||
|
r.rpush(
|
||||||
|
key3,
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
)
|
||||||
|
r.rpush(
|
||||||
|
key1,
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
)
|
||||||
|
r.rpush(key2, b"i\x05\x0b\xb1")
|
||||||
|
|
||||||
|
r.lpop(b"")
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.rpushx(key4)
|
||||||
|
r.move(b"", 1)
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.move(key3, -730)
|
||||||
|
r.ltrim(key3, -51547, -2)
|
||||||
|
r.rpoplpush(key4, b"")
|
||||||
|
|
||||||
|
r.rpush(key4, b"i\x05\x0b\xb1", b"\\h\xf2", b"\\h\xf2")
|
||||||
|
r.persist(key2)
|
||||||
|
r.exists(key1, key1, key1)
|
||||||
|
|
||||||
|
r.ltrim(b"", -12584, -3)
|
||||||
|
r.lrem(key4, -1, b"i\x05\x0b\xb1")
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.linsert(key2, b"\xa5", b"\\h\xf2", b"i\x05\x0b\xb1")
|
||||||
|
r.linsert(key2, "BEFORE", b"\\h\xf2", b"\\h\xf2")
|
||||||
|
r.ltrim(key2, 1, -2_147_483_648)
|
||||||
|
r.ltrim(key1, -4200252, 1)
|
||||||
|
r.rpush(
|
||||||
|
b"",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
)
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.rpop(key1, -2_147_483_648)
|
||||||
|
r.lrem(key1, 77, b"i\x05\x0b\xb1")
|
||||||
|
r.rpoplpush(b"", key2)
|
||||||
|
r.ltrim(b"", 0, 1)
|
||||||
|
r.unlink(b"")
|
||||||
|
r.ltrim(key1, 0, 0)
|
||||||
|
r.lrem(key3, 31029, b"\\h\xf2")
|
||||||
|
r.lrange(key1, -2, -91)
|
||||||
|
r.rpoplpush(key1, key2)
|
||||||
|
r.rpush(
|
||||||
|
key1,
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"\\h\xf2",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"i\x05\x0b\xb1",
|
||||||
|
b"\\h\xf2",
|
||||||
|
)
|
||||||
|
r.ltrim(key1, 0, 18)
|
||||||
|
r.keys("*")
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.move(key4, 993)
|
||||||
|
r.lrange(b"", 0, 38001)
|
||||||
|
with pytest.raises(redis.ResponseError):
|
||||||
|
r.sort(key4)
|
||||||
|
r.lindex(key1, -2)
|
||||||
|
r.rpoplpush(key4, key4)
|
||||||
|
r.lrem(key4, -18528, b"\\h\xf2")
|
Loading…
Add table
Add a link
Reference in a new issue