-
Notifications
You must be signed in to change notification settings - Fork 981
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Pytests overhaul #569
Pytests overhaul #569
Conversation
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
tests/dragonfly/generator.py
Outdated
@@ -0,0 +1,245 @@ | |||
import asyncio |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you add here what it does?
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
412e627
to
c158593
Compare
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
c158593
to
a1ff692
Compare
@@ -99,6 +105,16 @@ def create(self, **kwargs) -> DflyInstance: | |||
self.instances.append(instance) | |||
return instance | |||
|
|||
def start_all(self, instances): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can use python test containers and not run them as sub processes (if I understand correctly, this is what it does here, spins DFs sub processes). They reject my PR for supporting DF, but you can start a DF container with the right parameters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is inconvenient (increased memory requirements) and requires re-building a container just for testing some change
assert False, str(e) | ||
def gen_test_data(): | ||
for i in range(10): | ||
yield "key-"+str(i), "value-"+str(i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick - yield f"key-{i}" f"value-{i}"
BTW why did you removed the "gen_test_data"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because its now the only place where it would be used... Maybe I should keep it though
def gen_test_data(n, start=0, seed=None): | ||
for i in range(start, n): | ||
yield "k-"+str(i), "v-"+str(i) + ("-"+str(seed) if seed else "") | ||
async def wait_available_async(client: aioredis.Redis): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at what cases this is useful? I mean from what I know await will block the current task until the awaited function will return. So when the iteration will take place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for waiting until an instance becomes available for queries (i.e. exits the LOADING state). Its indeed supposed to block all this time, because the test has nothing else to to except wait for the instance to be available for comparing data
client = aioredis.Redis(port=port, db=target_db) | ||
return DataCapture(await self._capture_entries(client, keys)) | ||
|
||
async def compare(self, initial_capture, port=6379): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you have self.port, why not making this defaulted to self.port and not to 6379?
tests/dragonfly/utility.py
Outdated
('LPOP {k}', ValueType.LIST), | ||
#('SADD {k} {val}', ValueType.SET), | ||
#('SPOP {k}', ValueType.SET), | ||
('HSETNX {k} v0 {val}', ValueType.HSET), | ||
('HINCRBY {k} v1 1', ValueType.HSET), | ||
#('ZPOPMIN {k} 1', ValueType.ZSET), | ||
#('ZADD {k} 0 {val}', ValueType.ZSET) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Stable state currently has issues with set and zset commands (for example spop pops different values), so I commented them out for now to let the tests run
@@ -99,6 +105,16 @@ def create(self, **kwargs) -> DflyInstance: | |||
self.instances.append(instance) | |||
return instance | |||
|
|||
def start_all(self, instances): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is inconvenient (increased memory requirements) and requires re-building a container just for testing some change
assert False, str(e) | ||
def gen_test_data(): | ||
for i in range(10): | ||
yield "key-"+str(i), "value-"+str(i) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because its now the only place where it would be used... Maybe I should keep it though
def gen_test_data(n, start=0, seed=None): | ||
for i in range(start, n): | ||
yield "k-"+str(i), "v-"+str(i) + ("-"+str(seed) if seed else "") | ||
async def wait_available_async(client: aioredis.Redis): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is for waiting until an instance becomes available for queries (i.e. exits the LOADING state). Its indeed supposed to block all this time, because the test has nothing else to to except wait for the instance to be available for comparing data
Totally. I even claim we should use ubuntu 20.04 as a test which version to
assume.
…On Sat, Dec 31, 2022, 11:58 Vladislav ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In tests/dragonfly/utility.py
<#569 (comment)>
:
> + ('LPOP {k}', ValueType.LIST),
+ #('SADD {k} {val}', ValueType.SET),
+ #('SPOP {k}', ValueType.SET),
+ ('HSETNX {k} v0 {val}', ValueType.HSET),
+ ('HINCRBY {k} v1 1', ValueType.HSET),
+ #('ZPOPMIN {k} 1', ValueType.ZSET),
+ #('ZADD {k} 0 {val}', ValueType.ZSET)
Stable state currently has issues with set and zset commands (for example
spop pops different values), so I commented them out for now to let the
tests run
------------------------------
In tests/dragonfly/__init__.py
<#569 (comment)>
:
> @@ -99,6 +105,16 @@ def create(self, **kwargs) -> DflyInstance:
self.instances.append(instance)
return instance
+ def start_all(self, instances):
This is inconvenient (increased memory requirements) and requires
re-building a container just for testing some change
------------------------------
In tests/dragonfly/server_family_test.py
<#569 (comment)>
:
> def test_scan(client):
- try:
- for key, val in gen_test_data(n=10, seed="set-test-key"):
- res = client.set(key, val)
- assert res is not None
- cur, keys = client.scan(cursor=0, match=key, count=2)
- assert cur == 0
- assert len(keys) == 1
- assert keys[0] == key
- except Exception as e:
- assert False, str(e)
+ def gen_test_data():
+ for i in range(10):
+ yield "key-"+str(i), "value-"+str(i)
Because its now the only place where it would be used... Maybe I should
keep it though
------------------------------
In tests/dragonfly/utility.py
<#569 (comment)>
:
>
-def gen_test_data(n, start=0, seed=None):
- for i in range(start, n):
- yield "k-"+str(i), "v-"+str(i) + ("-"+str(seed) if seed else "")
+async def wait_available_async(client: aioredis.Redis):
This is for waiting until an instance becomes available for queries (i.e.
exits the LOADING state). Its indeed supposed to block all this time,
because the test has nothing else to to except wait for the instance to be
available for comparing data
------------------------------
In tests/dragonfly/utility.py
<#569 (comment)>
:
> + s = self.set_for_type(t)
+
+ if s is None or len(s) == 0:
+ return None, None
+
+ k = s.pop()
+ if not pop:
+ s.add(k)
+
+ return k, t
+
+ def generate_val(self, t: ValueType):
+ def rand_str(k=3, s=''):
+ return s.join(random.choices(string.ascii_letters, k=k))
+
+ if t == ValueType.STRING:
I know it exists, but its Python 3.10, whereas most repos of most distros
cover for now only Python 3.9
I would rather not use fancy new features so that its runnable with
default python and doesn't require installing a new version just to do it
—
Reply to this email directly, view it on GitHub
<#569 (review)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AA4BFCBV3AQ37HLIWIXA3NDWP77VRANCNFSM6AAAAAATCAJGMY>
.
You are receiving this because you commented.Message ID:
***@***.***>
|
Signed-off-by: Vladislav <vlad@dragonflydb.io>
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
1b4ae81
to
cbccb3e
Compare
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
6f7d0b4
to
a1513f5
Compare
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
They seem to pass now consistently It takes about 3 min to run them fully on my machine. I reduced the tests a little because if it fails, it usually tends to do so already on the medium sized ones. Otherwise testing will take eternity 😄 Some parts are commented out - those are the commands we don't support. It works on Redis though and once we support them, we'll just uncomment the parts (like SPOP for example) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Vlad, it's an amazing addition to our tests and to our testing methodology!
You really raise the bar for our testing quality. I gave you a few readability comments.
tests/README.md
Outdated
@@ -15,6 +15,8 @@ You can override the location of the binary using `DRAGONFLY_PATH` environment v | |||
### Custom arguments | |||
|
|||
- use `--gdb` to start all instances inside gdb. | |||
- use `--df arg=val` to pass custom arguments to all dragonfly instances. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you provide a full command instead of a single option?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does full command mean? You can use it multiple times like --df logtostdout --df proactor_threads=2
, I'll add this info
Signed-off-by: Vladislav Oleshko <vlad@dragonflydb.io>
Pushed fixes to you comments and some more last minute ones |
Big testing overhaul
A lot of new features that we introduce for replication, snapshotting, compression, serialization and cancellation don't have proper tests. Many of those features are also hard to test in general, under load and for corner cases.
So far pytests have made a good job in uncovering bugs, but they used only simple commands and a single database.
The new
DflySeeder
can issue command sequences that converge to some targeted number of keys and oscilliate upon reaching it. It supports all main data types (strings, lists, sets, hsets, zsets) and 10 incremental commands (but can be extended to any number).It allows creating captures on the master instance (that is expected to work faultless) and then comparing them to the state on different instances, showing any some changes if needed.
Its designed to be efficient (fully async, parallel work on multiple dbs, pipelined requests) so python's performance is not the bottleneck.
Example:
fixes #530