Skip to content

Commit fb17cc6

Browse files
iduartgomezclaude
andcommitted
feat(macros): add peer_connectivity_ratio attribute to freenet_test
Add support for configuring partial connectivity between peer nodes in freenet_test macro. This enables testing of subscription propagation in partially connected networks. Changes: - Add peer_connectivity_ratio (0.0-1.0) attribute to FreenetTestArgs - Generate blocked_addresses based on deterministic connectivity ratio - Pre-compute peer network ports when connectivity ratio is specified - Use (i * j) % 100 >= (ratio * 100) formula for deterministic blocking This feature is needed for test_ping_partially_connected_network which verifies subscription propagation across a network where regular nodes have partial connectivity to each other but full connectivity to gateways. Example usage: #[freenet_test( nodes = ["gw-0", "gw-1", "node-0", "node-1", "node-2"], gateways = ["gw-0", "gw-1"], auto_connect_peers = true, peer_connectivity_ratio = 0.5 // 50% connectivity between peers )] 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 9e6c39d commit fb17cc6

File tree

2 files changed

+75
-1
lines changed

2 files changed

+75
-1
lines changed

crates/freenet-macros/src/codegen.rs

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,35 @@ fn generate_node_setup(args: &FreenetTestArgs) -> TokenStream {
190190
}
191191
}
192192

193+
// Pre-compute peer network ports if we need partial connectivity
194+
let peer_network_ports_setup = if args.peer_connectivity_ratio.is_some() {
195+
let peer_indices: Vec<_> = args
196+
.nodes
197+
.iter()
198+
.enumerate()
199+
.filter(|(idx, node_label)| !is_gateway(args, node_label, *idx))
200+
.map(|(idx, _)| idx)
201+
.collect();
202+
203+
let peer_port_vars: Vec<_> = peer_indices
204+
.iter()
205+
.map(|idx| format_ident!("peer_network_port_{}", idx))
206+
.collect();
207+
208+
quote! {
209+
// Pre-bind sockets for all peers to get their network ports
210+
#(
211+
let peer_socket = std::net::TcpListener::bind("127.0.0.1:0")?;
212+
let #peer_port_vars = peer_socket.local_addr()?.port();
213+
std::mem::drop(peer_socket);
214+
)*
215+
}
216+
} else {
217+
quote! {}
218+
};
219+
220+
setup_code.push(peer_network_ports_setup);
221+
193222
// Third pass: Generate peer configurations (non-gateway nodes)
194223
for (idx, node_label) in args.nodes.iter().enumerate() {
195224
let config_var = format_ident!("config_{}", idx);
@@ -230,6 +259,34 @@ fn generate_node_setup(args: &FreenetTestArgs) -> TokenStream {
230259
}
231260
};
232261

262+
// Compute blocked addresses for this peer if partial connectivity is enabled
263+
let blocked_addresses_code = if let Some(ratio) = args.peer_connectivity_ratio {
264+
let peer_checks: Vec<_> = args
265+
.nodes
266+
.iter()
267+
.enumerate()
268+
.filter(|(other_idx, other_label)| !is_gateway(args, other_label, *other_idx) && *other_idx != idx)
269+
.map(|(other_idx, _)| {
270+
let port_var = format_ident!("peer_network_port_{}", other_idx);
271+
quote! {
272+
if (#idx * #other_idx) % 100 >= (#ratio * 100.0) as usize {
273+
blocked_addresses.push(std::net::SocketAddr::from((std::net::Ipv4Addr::LOCALHOST, #port_var)));
274+
}
275+
}
276+
})
277+
.collect();
278+
279+
quote! {
280+
let mut blocked_addresses = Vec::new();
281+
#(#peer_checks)*
282+
let blocked_addresses = Some(blocked_addresses);
283+
}
284+
} else {
285+
quote! {
286+
let blocked_addresses = None;
287+
}
288+
};
289+
233290
// Peer node configuration
234291
setup_code.push(quote! {
235292
let (#config_var, #temp_var) = {
@@ -249,6 +306,8 @@ fn generate_node_setup(args: &FreenetTestArgs) -> TokenStream {
249306

250307
let location: f64 = rand::Rng::random(&mut rand::rng());
251308

309+
#blocked_addresses_code
310+
252311
let config = freenet::config::ConfigArgs {
253312
ws_api: freenet::config::WebsocketApiArgs {
254313
address: Some(std::net::Ipv4Addr::LOCALHOST.into()),
@@ -267,7 +326,7 @@ fn generate_node_setup(args: &FreenetTestArgs) -> TokenStream {
267326
address: Some(std::net::Ipv4Addr::LOCALHOST.into()),
268327
network_port: Some(network_port),
269328
bandwidth_limit: None,
270-
blocked_addresses: None,
329+
blocked_addresses,
271330
},
272331
config_paths: freenet::config::ConfigPathsArgs {
273332
config_dir: Some(temp_dir.path().to_path_buf()),

crates/freenet-macros/src/parser.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ pub struct FreenetTestArgs {
2121
pub tokio_flavor: TokioFlavor,
2222
/// Tokio worker threads
2323
pub tokio_worker_threads: Option<usize>,
24+
/// Connectivity ratio between peers (0.0-1.0), controlling partial connectivity
25+
pub peer_connectivity_ratio: Option<f64>,
2426
}
2527

2628
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -47,6 +49,7 @@ impl syn::parse::Parse for FreenetTestArgs {
4749
let mut log_level = "freenet=debug,info".to_string();
4850
let mut tokio_flavor = TokioFlavor::CurrentThread;
4951
let mut tokio_worker_threads = None;
52+
let mut peer_connectivity_ratio = None;
5053

5154
// Parse key-value pairs
5255
while !input.is_empty() {
@@ -159,6 +162,17 @@ impl syn::parse::Parse for FreenetTestArgs {
159162
let lit: syn::LitBool = input.parse()?;
160163
auto_connect_peers = lit.value;
161164
}
165+
"peer_connectivity_ratio" => {
166+
let lit: syn::LitFloat = input.parse()?;
167+
let ratio: f64 = lit.base10_parse()?;
168+
if ratio < 0.0 || ratio > 1.0 {
169+
return Err(syn::Error::new(
170+
lit.span(),
171+
"peer_connectivity_ratio must be between 0.0 and 1.0",
172+
));
173+
}
174+
peer_connectivity_ratio = Some(ratio);
175+
}
162176
_ => {
163177
return Err(syn::Error::new(
164178
key.span(),
@@ -201,6 +215,7 @@ impl syn::parse::Parse for FreenetTestArgs {
201215
log_level,
202216
tokio_flavor,
203217
tokio_worker_threads,
218+
peer_connectivity_ratio,
204219
})
205220
}
206221
}

0 commit comments

Comments
 (0)