swactor/crates/distribution/tests/transport_and_codec.rs
Developer 59845f4059 feat: Docker realization, node binary, docs reorg, and simulation testing
Docker realization (bridging simulation to real TCP):
- NodeDriver (`crates/distribution/src/driver.rs`): bridges DistributedNode
  tick loop to TcpTransport with piggyback-extended wire messages
- swactor-node binary (`crates/node/`): CLI node with --listen, --seed,
  --dashboard-port, --actors flags
- Dockerfile: multi-stage build (rust:1.93-slim → debian:bookworm-slim)
- Docker integration tests (`tests/docker/`): 5-node cluster with 4
  scenarios (convergence, failure detection, actor resolution, rejoin)
- LAN cluster scripts for cross-machine validation
- TCP transport retry-on-stale-connection logic
- /api/distribution REST endpoint on dashboard (feature-gated)
- Piggyback fields (piggyback + from_addr) on Ping/Ack/PingReq messages

Docs reorganization:
- docs/runtime/ — actor-model, runtime, worker-thread, channels
- docs/distribution/ — distribution, swim, kademlia, transport
- docs/diagrams/ — all SVG files
- docs/connectome/ — connectome analysis
- docs/development_history/ — DOCKER_REALIZATION.md, SIMULATION_TESTING.md
- render_docs.sh outputs to docs/diagrams/
- README links updated to new paths

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 07:45:44 +00:00

204 lines
6.5 KiB
Rust

use swactor::actor::ActorAddress;
use swactor::transport::WireEnvelope;
use distribution::codec::distribution_codec_registry;
use distribution::messages::*;
use distribution::transport::{TcpAcceptor, TcpTransport};
use distribution::types::NodeId;
// ─── Wire format round-trip ─────────────────────────────────────────────────
#[test]
fn wire_envelope_roundtrips_through_tcp() {
let acceptor = TcpAcceptor::bind("127.0.0.1:0".parse().unwrap()).unwrap();
let addr = acceptor.local_addr();
let original = WireEnvelope {
dest: ActorAddress::new_random(),
type_tag: "test::Msg".to_string(),
payload: vec![1, 2, 3, 4, 5],
};
let original_clone = original.clone();
let sender = std::thread::spawn(move || {
let transport = TcpTransport::new(addr);
transport.send_to(addr, original_clone).unwrap();
});
std::thread::sleep(std::time::Duration::from_millis(50));
let mut streams = Vec::new();
let envelopes = loop {
let envs = acceptor.try_recv(&mut streams);
if !envs.is_empty() {
break envs;
}
std::thread::sleep(std::time::Duration::from_millis(10));
};
sender.join().unwrap();
assert_eq!(envelopes.len(), 1);
let (received, _peer) = &envelopes[0];
assert_eq!(received.dest, original.dest);
assert_eq!(received.type_tag, original.type_tag);
assert_eq!(received.payload, original.payload);
}
#[test]
fn wire_envelope_minimal_roundtrips() {
let acceptor = TcpAcceptor::bind("127.0.0.1:0".parse().unwrap()).unwrap();
let addr = acceptor.local_addr();
let original = WireEnvelope {
dest: ActorAddress::new_random(),
type_tag: "test::Minimal".to_string(),
payload: vec![42],
};
let original_clone = original.clone();
let sender = std::thread::spawn(move || {
let transport = TcpTransport::new(addr);
transport.send_to(addr, original_clone).unwrap();
});
std::thread::sleep(std::time::Duration::from_millis(50));
let mut streams = Vec::new();
let envelopes = loop {
let envs = acceptor.try_recv(&mut streams);
if !envs.is_empty() {
break envs;
}
std::thread::sleep(std::time::Duration::from_millis(10));
};
sender.join().unwrap();
let (received, _) = &envelopes[0];
assert_eq!(received.payload, vec![42]);
}
// ─── Codec registry ─────────────────────────────────────────────────────────
#[test]
fn distribution_codec_encodes_and_decodes_ping() {
let codecs = distribution_codec_registry();
let ping = Ping {
from: NodeId([0xAA; 32]),
from_addr: "127.0.0.1:7000".parse().unwrap(),
sequence: 42,
piggyback: vec![],
};
let type_id = std::any::TypeId::of::<Ping>();
let (tag, bytes) = codecs.encode(type_id, Box::new(ping.clone())).unwrap();
assert_eq!(&tag, "swactor_dist::Ping");
let decoded_any = codecs.decode(&tag, &bytes).unwrap();
let decoded: &Ping = decoded_any.downcast_ref().unwrap();
assert_eq!(decoded.from, ping.from);
assert_eq!(decoded.sequence, ping.sequence);
}
#[test]
fn distribution_codec_encodes_and_decodes_find_value_response() {
let codecs = distribution_codec_registry();
let resp = FindValueResponse::Closer(vec![
(NodeId([0x11; 32]), "127.0.0.1:8080".parse().unwrap()),
(NodeId([0x22; 32]), "127.0.0.1:8081".parse().unwrap()),
]);
let type_id = std::any::TypeId::of::<FindValueResponse>();
let (tag, bytes) = codecs.encode(type_id, Box::new(resp.clone())).unwrap();
let decoded_any = codecs.decode(&tag, &bytes).unwrap();
let decoded: &FindValueResponse = decoded_any.downcast_ref().unwrap();
match decoded {
FindValueResponse::Closer(nodes) => {
assert_eq!(nodes.len(), 2);
assert_eq!(nodes[0].0, NodeId([0x11; 32]));
}
_ => panic!("expected Closer variant"),
}
}
#[test]
fn all_message_types_registered_in_codec_registry() {
let codecs = distribution_codec_registry();
let tags = [
"swactor_dist::Ping",
"swactor_dist::Ack",
"swactor_dist::PingReq",
"swactor_dist::JoinRequest",
"swactor_dist::JoinResponse",
"swactor_dist::FindNodeRequest",
"swactor_dist::FindNodeResponse",
"swactor_dist::StoreRequest",
"swactor_dist::FindValueRequest",
"swactor_dist::FindValueResponse",
];
for tag in tags {
let result = codecs.decode(tag, &[]);
let err = result.unwrap_err();
let err_str = format!("{}", err);
assert!(
!err_str.contains("unknown type_tag"),
"Decoder not registered for tag '{tag}': {err_str}"
);
}
}
// ─── End-to-end: codec + TCP transport ──────────────────────────────────────
#[test]
fn ping_message_survives_codec_and_tcp_roundtrip() {
let codecs = distribution_codec_registry();
let acceptor = TcpAcceptor::bind("127.0.0.1:0".parse().unwrap()).unwrap();
let server_addr = acceptor.local_addr();
let dest = ActorAddress::new_random();
let ping = Ping {
from: NodeId([0xBB; 32]),
from_addr: "127.0.0.1:7001".parse().unwrap(),
sequence: 99,
piggyback: vec![],
};
let type_id = std::any::TypeId::of::<Ping>();
let (tag, payload) = codecs.encode(type_id, Box::new(ping.clone())).unwrap();
let envelope = WireEnvelope {
dest,
type_tag: tag,
payload,
};
let envelope_clone = envelope.clone();
let sender = std::thread::spawn(move || {
let transport = TcpTransport::new(server_addr);
transport.send_to(server_addr, envelope_clone).unwrap();
});
std::thread::sleep(std::time::Duration::from_millis(50));
let mut streams = Vec::new();
let envelopes = loop {
let envs = acceptor.try_recv(&mut streams);
if !envs.is_empty() {
break envs;
}
std::thread::sleep(std::time::Duration::from_millis(10));
};
sender.join().unwrap();
let (received, _) = &envelopes[0];
let (addr, msg_any) = codecs.receive(received.clone()).unwrap();
assert_eq!(addr, dest);
let decoded: &Ping = msg_any.downcast_ref().unwrap();
assert_eq!(decoded.from, NodeId([0xBB; 32]));
assert_eq!(decoded.sequence, 99);
}