Skip to content

Commit 27e83c4

Browse files
committed
feat(rust): test Valkey 9.2 INFO fields
Add an `info_returns_expected_fields` test, run against standalone servers (RESP2 and RESP3), which replaces `info_non_empty` and `info_sections`: - Default INFO contains one field from each default section. - Valkey 9.2.0+ only: INFO persistence `rdb_current_bgsave_type` and `rdb_last_bgsave_type` are one of fork, forkless, or none, and `forkless_estimated_seconds_remaining` is at least -1. The typed INFO, LATENCY DOCTOR/GRAPH, BGSAVE, and BGREWRITEAOF methods fail on cluster clients (#7362), so the test is standalone only and its `cluster_info` check is commented out until that is fixed. Mark each affected method with a TODO. Add `get_info`, `parse_int`, `INFO_DEFAULT_FIELDS`, and `BGSAVE_TYPES` test helpers, and allow the `use super::*` import in `resp_test!` and `matrix_test!` to go unused. Part of #7199. Signed-off-by: currantw <taylor.curran@improving.com>
1 parent 3dbfcf8 commit 27e83c4

6 files changed

Lines changed: 110 additions & 18 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060

6161
### Changes
6262

63+
* Go, Java, Node, Python: Add `cluster_info` `INFO` section (Valkey 9.1) ([#7199](https://github.com/valkey-io/valkey-glide/issues/7199))
6364
* Java: add useMutualTlsFromKeyStore to load mTLS client identity ([#7258](https://github.com/valkey-io/valkey-glide/pull/7258))
6465
* Java: Add `HGETDEL` command support ([#6145](https://github.com/valkey-io/valkey-glide/issues/6145))
6566
* Node: Add `HGETDEL` command support ([#6145](https://github.com/valkey-io/valkey-glide/issues/6145))

‎rust/src/commands/server_management.rs‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ use std::collections::HashMap;
1616
/// Server-management commands (`INFO`, `DBSIZE`, `FLUSHALL`, `CONFIG ...`, `TIME`).
1717
#[async_trait]
1818
pub trait ServerManagementCommands: CommandExecutor {
19+
// TODO #7362: Return per-node replies for cluster clients.
1920
/// Get server information and statistics (`INFO`).
2021
async fn info(&self) -> ValkeyResult<String> {
2122
String::from_owned_valkey_value(self.execute_command(cmd("INFO"), None).await?)
2223
}
2324

25+
// TODO #7362: Return per-node replies for cluster clients.
2426
/// Get server information for specific sections (`INFO section...`).
2527
async fn info_sections<S: ToValkeyArgs + Send + Sync>(
2628
&self,
@@ -99,12 +101,14 @@ pub trait ServerManagementCommands: CommandExecutor {
99101
String::from_owned_valkey_value(self.execute_command(cmd, None).await?)
100102
}
101103

104+
// TODO #7362: Return per-node replies for cluster clients.
102105
/// Asynchronously rewrite the append-only file (`BGREWRITEAOF`). Returns the
103106
/// server's status message.
104107
async fn bgrewriteaof(&self) -> ValkeyResult<String> {
105108
String::from_owned_valkey_value(self.execute_command(cmd("BGREWRITEAOF"), None).await?)
106109
}
107110

111+
// TODO #7362: Return per-node replies for cluster clients.
108112
/// Asynchronously save the dataset to disk (`BGSAVE`). Set `schedule` to defer
109113
/// until no other save is running. Returns the server's status message.
110114
async fn bgsave(&self, schedule: bool) -> ValkeyResult<String> {
@@ -190,12 +194,14 @@ pub trait ServerManagementCommands: CommandExecutor {
190194
i64::from_owned_valkey_value(self.execute_command(cmd, None).await?)
191195
}
192196

197+
// TODO #7362: Return per-node replies for cluster clients.
193198
/// Get a human-readable latency diagnosis report (`LATENCY DOCTOR`).
194199
async fn latency_doctor(&self) -> ValkeyResult<String> {
195200
let cmd = cmd("LATENCY").with_arg("DOCTOR");
196201
String::from_owned_valkey_value(self.execute_command(cmd, None).await?)
197202
}
198203

204+
// TODO #7362: Return per-node replies for cluster clients.
199205
/// Get a latency graph for an event (`LATENCY GRAPH`).
200206
async fn latency_graph<E: ToValkeyArgs + Send>(&self, event: E) -> ValkeyResult<String> {
201207
let cmd = cmd("LATENCY").with_arg("GRAPH").with_arg(event);

‎rust/tests/common/helpers.rs‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,53 @@ pub fn tkey(tag: &str, name: &str) -> String {
3030
format!("{{{tag}}}:{name}:{t}:{n}")
3131
}
3232

33+
/// One field from each default INFO section.
34+
pub const INFO_DEFAULT_FIELDS: [&str; 8] = [
35+
"redis_version", // server
36+
"connected_clients", // clients
37+
"used_memory", // memory
38+
"rdb_changes_since_last_save", // persistence
39+
"total_commands_processed", // stats
40+
"role", // replication
41+
"used_cpu_sys", // cpu
42+
"cluster_enabled", // cluster
43+
];
44+
45+
/// Expected server responses for the `rdb_*_bgsave_type` INFO persistence fields.
46+
///
47+
/// Since Valkey 9.2.0.
48+
pub const BGSAVE_TYPES: [&str; 3] = ["fork", "forkless", "none"];
49+
50+
/// Assert that `value` is an integer and return it.
51+
/// `name` identifies the value in the failure message.
52+
pub fn parse_int(value: Option<&String>, name: &str) -> i64 {
53+
match value.map(|v| v.parse::<i64>()) {
54+
Some(Ok(result)) => result,
55+
_ => panic!("{name} is not an integer: {value:?}"),
56+
}
57+
}
58+
59+
/// Return the fields and values from the given `INFO` sections.
60+
// TODO #7362: Support cluster clients once the typed `INFO` methods return per-node replies.
61+
pub async fn get_info<C>(c: &C, sections: &[&str]) -> std::collections::HashMap<String, String>
62+
where
63+
C: glide::ServerManagementCommands + Sync,
64+
{
65+
let info = if sections.is_empty() {
66+
c.info().await
67+
} else {
68+
c.info_sections(sections).await
69+
};
70+
71+
info.unwrap()
72+
.lines()
73+
.map(str::trim)
74+
.filter(|line| !line.is_empty() && !line.starts_with('#'))
75+
.filter_map(|line| line.split_once(':'))
76+
.map(|(k, v)| (k.to_string(), v.to_string()))
77+
.collect()
78+
}
79+
3380
/// Parse a `"major.minor.patch"` version string into a tuple.
3481
fn parse_version(s: &str) -> Option<(u32, u32, u32)> {
3582
let mut it = s.trim().split('.');

‎rust/tests/common/macros.rs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ macro_rules! retry_transient {
5959
macro_rules! resp_test {
6060
($name:ident, $client:ident, $body:block) => {
6161
mod $name {
62+
// The test body is placed in this module, so it imports everything that the test file imports.
63+
// Some tests don't need any of them, so allow the import to go unused.
64+
#[allow(unused_imports)]
6265
use super::*;
6366
#[allow(unused_imports)]
6467
use $crate::common;
@@ -108,6 +111,11 @@ macro_rules! resp_test {
108111
macro_rules! matrix_test {
109112
($name:ident, $client:ident, $body:block) => {
110113
mod $name {
114+
// The test body is placed in this module, so it imports everything that the test
115+
// file imports (e.g. command traits such as `ServerManagementCommands`). Some test
116+
// bodies don't need any of them (e.g. a body that only calls `common::` helpers),
117+
// so allow the import to go unused rather than fail `clippy -D warnings`.
118+
#[allow(unused_imports)]
111119
use super::*;
112120
#[allow(unused_imports)]
113121
use $crate::common;

‎rust/tests/common/mod.rs‎

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,18 @@ mod pubsub;
2626
mod server;
2727
mod timeout;
2828

29-
pub use cluster::{ClusterHarness, is_transient_cluster_error};
30-
pub use helpers::{key, server_version, tkey, version_below};
31-
pub use pubsub::{wait_for_numpat, wait_for_numsub};
29+
pub use cluster::ClusterHarness;
30+
pub use cluster::is_transient_cluster_error;
31+
pub use helpers::BGSAVE_TYPES;
32+
pub use helpers::INFO_DEFAULT_FIELDS;
33+
pub use helpers::get_info;
34+
pub use helpers::key;
35+
pub use helpers::parse_int;
36+
pub use helpers::server_version;
37+
pub use helpers::tkey;
38+
pub use helpers::version_below;
39+
pub use pubsub::wait_for_numpat;
40+
pub use pubsub::wait_for_numsub;
3241
pub use server::TestServer;
33-
pub use timeout::{test_timeout, with_test_timeout};
42+
pub use timeout::test_timeout;
43+
pub use timeout::with_test_timeout;

‎rust/tests/it_server.rs‎

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,21 +7,41 @@ use glide::FlushAllOptions;
77
use glide::FlushDbOptions;
88
use glide::{AsyncTypedCommands, ServerManagementCommands};
99

10-
resp_test!(info_non_empty, c, {
11-
let info = c.info().await.unwrap();
12-
13-
// INFO always contains the server section header.
14-
assert!(!info.is_empty());
15-
assert!(
16-
info.contains("redis_version")
17-
|| info.contains("valkey_version")
18-
|| info.contains("# Server")
19-
);
20-
});
10+
// TODO #7362: Change to `matrix_test!` once the typed `INFO` methods return per-node replies.
11+
resp_test!(info_returns_expected_fields, c, {
12+
// Default fields
13+
let info = common::get_info(&c, &[]).await;
14+
for field in common::INFO_DEFAULT_FIELDS {
15+
assert!(info.contains_key(field));
16+
}
17+
18+
// Valkey 9.2+ fields
19+
// ------------------
20+
21+
if common::version_below(&c, (9, 2, 0)).await {
22+
return;
23+
}
24+
25+
// Persistence fields
26+
let persistence = common::get_info(&c, &["persistence"]).await;
27+
28+
assert!(common::BGSAVE_TYPES.contains(&persistence["rdb_current_bgsave_type"].as_str()));
29+
assert!(common::BGSAVE_TYPES.contains(&persistence["rdb_last_bgsave_type"].as_str()));
30+
31+
let remaining_name = "forkless_estimated_seconds_remaining";
32+
let remaining_value = common::parse_int(persistence.get(remaining_name), remaining_name);
33+
assert!(remaining_value >= -1);
2134

22-
resp_test!(info_sections, c, {
23-
let info = c.info_sections(&["server"]).await.unwrap();
24-
assert!(!info.is_empty());
35+
// Cluster info fields
36+
// TODO #7362: Uncomment once the typed `INFO` methods return per-node replies.
37+
// let is_cluster = (&c as &dyn std::any::Any).is::<glide::GlideClusterClient>();
38+
// if is_cluster {
39+
// let cluster_info = common::get_info(&c, &["cluster_info"]).await;
40+
//
41+
// let fallbacks_name = "cluster_io_main_thread_fallbacks";
42+
// let fallbacks_value = common::parse_int(cluster_info.get(fallbacks_name), fallbacks_name);
43+
// assert!(fallbacks_value >= 0);
44+
// }
2545
});
2646

2747
resp_test!(dbsize, c, {

0 commit comments

Comments
 (0)