Skip to content

Commit 142754f

Browse files
committed
fix: matching both devnet4 and devnet5 slot clock fixtures
1 parent 06c05d0 commit 142754f

2 files changed

Lines changed: 85 additions & 75 deletions

File tree

testing/lean-spec-tests/src/slot_clock.rs

Lines changed: 73 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ use std::path::Path;
33
use anyhow::{anyhow, bail, ensure};
44
use tracing::info;
55

6-
use crate::types::{
7-
TestFixture,
8-
slot_clock::{CurrentTimeInput, FromSlotInput, FromUnixTimeInput, SlotClockTest},
9-
};
6+
use crate::types::{TestFixture, slot_clock::SlotClockTest};
107

118
/// Load a slot_clock test fixture from a JSON file
129
pub fn load_slot_clock_test(path: impl AsRef<Path>) -> anyhow::Result<TestFixture<SlotClockTest>> {
@@ -17,14 +14,33 @@ pub fn load_slot_clock_test(path: impl AsRef<Path>) -> anyhow::Result<TestFixtur
1714
.map_err(|err| anyhow!("Failed to parse test file {}: {err}", path.display()))
1815
}
1916

17+
/// Parse a JSON value as u64, accepting both integer and float representations.
18+
fn json_u64(v: &serde_json::Value, field: &str) -> anyhow::Result<u64> {
19+
v.as_u64()
20+
.or_else(|| v.as_f64().map(|f| f as u64))
21+
.ok_or_else(|| anyhow!("Missing or non-numeric field: {field}"))
22+
}
23+
2024
/// Run a single slot_clock test case
2125
pub fn run_slot_clock_test(test_name: &str, test: &SlotClockTest) -> anyhow::Result<()> {
22-
info!(
23-
"Running slot_clock test: {test_name} (operation={})",
24-
test.operation
25-
);
26+
// Config lives at top-level in devnet5, inside output in devnet4.
27+
let cfg = test
28+
.config
29+
.as_ref()
30+
.or(test.output.config.as_ref())
31+
.ok_or_else(|| anyhow!("Missing config"))?;
32+
33+
// Operation kind: plain string in devnet4, object with `kind` field in devnet5.
34+
let kind: &str = if let Some(s) = test.operation.as_str() {
35+
s
36+
} else {
37+
test.operation["kind"]
38+
.as_str()
39+
.ok_or_else(|| anyhow!("Missing operation.kind"))?
40+
};
41+
42+
info!("Running slot_clock test: {test_name} (operation={kind})");
2643

27-
let cfg = &test.output.config;
2844
ensure!(
2945
cfg.seconds_per_slot * 1000 == cfg.intervals_per_slot * cfg.milliseconds_per_interval,
3046
"Inconsistent config: secondsPerSlot * 1000 != intervalsPerSlot * msPerInterval",
@@ -33,35 +49,46 @@ pub fn run_slot_clock_test(test_name: &str, test: &SlotClockTest) -> anyhow::Res
3349
let ms_per_slot = cfg.seconds_per_slot * 1000;
3450
let ms_per_interval = cfg.milliseconds_per_interval;
3551

36-
match test.operation.as_str() {
52+
// In devnet4 params are in `input`; in devnet5 they are in the `operation` object.
53+
let params: &serde_json::Value = if let Some(inp) = test.input.as_ref() {
54+
inp
55+
} else {
56+
&test.operation
57+
};
58+
59+
// Look up a u64 param, falling back to an alternate key name if the first is missing.
60+
let param_u64 = |key: &str, alt: Option<&str>| -> anyhow::Result<u64> {
61+
let v = params
62+
.get(key)
63+
.or_else(|| alt.and_then(|k| params.get(k)))
64+
.ok_or_else(|| anyhow!("Missing param: {key}"))?;
65+
json_u64(v, key)
66+
};
67+
68+
match kind {
3769
"current_slot" => {
38-
let input: CurrentTimeInput = serde_json::from_value(test.input.clone())?;
39-
let expected = test
40-
.output
41-
.slot
42-
.ok_or_else(|| anyhow!("Missing output.slot"))?;
43-
let genesis_ms = input.genesis_time * 1000;
44-
let actual = if input.current_time_ms <= genesis_ms {
70+
let genesis_time = param_u64("genesisTime", None)?;
71+
// devnet4: currentTimeMs (integer), devnet5: currentTimeMilliseconds (float)
72+
let current_time_ms = param_u64("currentTimeMs", Some("currentTimeMilliseconds"))?;
73+
let expected = test.output.slot.ok_or_else(|| anyhow!("Missing output.slot"))?;
74+
let genesis_ms = genesis_time * 1000;
75+
let actual = if current_time_ms <= genesis_ms {
4576
0
4677
} else {
47-
(input.current_time_ms - genesis_ms) / ms_per_slot
78+
(current_time_ms - genesis_ms) / ms_per_slot
4879
};
49-
ensure!(
50-
actual == expected,
51-
"current_slot mismatch: expected {expected}, got {actual}"
52-
);
80+
ensure!(actual == expected, "current_slot mismatch: expected {expected}, got {actual}");
5381
}
5482
"current_interval" => {
55-
let input: CurrentTimeInput = serde_json::from_value(test.input.clone())?;
56-
let expected = test
57-
.output
58-
.interval
59-
.ok_or_else(|| anyhow!("Missing output.interval"))?;
60-
let genesis_ms = input.genesis_time * 1000;
61-
let actual = if input.current_time_ms <= genesis_ms {
83+
let genesis_time = param_u64("genesisTime", None)?;
84+
let current_time_ms = param_u64("currentTimeMs", Some("currentTimeMilliseconds"))?;
85+
let expected =
86+
test.output.interval.ok_or_else(|| anyhow!("Missing output.interval"))?;
87+
let genesis_ms = genesis_time * 1000;
88+
let actual = if current_time_ms <= genesis_ms {
6289
0
6390
} else {
64-
let elapsed = input.current_time_ms - genesis_ms;
91+
let elapsed = current_time_ms - genesis_ms;
6592
(elapsed % ms_per_slot) / ms_per_interval
6693
};
6794
ensure!(
@@ -70,44 +97,39 @@ pub fn run_slot_clock_test(test_name: &str, test: &SlotClockTest) -> anyhow::Res
7097
);
7198
}
7299
"total_intervals" => {
73-
let input: CurrentTimeInput = serde_json::from_value(test.input.clone())?;
100+
let genesis_time = param_u64("genesisTime", None)?;
101+
let current_time_ms = param_u64("currentTimeMs", Some("currentTimeMilliseconds"))?;
74102
let expected = test
75103
.output
76104
.total_intervals
77105
.ok_or_else(|| anyhow!("Missing output.totalIntervals"))?;
78-
let genesis_ms = input.genesis_time * 1000;
79-
let actual = if input.current_time_ms <= genesis_ms {
106+
let genesis_ms = genesis_time * 1000;
107+
let actual = if current_time_ms <= genesis_ms {
80108
0
81109
} else {
82-
(input.current_time_ms - genesis_ms) / ms_per_interval
110+
(current_time_ms - genesis_ms) / ms_per_interval
83111
};
84112
ensure!(
85113
actual == expected,
86114
"total_intervals mismatch: expected {expected}, got {actual}"
87115
);
88116
}
89117
"from_slot" => {
90-
let input: FromSlotInput = serde_json::from_value(test.input.clone())?;
91-
let expected = test
92-
.output
93-
.interval
94-
.ok_or_else(|| anyhow!("Missing output.interval"))?;
95-
let actual = input.slot * cfg.intervals_per_slot;
96-
ensure!(
97-
actual == expected,
98-
"from_slot mismatch: expected {expected}, got {actual}"
99-
);
118+
let slot = param_u64("slot", None)?;
119+
let expected =
120+
test.output.interval.ok_or_else(|| anyhow!("Missing output.interval"))?;
121+
let actual = slot * cfg.intervals_per_slot;
122+
ensure!(actual == expected, "from_slot mismatch: expected {expected}, got {actual}");
100123
}
101124
"from_unix_time" => {
102-
let input: FromUnixTimeInput = serde_json::from_value(test.input.clone())?;
103-
let expected = test
104-
.output
105-
.interval
106-
.ok_or_else(|| anyhow!("Missing output.interval"))?;
107-
let actual = if input.unix_seconds <= input.genesis_time {
125+
let genesis_time = param_u64("genesisTime", None)?;
126+
let unix_seconds = param_u64("unixSeconds", None)?;
127+
let expected =
128+
test.output.interval.ok_or_else(|| anyhow!("Missing output.interval"))?;
129+
let actual = if unix_seconds <= genesis_time {
108130
0
109131
} else {
110-
((input.unix_seconds - input.genesis_time) * 1000) / ms_per_interval
132+
((unix_seconds - genesis_time) * 1000) / ms_per_interval
111133
};
112134
ensure!(
113135
actual == expected,

testing/lean-spec-tests/src/types/slot_clock.rs

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,23 @@ use serde::Deserialize;
44
#[serde(rename_all = "camelCase")]
55
pub struct SlotClockTest {
66
pub network: String,
7-
pub operation: String,
8-
pub input: serde_json::Value,
7+
/// devnet4: plain string; devnet5: object with `kind` and input params
8+
pub operation: serde_json::Value,
9+
/// devnet4 only — params moved into `operation` in devnet5
10+
#[serde(default)]
11+
pub input: Option<serde_json::Value>,
912
pub output: SlotClockOutput,
13+
/// devnet5: top-level; devnet4: inside `output`
14+
#[serde(default)]
15+
pub config: Option<SlotClockConfig>,
1016
}
1117

1218
#[derive(Debug, Deserialize)]
1319
#[serde(rename_all = "camelCase")]
1420
pub struct SlotClockOutput {
15-
pub config: SlotClockConfig,
21+
/// devnet4 only — moved to top-level in devnet5
22+
#[serde(default)]
23+
pub config: Option<SlotClockConfig>,
1624
#[serde(default)]
1725
pub slot: Option<u64>,
1826
#[serde(default)]
@@ -21,30 +29,10 @@ pub struct SlotClockOutput {
2129
pub total_intervals: Option<u64>,
2230
}
2331

24-
#[derive(Debug, Deserialize)]
32+
#[derive(Debug, Clone, Deserialize)]
2533
#[serde(rename_all = "camelCase")]
2634
pub struct SlotClockConfig {
2735
pub seconds_per_slot: u64,
2836
pub intervals_per_slot: u64,
2937
pub milliseconds_per_interval: u64,
3038
}
31-
32-
#[derive(Debug, Deserialize)]
33-
#[serde(rename_all = "camelCase")]
34-
pub struct CurrentTimeInput {
35-
pub genesis_time: u64,
36-
pub current_time_ms: u64,
37-
}
38-
39-
#[derive(Debug, Deserialize)]
40-
#[serde(rename_all = "camelCase")]
41-
pub struct FromSlotInput {
42-
pub slot: u64,
43-
}
44-
45-
#[derive(Debug, Deserialize)]
46-
#[serde(rename_all = "camelCase")]
47-
pub struct FromUnixTimeInput {
48-
pub unix_seconds: u64,
49-
pub genesis_time: u64,
50-
}

0 commit comments

Comments
 (0)