Skip to content

Commit e192aed

Browse files
authored
Merge pull request #10 from Nagitch/feature/add-roundtrip-tests
Feature/add roundtrip tests
2 parents 9d5f433 + 70468ca commit e192aed

File tree

9 files changed

+238
-12
lines changed

9 files changed

+238
-12
lines changed

Cargo.lock

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ members = [
33
"osc-ir",
44
"osc-codec-json",
55
"osc-codec-msgpack",
6-
# "osc-adapter-osc-types", # Temporarily disabled due to dependency issues
6+
"osc-adapter-osc-types",
77
"osc-devtools",
88
]
99
resolver = "2"

osc-adapter-osc-types/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ osc-ir = { path = "../osc-ir", features = ["alloc"] }
1515
osc-types10 = { version = "0.1.0-alpha.2", optional = true, default-features = false }
1616
osc-types11 = { version = "0.1.0-alpha.2", optional = true, default-features = false }
1717

18-
[workspace]
18+
[dev-dependencies]
19+
osc-codec-msgpack = { path = "../osc-codec-msgpack" }

osc-adapter-osc-types/src/lib.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ pub mod v10 {
6868
}
6969
}
7070

71-
fn ir_to_arg<'a>(value: &'a IrValue) -> Option<osc::OscType<'a>> {
71+
fn ir_to_arg(value: &IrValue) -> Option<osc::OscType<'_>> {
7272
match value {
7373
IrValue::Integer(i) => i32::try_from(*i).ok().map(osc::OscType::Int),
7474
IrValue::Float(f) => Some(osc::OscType::Float(*f as f32)),
@@ -83,7 +83,7 @@ pub mod v10 {
8383
message_to_ir_map(message.address, args)
8484
}
8585

86-
pub fn ir_to_message<'a>(value: &'a IrValue) -> Option<osc::Message<'a>> {
86+
pub fn ir_to_message(value: &IrValue) -> Option<osc::Message<'_>> {
8787
let (address, args) = try_extract_message(value)?;
8888
let mut osc_args = Vec::with_capacity(args.len());
8989
for arg in args {
@@ -110,7 +110,7 @@ pub mod v11 {
110110
}
111111
}
112112

113-
fn ir_to_arg<'a>(value: &'a IrValue) -> Option<osc::OscType<'a>> {
113+
fn ir_to_arg(value: &IrValue) -> Option<osc::OscType<'_>> {
114114
match value {
115115
IrValue::Integer(i) => i32::try_from(*i).ok().map(osc::OscType::Int),
116116
IrValue::Float(f) => Some(osc::OscType::Float(*f as f32)),
@@ -126,7 +126,7 @@ pub mod v11 {
126126
message_to_ir_map(message.address, args)
127127
}
128128

129-
pub fn ir_to_message<'a>(value: &'a IrValue) -> Option<osc::Message<'a>> {
129+
pub fn ir_to_message(value: &IrValue) -> Option<osc::Message<'_>> {
130130
let (address, args) = try_extract_message(value)?;
131131
let mut osc_args = Vec::with_capacity(args.len());
132132
for arg in args {
@@ -216,7 +216,7 @@ mod tests {
216216
matches!(message.args[1], osc::OscType::Float(f) if (f + 1.25).abs() < f32::EPSILON)
217217
);
218218
assert!(matches!(message.args[2], osc::OscType::String("value")));
219-
assert!(matches!(message.args[3], osc::OscType::Blob(slice) if slice == &[9, 8, 7]));
219+
assert!(matches!(message.args[3], osc::OscType::Blob(slice) if slice == [9, 8, 7]));
220220
}
221221

222222
#[test]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#![cfg(feature = "osc10")]
2+
3+
use osc_adapter_osc_types as adapter; // crate name with hyphen becomes underscore
4+
use osc_codec_msgpack as msgpack;
5+
use osc_types10 as osc;
6+
7+
#[test]
8+
fn message_ir_msgpack_roundtrip() {
9+
// Build a basic OSC 1.0 message
10+
let message = osc::Message {
11+
address: "/roundtrip",
12+
args: vec![
13+
osc::OscType::Int(7),
14+
osc::OscType::Float(-1.25),
15+
osc::OscType::String("value"),
16+
osc::OscType::Blob(&[9, 8, 7]),
17+
],
18+
};
19+
20+
// OSC -> IrValue
21+
let ir = adapter::v10::message_to_ir(&message);
22+
23+
// IrValue -> MsgPack -> IrValue
24+
let bytes = msgpack::to_msgpack(&ir);
25+
let ir2 = msgpack::from_msgpack(&bytes);
26+
27+
// IrValue -> OSC
28+
let message2 = adapter::v10::ir_to_message(&ir2).expect("expected successful conversion");
29+
30+
// Validate
31+
assert_eq!(message2.address, "/roundtrip");
32+
assert!(matches!(message2.args[0], osc::OscType::Int(7)));
33+
if let osc::OscType::Float(v) = message2.args[1] {
34+
assert!((v + 1.25).abs() < f32::EPSILON);
35+
} else {
36+
panic!("expected Float arg");
37+
}
38+
assert!(matches!(message2.args[2], osc::OscType::String("value")));
39+
assert!(matches!(message2.args[3], osc::OscType::Blob(slice) if slice == [9, 8, 7]));
40+
}

osc-codec-json/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ mod tests {
144144
bundle.add_message(IrValue::from(42));
145145

146146
let mut nested_bundle = IrBundle::immediate();
147-
nested_bundle.add_message(IrValue::from(true));
148-
nested_bundle.add_message(IrValue::from(3.14));
147+
nested_bundle.add_message(IrValue::from(true));
148+
nested_bundle.add_message(IrValue::from(core::f64::consts::PI));
149149

150150
bundle.add_bundle(nested_bundle);
151151

osc-codec-msgpack/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,7 @@ readme = "README.md"
1515
[dependencies]
1616
osc-ir = { version = "0.1.0-alpha.1", path = "../osc-ir", features = ["alloc", "serde"] }
1717
serde = { version = "1", features = ["derive"] }
18-
rmp-serde = "1"
18+
rmp-serde = "1"
19+
20+
[dev-dependencies]
21+
rmpv = "1"

osc-codec-msgpack/src/lib.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,150 @@ mod tests {
9393
let decoded = from_msgpack(&bytes);
9494
assert_eq!(value, decoded);
9595
}
96+
97+
#[test]
98+
fn roundtrip_osc_message_like() {
99+
// Construct an IrValue that matches the adapter's OSC message representation
100+
let value = IrValue::Map(vec![
101+
("$type".into(), IrValue::from("osc.message")),
102+
("address".into(), IrValue::from("/test")),
103+
(
104+
"args".into(),
105+
IrValue::Array(vec![
106+
IrValue::Integer(7),
107+
IrValue::Float(1.5),
108+
IrValue::from("text"),
109+
IrValue::Binary(vec![1_u8, 2, 3]),
110+
]),
111+
),
112+
]);
113+
114+
let bytes = to_msgpack(&value);
115+
let decoded = from_msgpack(&bytes);
116+
117+
// Structure-preserving roundtrip
118+
assert_eq!(decoded, value);
119+
120+
// Extract and validate fields similar to adapter::try_extract_message
121+
let map = decoded.as_map().expect("expected map");
122+
let address = map.iter().find(|(k, _)| k == "address").unwrap().1.as_str();
123+
assert_eq!(address, Some("/test"));
124+
125+
let args = map
126+
.iter()
127+
.find(|(k, _)| k == "args")
128+
.unwrap()
129+
.1
130+
.as_array()
131+
.expect("expected args array");
132+
133+
assert_eq!(args.len(), 4);
134+
assert_eq!(args[0].as_integer(), Some(7));
135+
assert!((args[1].as_float().unwrap() - 1.5).abs() < f64::EPSILON);
136+
assert_eq!(args[2].as_str(), Some("text"));
137+
assert_eq!(args[3].as_binary(), Some(&[1_u8, 2, 3][..]));
138+
}
139+
140+
#[test]
141+
fn msgpack_bytes_are_valid_and_match_contents() {
142+
use std::io::Cursor;
143+
use rmpv::{decode::read_value, Value};
144+
145+
// Prepare an OSC-like message map as IrValue
146+
let value = IrValue::Map(vec![
147+
("$type".into(), IrValue::from("osc.message")),
148+
("address".into(), IrValue::from("/validate")),
149+
(
150+
"args".into(),
151+
IrValue::Array(vec![
152+
IrValue::Integer(123),
153+
IrValue::Float(-2.5),
154+
IrValue::from("ok"),
155+
IrValue::Binary(vec![0xAA, 0xBB]),
156+
]),
157+
),
158+
]);
159+
160+
// Encode to MessagePack
161+
let bytes = to_msgpack(&value);
162+
163+
// Ensure bytes are valid MessagePack by decoding with rmpv
164+
let mut cursor = Cursor::new(&bytes);
165+
let root = read_value(&mut cursor).expect("must decode as msgpack Value");
166+
167+
// Helper: unwrap serde's externally tagged enum representation
168+
fn unwrap_enum(v: &Value) -> (&str, &Value) {
169+
match v {
170+
// Map form: { "Variant": payload }
171+
Value::Map(kv) if kv.len() == 1 => {
172+
let (k, v) = &kv[0];
173+
let name = match k { Value::String(s) => s.as_str().expect("variant name"), _ => panic!("invalid enum key") };
174+
(name, v)
175+
}
176+
// Array form: ["Variant", payload]
177+
Value::Array(items) if items.len() == 2 => {
178+
let name = match &items[0] { Value::String(s) => s.as_str().expect("variant name"), _ => panic!("invalid enum tag array") };
179+
(name, &items[1])
180+
}
181+
other => panic!("unexpected enum encoding: {:?}", other),
182+
}
183+
}
184+
185+
// Root must be the IrValue::Map(enum) variant
186+
let (root_variant, root_payload) = unwrap_enum(&root);
187+
assert_eq!(root_variant, "Map");
188+
189+
// Payload is Vec<(String, IrValue)> serialized as array of 2-element arrays
190+
let entries = match root_payload { Value::Array(a) => a, other => panic!("expected entries array, got {:?}", other) };
191+
192+
// Collect into hashmap-like view: key -> encoded IrValue
193+
let get = |key: &str| -> &Value {
194+
entries
195+
.iter()
196+
.find_map(|entry| match entry {
197+
Value::Array(items) if items.len() == 2 => match (&items[0], &items[1]) {
198+
(Value::String(s), v) if s.as_str() == Some(key) => Some(v),
199+
_ => None,
200+
},
201+
_ => None,
202+
})
203+
.expect("entry not found")
204+
};
205+
206+
// $type: IrValue::String("osc.message") -> enum String with payload string
207+
let (ty_variant, ty_payload) = unwrap_enum(get("$type"));
208+
assert_eq!(ty_variant, "String");
209+
assert!(matches!(ty_payload, Value::String(s) if s.as_str() == Some("osc.message")));
210+
211+
// address: IrValue::String("/validate")
212+
let (addr_variant, addr_payload) = unwrap_enum(get("address"));
213+
assert_eq!(addr_variant, "String");
214+
assert!(matches!(addr_payload, Value::String(s) if s.as_str() == Some("/validate")));
215+
216+
// args: IrValue::Array([...]) -> enum Array with payload array of encoded IrValue
217+
let (args_variant, args_payload) = unwrap_enum(get("args"));
218+
assert_eq!(args_variant, "Array");
219+
let args = match args_payload { Value::Array(a) => a, other => panic!("expected args payload array, got {:?}", other) };
220+
assert_eq!(args.len(), 4);
221+
222+
// 0: Integer(123)
223+
let (v0_variant, v0_payload) = unwrap_enum(&args[0]);
224+
assert_eq!(v0_variant, "Integer");
225+
assert!(matches!(v0_payload, Value::Integer(i) if i.as_i64() == Some(123)));
226+
227+
// 1: Float(-2.5)
228+
let (v1_variant, v1_payload) = unwrap_enum(&args[1]);
229+
assert_eq!(v1_variant, "Float");
230+
assert!(matches!(v1_payload, Value::F64(x) if (*x + 2.5).abs() < f64::EPSILON));
231+
232+
// 2: String("ok")
233+
let (v2_variant, v2_payload) = unwrap_enum(&args[2]);
234+
assert_eq!(v2_variant, "String");
235+
assert!(matches!(v2_payload, Value::String(s) if s.as_str() == Some("ok")));
236+
237+
// 3: Binary([0xAA, 0xBB])
238+
let (v3_variant, v3_payload) = unwrap_enum(&args[3]);
239+
assert_eq!(v3_variant, "Binary");
240+
assert!(matches!(v3_payload, Value::Binary(b) if b.as_slice() == [0xAA, 0xBB]));
241+
}
96242
}

osc-ir/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,8 +581,8 @@ mod tests {
581581
nested2.add_message(IrValue::from("nested2 message"));
582582

583583
let mut deeply_nested = IrBundle::new(IrTimetag::from_ntp(4000));
584-
deeply_nested.add_message(IrValue::from("deeply nested message"));
585-
deeply_nested.add_message(IrValue::from(3.14));
584+
deeply_nested.add_message(IrValue::from("deeply nested message"));
585+
deeply_nested.add_message(IrValue::from(core::f64::consts::PI));
586586

587587
nested2.add_bundle(deeply_nested);
588588

0 commit comments

Comments
 (0)