Skip to content

Commit

Permalink
Integration tests for commands, delete operations, expiry
Browse files Browse the repository at this point in the history
Add test for bloom command arity, behavior and basic error

Set up a base case for valkey bloom filter module

Signed-off-by: Vanessa Tang <[email protected]>

Set info of expansion to Null for NONSCALING mode

Signed-off-by: Vanessa Tang <[email protected]>

Add test for basic valkey command

Signed-off-by: Vanessa Tang <[email protected]>
  • Loading branch information
YueTang-Vanessa committed Oct 3, 2024
1 parent f299216 commit 2b3aacc
Show file tree
Hide file tree
Showing 6 changed files with 225 additions and 20 deletions.
4 changes: 2 additions & 2 deletions src/bloom/command_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ pub fn bloom_filter_info(ctx: &Context, input_args: &[ValkeyString]) -> ValkeyRe
"ITEMS" => Ok(ValkeyValue::Integer(val.cardinality())),
"EXPANSION" => {
if val.expansion == 0 {
return Ok(ValkeyValue::Integer(-1));
return Ok(ValkeyValue::Null);
}
Ok(ValkeyValue::Integer(val.expansion as i64))
}
Expand All @@ -419,7 +419,7 @@ pub fn bloom_filter_info(ctx: &Context, input_args: &[ValkeyString]) -> ValkeyRe
ValkeyValue::SimpleStringStatic("Expansion rate"),
];
if val.expansion == 0 {
result.push(ValkeyValue::Integer(-1));
result.push(ValkeyValue::Null);
} else {
result.push(ValkeyValue::Integer(val.expansion as i64));
}
Expand Down
4 changes: 2 additions & 2 deletions src/bloom/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ pub const ERROR: &str = "ERROR";
pub const NON_SCALING_FILTER_FULL: &str = "ERR non scaling filter is full";
pub const NOT_FOUND: &str = "ERR not found";
pub const ITEM_EXISTS: &str = "ERR item exists";
pub const INVALID_INFO_VALUE: &str = "ERR Invalid information value";
pub const BAD_EXPANSION: &str = "ERR bad expansion";
pub const INVALID_INFO_VALUE: &str = "Invalid information value";
pub const BAD_EXPANSION: &str = "Bad expansion";
pub const BAD_CAPACITY: &str = "ERR bad capacity";
pub const BAD_ERROR_RATE: &str = "ERR bad error rate";
pub const ERROR_RATE_RANGE: &str = "ERR (0 < error rate range < 1)";
Expand Down
63 changes: 55 additions & 8 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
import time
import pytest
from valkey import ResponseError
from valkeytests.valkey_test_case import ValkeyTestCase
from valkey_bloom_test_case import ValkeyBloomTestCaseBase
from valkeytests.conftest import resource_port_tracker
import logging
import os

class TestBloomBasic(ValkeyTestCase):

def get_custom_args(self):
self.set_server_version(os.environ['SERVER_VERSION'])
return {
'loadmodule': os.getenv('MODULE_PATH'),
}
class TestBloomBasic(ValkeyBloomTestCaseBase):

def test_basic(self):
client = self.server.get_new_client()
Expand All @@ -31,3 +25,56 @@ def test_basic(self):
assert bf_exists_result == 1
bf_exists_result = client.execute_command('BF.EXISTS filter1 item2')
assert bf_exists_result == 0

def test_bloom_with_basic_valkey_command(self):
client = self.server.get_new_client()
# check bloom filter with basic valkey command
# modify and delete
assert client.execute_command('BF.ADD filter1 item1') == 1
assert client.execute_command('DEL filter1') == 1
assert client.execute_command('BF.EXISTS filter1 item1') == 0
assert client.execute_command('FLUSHALL')
assert client.info_obj().num_keys() == 0
assert client.execute_command('BF.ADD A ITEMA') == 1
assert client.execute_command('BF.ADD B ITEMB') == 1
assert client.execute_command('UNLINK A B C') == 2
assert client.execute_command('BF.MEXISTS A ITEMA ITEMB') == [0, 0]
assert client.execute_command('BF.ADD key1 val1') == 1
assert client.execute_command('BF.ADD key2 val2') == 1
assert client.execute_command('TOUCH key1 key2') == 2

# expiration
assert client.execute_command('BF.ADD key3 val3') == 1
time.sleep(1)
assert client.execute_command('OBJECT IDLETIME KEY') == None
assert client.execute_command('OBJECT IDLETIME key3') > 0
assert client.execute_command('BF.ADD TEST_EXP ITEM') == 1
assert client.execute_command('TTL TEST_EXP') == -1
curr_time = int(time.time())
assert client.execute_command(f'EXPIREAT TEST_EXP {curr_time - 1}') == 1
assert client.execute_command('BF.EXISTS TEST_EXP ITEM') == 0
assert client.execute_command('BF.ADD TEST_PERSIST ITEM') == 1
assert client.execute_command('TTL TEST_PERSIST') == -1
assert client.execute_command(f'EXPIREAT TEST_PERSIST {curr_time + 100000}') == 1
assert client.execute_command('BF.EXISTS TEST_PERSIST ITEM') == 1
assert client.execute_command('TTL TEST_PERSIST') > 0
assert client.execute_command('PERSIST TEST_PERSIST') == 1
assert client.execute_command('TTL TEST_PERSIST') == -1

# transaction
assert client.execute_command('MULTI') == b'OK'
assert client.execute_command('BF.ADD M1 V1') == b'QUEUED'
assert client.execute_command('BF.ADD M2 V2') == b'QUEUED'
assert client.execute_command('BF.EXISTS M1 V1') == b'QUEUED'
assert client.execute_command('DEL M1') == b'QUEUED'
assert client.execute_command('BF.EXISTS M1 V1') == b'QUEUED'
assert client.execute_command('EXEC') == [1, 1, 1, 1, 0]

# lua
load_filter = """
redis.call('BF.ADD', 'LUA1', 'ITEM1');
redis.call('BF.ADD', 'LUA2', 'ITEM2');
redis.call('BF.MADD', 'LUA2', 'ITEMS3', 'ITEMS4', 'ITEMS5');
"""
client.eval(load_filter, 0)
assert client.execute_command('BF.MEXISTS LUA2 ITEMS2 ITEMS3 ITEMS4') == [0, 1, 1]
136 changes: 136 additions & 0 deletions tests/test_bloom_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import pytest
from valkey_bloom_test_case import ValkeyBloomTestCaseBase
from valkeytests.conftest import resource_port_tracker

class TestBloomCommand(ValkeyBloomTestCaseBase):

def verify_command_arity(self, command, expected_arity):
command_info = self.client.execute_command('COMMAND', 'INFO', command)
actual_arity = command_info.get(command).get('arity')
assert actual_arity == expected_arity, f"Arity mismatch for command '{command}'"

def test_bloom_command_arity(self):
self.verify_command_arity('BF.EXISTS', -1)
self.verify_command_arity('BF.ADD', -1)
self.verify_command_arity('BF.MEXISTS', -1)
self.verify_command_arity('BF.MADD', -1)
self.verify_command_arity('BF.CARD', -1)
self.verify_command_arity('BF.RESERVE', -1)
self.verify_command_arity('BF.INFO', -1)
self.verify_command_arity('BF.INSERT', -1)

def test_bloom_command_error(self):
basic_error_test_cases = [
# not found
('BF.INFO TEST404', 'not found'),
# incorrect syntax and argument usage
('BF.ADDX TEST KEY', 'unknown command \'BF.ADDX\', with args beginning with: \'TEST\' \'KEY\' '),
('BF.EXIST KEY', 'unknown command \'BF.EXIST\', with args beginning with: \'KEY\' '),
('bf.reserv', 'unknown command \'bf.reserv\', with args beginning with: '),
('bf.info key item', 'Invalid information value'),
('bf.insert key CAPACITY 10000 ERROR 0.01 EXPANSION 0.99 NOCREATE NONSCALING ITEMS test1 test2 test3', 'Bad expansion'),
('BF.INSERT KEY HELLO WORLD', 'wrong number of arguments for \'BF.INSERT\' command'),
('BF.RESERVE KEY SSS SSS', 'bad error rate'),
('BF.RESERVE KEY 0.01 SSS', 'bad capacity'),
('BF.RESERVE KEY 0.01 0.01', 'bad capacity'),
('BF.RESERVE KEY 0.01 -1', 'bad capacity'),
('BF.RESERVE TT 0.01 1 NONSCALING EXPANSION 1', 'wrong number of arguments for \'BF.RESERVE\' command'),
('BF.RESERVE bf 0.01 1000', 'item exists'),
('BF.ADD bf_non 2', 'non scaling filter is full'),
('BF.RESERVE TEST_CAP 0.50 0', '(capacity should be larger than 0)'),
('BF.INSERT KEY error 2 ITEMS test1', '(0 < error rate range < 1)'),

# wrong number of arguments
('BF.ADD TEST', 'wrong number of arguments for \'BF.ADD\' command'),
('BF.ADD', 'wrong number of arguments for \'BF.ADD\' command'),
('BF.ADD HELLO TEST WORLD', 'wrong number of arguments for \'BF.ADD\' command'),
('BF.CARD KEY ITEM', 'wrong number of arguments for \'BF.CARD\' command'),
('bf.card', 'wrong number of arguments for \'BF.CARD\' command'),
('BF.EXISTS', 'wrong number of arguments for \'BF.EXISTS\' command'),
('bf.exists item', 'wrong number of arguments for \'BF.EXISTS\' command'),
('bf.exists key item hello', 'wrong number of arguments for \'BF.EXISTS\' command'),
('BF.INFO', 'wrong number of arguments for \'BF.INFO\' command'),
('bf.info key capacity size', 'wrong number of arguments for \'BF.INFO\' command'),
('BF.INSERT', 'wrong number of arguments for \'BF.INSERT\' command'),
('BF.INSERT KEY', 'wrong number of arguments for \'BF.INSERT\' command'),
('BF.INSERT KEY HELLO', 'wrong number of arguments for \'BF.INSERT\' command'),
('BF.MADD', 'wrong number of arguments for \'BF.MADD\' command'),
('BF.MADD KEY', 'wrong number of arguments for \'BF.MADD\' command'),
('BF.MEXISTS', 'wrong number of arguments for \'BF.MEXISTS\' command'),
('BF.MEXISTS INFO', 'wrong number of arguments for \'BF.MEXISTS\' command'),
('BF.RESERVE', 'wrong number of arguments for \'BF.RESERVE\' command'),
('BF.RESERVE KEY', 'wrong number of arguments for \'BF.RESERVE\' command'),
('BF.RESERVE KEY SSS', 'wrong number of arguments for \'BF.RESERVE\' command'),
('BF.RESERVE TT1 0.01 1 NONSCALING test1 test2 test3', 'wrong number of arguments for \'BF.RESERVE\' command'),
]

assert self.client.execute_command('FLUSHALL')
assert self.client.info_obj().num_keys() == 0
assert self.client.execute_command('BF.ADD key item') == 1
assert self.client.execute_command('BF.RESERVE bf 0.01 1000') == b'OK'

# non scaling filter
assert self.client.execute_command('BF.RESERVE bf_non 0.01 2 NONSCALING') == b'OK'
assert self.client.execute_command('BF.ADD bf_non 0') == 1
assert self.client.execute_command('BF.ADD bf_non 1') == 1

for test_case in basic_error_test_cases:
cmd = test_case[0]
expected_err_reply = test_case[1]
self.verify_error_response(self.client, cmd, expected_err_reply)

def test_bloom_command_behavior(self):
basic_behavior_test_case = [
('BF.ADD key item', 1),
('BF.ADD key item', 0),
('BF.ADD key item1', 1),
('BF.EXISTS key item', 1),
('BF.EXISTS key item2', 0),
('BF.MADD key item item2', [0, 1]),
('BF.EXISTS key item', 1),
('BF.EXISTS key item2', 1),
('BF.EXISTS key item3', 0),
('BF.MADD hello world1 world2 world3', [1, 1, 1]),
('BF.MADD hello world1 world2 world3 world4', [0, 0, 0, 1]),
('BF.MEXISTS hello world5', [0]),
('BF.MADD hello world5', [1]),
('BF.MEXISTS hello world5 world6 world7', [1, 0, 0]),
('BF.INSERT TEST ITEMS ITEMS', [1]),
('BF.INSERT TEST CAPACITY 1000 ITEMS ITEMS', [0]),
('BF.INSERT TEST CAPACITY 200 error 0.99 ITEMS ITEMS ITEMS1 ITEMS2', [0, 1, 1]),
('BF.INSERT TEST CAPACITY 300 ERROR 0.90 EXPANSION 1 ITEMS ITEMS FOO', [0, 1]),
('BF.INSERT TEST ERROR 0.80 EXPANSION 3 NOCREATE items BOO', [1]),
('BF.INSERT TEST ERROR 0.80 EXPANSION 1 NOCREATE NONSCALING items BOO', [0]),
('BF.INFO TEST Capacity', 100),
('BF.INFO TEST ITEMS', 5),
('bf.info TEST expansion', 2),
('BF.CARD key', 3),
('BF.CARD hello', 5),
('BF.CARD TEST', 5),
('bf.card HELLO', 0),
('BF.RESERVE bf 0.01 1000', b'OK'),
('BF.RESERVE bf_exp 0.01 1000 EXPANSION 2', b'OK'),
('BF.RESERVE bf_non 0.01 1000 NONSCALING', b'OK'),
('bf.info bf_exp expansion', 2),
('BF.INFO bf_non expansion', None),
]

assert self.client.execute_command('FLUSHALL')
assert self.client.info_obj().num_keys() == 0

for test_case in basic_behavior_test_case:
cmd = test_case[0]
expected_result = test_case[1]
self.verify_command_behavior_as_expected(self.client, cmd, expected_result)

# test bf.info
assert self.client.execute_command('BF.RESERVE BF_INFO 0.50 2000 NONSCALING') == b'OK'
bf_info = self.client.execute_command('BF.INFO BF_INFO')
capacity_index = bf_info.index(b'Capacity') + 1
filter_index = bf_info.index(b'Number of filters') + 1
item_index = bf_info.index(b'Number of items inserted') + 1
expansion_index = bf_info.index(b'Expansion rate') + 1
assert bf_info[capacity_index] == self.client.execute_command('BF.INFO BF_INFO CAPACITY') == 2000
assert bf_info[filter_index] == self.client.execute_command('BF.INFO BF_INFO FILTERS') == 1
assert bf_info[item_index] == self.client.execute_command('BF.INFO BF_INFO ITEMS') == 0
assert bf_info[expansion_index] == self.client.execute_command('BF.INFO BF_INFO EXPANSION') == None
10 changes: 2 additions & 8 deletions tests/test_save_and_restore.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import pytest, time
import os
from valkey_test_case import ValkeyTestCase
from valkey_bloom_test_case import ValkeyBloomTestCaseBase
from valkeytests.conftest import resource_port_tracker

class TestBloomSaveRestore(ValkeyTestCase):

def get_custom_args(self):
self.set_server_version(os.environ['SERVER_VERSION'])
return {
'loadmodule': os.getenv('MODULE_PATH'),
}
class TestBloomSaveRestore(ValkeyBloomTestCaseBase):

def test_basic_save_and_restore(self):
client = self.server.get_new_client()
Expand Down
28 changes: 28 additions & 0 deletions tests/valkey_bloom_test_case.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
import pytest
from valkeytests.valkey_test_case import ValkeyTestCase
from valkey import ResponseError

class ValkeyBloomTestCaseBase(ValkeyTestCase):

def get_custom_args(self):
self.set_server_version(os.environ['SERVER_VERSION'])
return {
'loadmodule': os.getenv('MODULE_PATH'),
}

def verify_error_response(self, client, cmd, expected_err_reply):
try:
client.execute_command(cmd)
assert False
except ResponseError as e:
assert_error_msg = f"Actual error message: '{str(e)}' is different from expected error message '{expected_err_reply}'"
assert str(e) == expected_err_reply, assert_error_msg

def verify_command_behavior_as_expected(self, client, cmd, expected_result):
try:
cmd_actual_result = client.execute_command(cmd)
assert_error_msg = f"Actual command response '{cmd_actual_result}' is different from expected response '{expected_result}'"
assert cmd_actual_result == expected_result, assert_error_msg
except:
print("Something went wrong in command behavior verification")

0 comments on commit 2b3aacc

Please sign in to comment.