Skip to content

Commit

Permalink
Add Integration Testing for correctness of scaling and non scaling fi…
Browse files Browse the repository at this point in the history
…lters and other core functionality validation (#15)

* Add Integration Testing for correctness of scaling and non scaling filters, and maxmemory, memory usage, type, encoding, etc

* Refactor correctness integration tests

Signed-off-by: Karthik Subbarao <karthikrs2021@gmail.com>

* Fix comment

Signed-off-by: Karthik Subbarao <karthikrs2021@gmail.com>

* Refactor

Signed-off-by: Karthik Subbarao <karthikrs2021@gmail.com>

* Fix import

Signed-off-by: Karthik Subbarao <karthikrs2021@gmail.com>

---------

Signed-off-by: KarthikSubbarao <karthikrs2021@gmail.com>
Signed-off-by: Karthik Subbarao <karthikrs2021@gmail.com>
  • Loading branch information
KarthikSubbarao authored Oct 15, 2024
1 parent fde4d37 commit f5aa39e
Show file tree
Hide file tree
Showing 6 changed files with 329 additions and 74 deletions.
67 changes: 26 additions & 41 deletions src/bloom/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,14 +255,17 @@ mod tests {
) -> (i64, i64) {
let mut new_item_idx = starting_item_idx;
let mut fp_count = 0;
while bf.cardinality() < capacity_needed {
let mut cardinality = bf.cardinality();
while cardinality < capacity_needed {
let item = format!("{}{}", rand_prefix, new_item_idx);
let result = bf.add_item(item.as_bytes());
match result {
Ok(0) => {
fp_count += 1;
}
Ok(1) => {}
Ok(1) => {
cardinality += 1;
}
Ok(i64::MIN..=-1_i64) | Ok(2_i64..=i64::MAX) => {
panic!("We do not expect add_item to return any Integer other than 0 or 1.")
}
Expand All @@ -272,22 +275,21 @@ mod tests {
};
new_item_idx += 1;
}
(fp_count, new_item_idx)
(fp_count, new_item_idx - 1)
}

/// Loops from the start index till the end index and uses the exists operation on the provided bloom filter.
/// The item name used in exists operations is rand_prefix + the index (based on the iteration).
/// The results are matched against the `expected_result` and an error_count tracks the wrong results.
/// Asserts that the error_count is within the expected false positive (+ margin) rate.
fn item_exists_test(
/// Returns the error count and number of operations performed.
fn check_items_exist(
bf: &BloomFilterType,
start_idx: i64,
end_idx: i64,
expected_fp_rate: f32,
fp_margin: f32,
expected_result: bool,
rand_prefix: &String,
) {
) -> (i64, i64) {
let mut error_count = 0;
for i in start_idx..=end_idx {
let item = format!("{}{}", rand_prefix, i);
Expand All @@ -297,8 +299,7 @@ mod tests {
}
}
let num_operations = (end_idx - start_idx) + 1;
// Validate that the real fp_rate is not much more than the configured fp_rate.
fp_assert(error_count, num_operations, expected_fp_rate, fp_margin);
(error_count, num_operations)
}

fn fp_assert(error_count: i64, num_operations: i64, expected_fp_rate: f32, fp_margin: f32) {
Expand Down Expand Up @@ -365,24 +366,22 @@ mod tests {
.filters
.iter()
.any(|filter| filter.bloom.bitmap() == restore_filter.bloom.bitmap())));
item_exists_test(
let (error_count, _) = check_items_exist(
restored_bloom_filter_type,
1,
add_operation_idx,
expected_fp_rate,
fp_margin,
true,
rand_prefix,
);
item_exists_test(
assert!(error_count == 0);
let (error_count, num_operations) = check_items_exist(
restored_bloom_filter_type,
add_operation_idx + 1,
add_operation_idx * 2,
expected_fp_rate,
fp_margin,
false,
rand_prefix,
);
fp_assert(error_count, num_operations, expected_fp_rate, fp_margin);
}

#[test]
Expand Down Expand Up @@ -411,25 +410,18 @@ mod tests {
fp_assert(error_count, add_operation_idx, expected_fp_rate, fp_margin);
// Validate item "exists" operations on bloom filters are ensuring correctness.
// This tests for items already added to the filter and expects them to exist.
item_exists_test(
&bf,
1,
add_operation_idx,
expected_fp_rate,
fp_margin,
true,
&rand_prefix,
);
let (error_count, _) = check_items_exist(&bf, 1, add_operation_idx, true, &rand_prefix);
assert!(error_count == 0);
// This tests for items which are not added to the filter and expects them to not exist.
item_exists_test(
let (error_count, num_operations) = check_items_exist(
&bf,
add_operation_idx + 1,
add_operation_idx * 2,
expected_fp_rate,
fp_margin,
false,
&rand_prefix,
);
// Validate that the real fp_rate is not much more than the configured fp_rate.
fp_assert(error_count, num_operations, expected_fp_rate, fp_margin);

// Verify restore
let mut restore_bf = BloomFilterType::create_copy_from(&bf);
Expand Down Expand Up @@ -459,14 +451,14 @@ mod tests {
assert_eq!(bf.capacity(), initial_capacity as i64);
assert_eq!(bf.cardinality(), 0);
let mut total_error_count = 0;
let mut add_operation_idx = 1;
let mut add_operation_idx = 0;
// Validate the scaling behavior of the bloom filter.
for filter_idx in 1..=num_filters_to_scale {
let expected_total_capacity = initial_capacity * (expansion.pow(filter_idx) - 1);
let (error_count, new_add_operation_idx) = add_items_till_capacity(
&mut bf,
expected_total_capacity as i64,
add_operation_idx,
add_operation_idx + 1,
&rand_prefix,
);
add_operation_idx = new_add_operation_idx;
Expand All @@ -487,25 +479,18 @@ mod tests {
);
// Validate item "exists" operations on bloom filters are ensuring correctness.
// This tests for items already added to the filter and expects them to exist.
item_exists_test(
&bf,
1,
add_operation_idx,
expected_fp_rate,
fp_margin,
true,
&rand_prefix,
);
let (error_count, _) = check_items_exist(&bf, 1, add_operation_idx, true, &rand_prefix);
assert!(error_count == 0);
// This tests for items which are not added to the filter and expects them to not exist.
item_exists_test(
let (error_count, num_operations) = check_items_exist(
&bf,
add_operation_idx + 1,
add_operation_idx * 2,
expected_fp_rate,
fp_margin,
false,
&rand_prefix,
);
// Validate that the real fp_rate is not much more than the configured fp_rate.
fp_assert(error_count, num_operations, expected_fp_rate, fp_margin);

// Verify restore
let restore_bloom_filter_type = BloomFilterType::create_copy_from(&bf);
Expand Down
93 changes: 65 additions & 28 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class TestBloomBasic(ValkeyBloomTestCaseBase):

def test_basic(self):
client = self.server.get_new_client()
# Validate that the valkey-bloom module is loaded.
module_list_data = client.execute_command('MODULE LIST')
module_list_count = len(module_list_data)
assert module_list_count == 1
Expand All @@ -20,22 +21,58 @@ def test_basic(self):
module_loaded = True
break
assert(module_loaded)
# Validate that all the BF.* commands are supported on the server.
command_cmd_result = client.execute_command('COMMAND')
bf_cmds = ["BF.ADD", "BF.EXISTS", "BF.MADD", "BF.MEXISTS", "BF.INFO", "BF.CARD", "BF.RESERVE", "BF.INSERT"]
assert all(item in command_cmd_result for item in bf_cmds)
# Basic bloom filter create, item add and item exists validation.
bf_add_result = client.execute_command('BF.ADD filter1 item1')
assert bf_add_result == 1
bf_exists_result = client.execute_command('BF.EXISTS filter1 item1')
assert bf_exists_result == 1
bf_exists_result = client.execute_command('BF.EXISTS filter1 item2')
assert bf_exists_result == 0

def test_bloom_modification(self):
def test_copy_and_exists_cmd(self):
client = self.server.get_new_client()
madd_result = client.execute_command('BF.MADD filter item1 item2 item3 item4')
assert client.execute_command('EXISTS filter') == 1
mexists_result = client.execute_command('BF.MEXISTS filter item1 item2 item3 item4')
assert len(madd_result) == 4 and len(mexists_result) == 4
assert client.execute_command('COPY filter new_filter') == 1
assert client.execute_command('EXISTS new_filter') == 1
copy_mexists_result = client.execute_command('BF.MEXISTS new_filter item1 item2 item3 item4')
assert mexists_result == copy_mexists_result

def test_memory_usage_cmd(self):
client = self.server.get_new_client()
assert client.execute_command('BF.ADD filter item1') == 1
memory_usage = client.execute_command('MEMORY USAGE filter')
info_size = client.execute_command('BF.INFO filter SIZE')
assert memory_usage > info_size and info_size > 0

def test_module_data_type(self):
# Validate the name of the Module data type.
client = self.server.get_new_client()
assert client.execute_command('BF.ADD filter item1') == 1
type_result = client.execute_command('TYPE filter')
assert type_result == b"bloomfltr"
# Validate the name of the Module data type.
encoding_result = client.execute_command('OBJECT ENCODING filter')
assert encoding_result == b"raw"

def test_bloom_obj_access(self):
client = self.server.get_new_client()
# check bloom filter with basic valkey command
# cmd touch
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
assert client.execute_command('TOUCH key3') == 0
self.verify_key_number(client, 2)
self.verify_server_key_count(client, 2)
assert client.execute_command('DBSIZE') == 2
random_key = client.execute_command('RANDOMKEY')
assert random_key == b"key1" or random_key == b"key2"

def test_bloom_transaction(self):
client = self.server.get_new_client()
Expand All @@ -47,9 +84,9 @@ def test_bloom_transaction(self):
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]
self.verify_bloom_filter_existence(client, 'M2', 'V2')
self.verify_bloom_filter_existence(client, 'M1', 'V1', should_exist=False)
self.verify_key_number(client, 1)
self.verify_bloom_filter_item_existence(client, 'M2', 'V2')
self.verify_bloom_filter_item_existence(client, 'M1', 'V1', should_exist=False)
self.verify_server_key_count(client, 1)

def test_bloom_lua(self):
client = self.server.get_new_client()
Expand All @@ -61,62 +98,62 @@ def test_bloom_lua(self):
"""
client.eval(load_filter, 0)
assert client.execute_command('BF.MEXISTS LUA2 ITEM1 ITEM3 ITEM4') == [0, 1, 1]
self.verify_key_number(client, 2)
self.verify_server_key_count(client, 2)

def test_bloom_deletes(self):
client = self.server.get_new_client()
# delete
assert client.execute_command('BF.ADD filter1 item1') == 1
self.verify_bloom_filter_existence(client, 'filter1', 'item1')
self.verify_key_number(client, 1)
self.verify_bloom_filter_item_existence(client, 'filter1', 'item1')
self.verify_server_key_count(client, 1)
assert client.execute_command('DEL filter1') == 1
self.verify_bloom_filter_existence(client, 'filter1', 'item1', should_exist=False)
self.verify_key_number(client, 0)
self.verify_bloom_filter_item_existence(client, 'filter1', 'item1', should_exist=False)
self.verify_server_key_count(client, 0)

# flush
self.insert_bloom_filter(client, number_of_bf=10)
self.verify_key_number(client, 10)
self.create_bloom_filters_and_add_items(client, number_of_bf=10)
self.verify_server_key_count(client, 10)
assert client.execute_command('FLUSHALL')
self.verify_key_number(client, 0)
self.verify_server_key_count(client, 0)

# unlink
assert client.execute_command('BF.ADD A ITEMA') == 1
assert client.execute_command('BF.ADD B ITEMB') == 1
self.verify_bloom_filter_existence(client, 'A', 'ITEMA')
self.verify_bloom_filter_existence(client, 'B', 'ITEMB')
self.verify_bloom_filter_existence(client, 'C', 'ITEMC', should_exist=False)
self.verify_key_number(client, 2)
self.verify_bloom_filter_item_existence(client, 'A', 'ITEMA')
self.verify_bloom_filter_item_existence(client, 'B', 'ITEMB')
self.verify_bloom_filter_item_existence(client, 'C', 'ITEMC', should_exist=False)
self.verify_server_key_count(client, 2)
assert client.execute_command('UNLINK A B C') == 2
assert client.execute_command('BF.MEXISTS A ITEMA ITEMB') == [0, 0]
self.verify_bloom_filter_existence(client, 'A', 'ITEMA', should_exist=False)
self.verify_bloom_filter_existence(client, 'B', 'ITEMB', should_exist=False)
self.verify_key_number(client, 0)
self.verify_bloom_filter_item_existence(client, 'A', 'ITEMA', should_exist=False)
self.verify_bloom_filter_item_existence(client, 'B', 'ITEMB', should_exist=False)
self.verify_server_key_count(client, 0)

def test_bloom_expiration(self):
client = self.server.get_new_client()
# expiration
# cmd object idletime
self.verify_key_number(client, 0)
self.verify_server_key_count(client, 0)
assert client.execute_command('BF.ADD TEST_IDLE val3') == 1
self.verify_bloom_filter_existence(client, 'TEST_IDLE', 'val3')
self.verify_key_number(client, 1)
self.verify_bloom_filter_item_existence(client, 'TEST_IDLE', 'val3')
self.verify_server_key_count(client, 1)
time.sleep(1)
assert client.execute_command('OBJECT IDLETIME test_idle') == None
assert client.execute_command('OBJECT IDLETIME TEST_IDLE') > 0
# cmd ttl, expireat
assert client.execute_command('BF.ADD TEST_EXP ITEM') == 1
assert client.execute_command('TTL TEST_EXP') == -1
self.verify_bloom_filter_existence(client, 'TEST_EXP', 'ITEM')
self.verify_key_number(client, 2)
self.verify_bloom_filter_item_existence(client, 'TEST_EXP', 'ITEM')
self.verify_server_key_count(client, 2)
curr_time = int(time.time())
assert client.execute_command(f'EXPIREAT TEST_EXP {curr_time + 5}') == 1
wait_for_equal(lambda: client.execute_command('BF.EXISTS TEST_EXP ITEM'), 0)
self.verify_key_number(client, 1)
self.verify_server_key_count(client, 1)
# cmd persist
assert client.execute_command('BF.ADD TEST_PERSIST ITEM') == 1
assert client.execute_command('TTL TEST_PERSIST') == -1
self.verify_bloom_filter_existence(client, 'TEST_PERSIST', 'ITEM')
self.verify_key_number(client, 2)
self.verify_bloom_filter_item_existence(client, 'TEST_PERSIST', 'ITEM')
self.verify_server_key_count(client, 2)
assert client.execute_command(f'EXPIREAT TEST_PERSIST {curr_time + 100000}') == 1
assert client.execute_command('TTL TEST_PERSIST') > 0
assert client.execute_command('PERSIST TEST_PERSIST') == 1
Expand Down
Loading

0 comments on commit f5aa39e

Please sign in to comment.