Skip to content

Commit 298de7d

Browse files
qdotclaude
andcommitted
feat(tests): enable v0 protocol test suite with SingleMotorVibrateCmd
Adds SingleMotorVibrateCmd support to the V0 client device and test runner. For single-vibrator devices, SingleMotorVibrateCmd produces identical hardware output to VibrateCmd, so existing YAML assertions work without changes. Enables 48 embedded + 48 JSON v0 tests (96 total). Excludes 14 devices: 7 with multi-vibrator hardware (SingleMotorVibrateCmd addresses all motors, changing expected bytes), and 7 with top-level Rotate/Linear commands unsupported in V0. Fixes a pre-existing bug in the JSON schema: DeviceMessagesV0 enum was missing StopDeviceCmd, causing V0 DeviceAdded messages to fail schema validation in the JSON transport path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0f6f10f commit 298de7d

4 files changed

Lines changed: 135 additions & 10 deletions

File tree

crates/buttplug_core/schema/buttplug-schema.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@
341341
"enum": [
342342
"FleshlightLaunchFW12Cmd",
343343
"SingleMotorVibrateCmd",
344+
"StopDeviceCmd",
344345
"KiirooCmd",
345346
"LovenseCmd",
346347
"VorzeA10CycloneCmd"

crates/buttplug_tests/tests/test_device_protocols.rs

Lines changed: 119 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,31 +1010,141 @@ async fn test_device_protocols_json_v1(test_file: &str) {
10101010
util::device_test::client::client_v1::run_json_test_case(&load_test_case(test_file).await).await;
10111011
}
10121012

1013-
// V0 tests are blocked on wrapping all Vibrate/Rotate/Linear command pairs in
1014-
// VersionGated(min: 1). V0 only supports SingleMotorVibrateCmd and Stop.
1015-
// Once YAML files are updated, uncomment this block.
1016-
/*
1013+
// V0 supports SingleMotorVibrateCmd (single speed, all motors) and StopDeviceCmd.
1014+
// For single-vibrator devices, SingleMotorVibrateCmd produces identical hardware
1015+
// output to VibrateCmd, so existing YAML test assertions work without changes.
1016+
// Excluded: multi-vibrator (bananasome), top-level Rotate/Linear devices.
1017+
//#[test_case("test_cowgirl_cone_protocol.yaml" ; "The Cowgirl Cone Protocol")]
10171018
#[test_case("test_activejoy_protocol.yaml" ; "ActiveJoy Protocol")]
1018-
#[test_case("test_aneros_protocol.yaml" ; "Aneros Protocol")]
1019-
#[test_case("test_bananasome_protocol.yaml" ; "Bananasome Protocol")]
1019+
#[test_case("test_adrienlastic_protocol.yaml" ; "Adrien Lastic Protocol")]
1020+
#[test_case("test_amorelie_joy_protocol.yaml" ; "Amorelie Joy Protocol")]
1021+
// aneros excluded: multi-vibrator device (SingleMotorVibrateCmd addresses all motors)
1022+
#[test_case("test_ankni_protocol_no_handshake.yaml" ; "Ankni Protocol - No Handshake")]
1023+
#[test_case("test_ankni_protocol.yaml" ; "Ankni Protocol")]
1024+
// bananasome excluded: multi-vibrator (top-level Vibrate Index: 1)
1025+
// cachito excluded: multi-vibrator device
1026+
#[test_case("test_cowgirl_protocol.yaml" ; "The Cowgirl Protocol")]
1027+
#[test_case("test_cupido_protocol.yaml" ; "Cupido Protocol")]
1028+
#[test_case("test_deepsire.yaml" ; "DeepSire Protocol")]
1029+
#[test_case("test_feelingso.yaml" ; "FeelingSo Protocol")]
1030+
// fleshy_thrust excluded: top-level Linear
1031+
#[test_case("test_fluffer_protocol.yaml" ; "Fluffer Protocol")]
1032+
#[test_case("test_foreo_protocol.yaml" ; "Foreo Protocol")]
1033+
#[test_case("test_fox_protocol.yaml" ; "Fox Protocol")]
1034+
//#[test_case("test_fredorch_protocol.yaml" ; "Fredorch Protocol")]
1035+
#[test_case("test_galaku_nebula.yaml" ; "Galaku Pump Protocol - Nebula")]
1036+
#[test_case("test_galaku.yaml" ; "Galaku Protocol")]
1037+
#[test_case("test_hgod_protocol.yaml" ; "Hgod Protocol")]
10201038
#[test_case("test_hismith_auxfun_box.yaml" ; "Hismith Mini Protocol - Auxfun Box")]
1039+
#[test_case("test_hismith_sinloli.yaml" ; "Hismith Mini Protocol - Sinloli")]
1040+
#[test_case("test_hismith_thrusting_cup.yaml" ; "Hismith Protocol - Thrusting Cup")]
10211041
#[test_case("test_hismith_v4.yaml" ; "Hismith Mini Protocol - Hismith v4")]
1042+
#[test_case("test_hismith_wildolo.yaml" ; "Hismith Protocol - Wildolo")]
1043+
#[test_case("test_itoys_protocol.yaml" ; "iToys Protocol")]
1044+
#[test_case("test_lelo_idawave.yaml" ; "Lelo Harmony Protocol - Ida Wave")]
1045+
//#[test_case("test_lelo_tianiharmony.yaml" ; "Lelo Harmony Protocol - Tiani Harmony")]
1046+
#[test_case("test_leten_protocol.yaml" ; "Leten Protocol")]
1047+
// loob excluded: top-level Linear
1048+
#[test_case("test_lovense_battery_non_default.yaml" ; "Lovense Protocol - Lovense Battery (Non-Default Devices)")]
1049+
#[test_case("test_lovense_battery.yaml" ; "Lovense Protocol - Lovense Battery (Default Devices)")]
1050+
#[test_case("test_lovense_flexer_fw2.yaml" ; "Lovense Protocol - Flexer FW2")]
1051+
#[test_case("test_lovense_max.yaml" ; "Lovense Protocol - Lovense Max (Vibrate/Constrict)")]
1052+
#[test_case("test_lovense_single_vibrator.yaml" ; "Lovense Protocol - Single Vibrator Device")]
1053+
#[test_case("test_luvmazer_protocol.yaml" ; "Luvmazer Protocol")]
1054+
#[test_case("test_magic_motion_1_magic_cell.yaml" ; "MagicMotion Protocol 1 - Magic Cell")]
1055+
#[test_case("test_magic_motion_2_equinox.yaml" ; "MagicMotion Protocol 2 - Equinox")]
1056+
#[test_case("test_magic_motion_3_krush.yaml" ; "MagicMotion Protocol 3 - Krush")]
1057+
//#[test_case("test_meese_protocol.yaml" ; "Meese Protocol")]
1058+
#[test_case("test_mizzzee_protocol.yaml" ; "Mizz Zee Protocol")]
1059+
#[test_case("test_mizzzee_v2_protocol.yaml" ; "Mizz Zee v2 Protocol")]
1060+
// motorbunny excluded: top-level Rotate
1061+
// nexus_revo excluded: top-level Rotate
1062+
#[test_case("test_nobra_protocol.yaml" ; "Nobra Protocol")]
1063+
#[test_case("test_omobo_protocol.yaml" ; "Omobo Protocol")]
1064+
#[test_case("test_pink_punch_protocol.yaml" ; "Pink Punch Protocol")]
1065+
#[test_case("test_sakuraneko_koikoi.yaml" ; "Sakuraneko Protocol - Koikoi")]
1066+
#[test_case("test_sakuraneko_protocol.yaml" ; "Sakuraneko Protocol")]
1067+
#[test_case("test_sensee_protocol.yaml" ; "Sensee Diandou Protocol - Rabbit")]
1068+
// serveu excluded: top-level Linear
1069+
#[test_case("test_svakom_alex_v2.yaml" ; "Svakom Alex Neo 2")]
1070+
#[test_case("test_svakom_alex.yaml" ; "Svakom Alex Neo")]
1071+
// tcode excluded: top-level Linear
1072+
#[test_case("test_wetoy_protocol.yaml" ; "WeToy Protocol")]
1073+
//#[test_case("test_wevibe_4plus.yaml" ; "WeVibe Protocol (Legacy) - 4 Plus")]
1074+
//#[test_case("test_wevibe_chorus.yaml" ; "WeVibe Protocol (Chorus) - Chorus")]
1075+
#[test_case("test_wevibe_moxie.yaml" ; "WeVibe Protocol (8bit) - Moxie")]
1076+
#[test_case("test_wevibe_pivot.yaml" ; "WeVibe Protocol (Legacy) - Pivot")]
1077+
//#[test_case("test_wevibe_vector.yaml" ; "WeVibe Protocol (8bit) - Vector")]
10221078
#[test_case("test_xibao_protocol.yaml" ; "Xibao Protocol")]
1079+
#[test_case("test_xiuxiuda_protocol.yaml" ; "Xiuxiuda Protocol")]
1080+
#[test_case("test_xuanhuan_protocol.yaml" ; "Xuanhuan Protocol")]
10231081
#[tokio::test]
10241082
async fn test_device_protocols_embedded_v0(test_file: &str) {
10251083
//tracing_subscriber::fmt::init();
10261084
util::device_test::client::client_v0::run_embedded_test_case(&load_test_case(test_file).await)
10271085
.await;
10281086
}
10291087

1088+
//#[test_case("test_cowgirl_cone_protocol.yaml" ; "The Cowgirl Cone Protocol")]
10301089
#[test_case("test_activejoy_protocol.yaml" ; "ActiveJoy Protocol")]
1031-
#[test_case("test_aneros_protocol.yaml" ; "Aneros Protocol")]
1032-
#[test_case("test_bananasome_protocol.yaml" ; "Bananasome Protocol")]
1090+
#[test_case("test_adrienlastic_protocol.yaml" ; "Adrien Lastic Protocol")]
1091+
#[test_case("test_amorelie_joy_protocol.yaml" ; "Amorelie Joy Protocol")]
1092+
// Multi-vibrator devices excluded: SingleMotorVibrateCmd addresses all motors,
1093+
// producing different hardware output than VibrateCmd targeting motor 0 only.
1094+
//#[test_case("test_aneros_protocol.yaml" ; "Aneros Protocol")]
1095+
#[test_case("test_ankni_protocol_no_handshake.yaml" ; "Ankni Protocol - No Handshake")]
1096+
#[test_case("test_ankni_protocol.yaml" ; "Ankni Protocol")]
1097+
//#[test_case("test_cachito_protocol.yaml" ; "Cachito Protocol")]
1098+
#[test_case("test_cowgirl_protocol.yaml" ; "The Cowgirl Protocol")]
1099+
#[test_case("test_cupido_protocol.yaml" ; "Cupido Protocol")]
1100+
#[test_case("test_deepsire.yaml" ; "DeepSire Protocol")]
1101+
#[test_case("test_feelingso.yaml" ; "FeelingSo Protocol")]
1102+
#[test_case("test_fluffer_protocol.yaml" ; "Fluffer Protocol")]
1103+
#[test_case("test_foreo_protocol.yaml" ; "Foreo Protocol")]
1104+
#[test_case("test_fox_protocol.yaml" ; "Fox Protocol")]
1105+
//#[test_case("test_fredorch_protocol.yaml" ; "Fredorch Protocol")]
1106+
#[test_case("test_galaku_nebula.yaml" ; "Galaku Pump Protocol - Nebula")]
1107+
#[test_case("test_galaku.yaml" ; "Galaku Protocol")]
1108+
#[test_case("test_hgod_protocol.yaml" ; "Hgod Protocol")]
10331109
#[test_case("test_hismith_auxfun_box.yaml" ; "Hismith Mini Protocol - Auxfun Box")]
1110+
#[test_case("test_hismith_sinloli.yaml" ; "Hismith Mini Protocol - Sinloli")]
1111+
#[test_case("test_hismith_thrusting_cup.yaml" ; "Hismith Protocol - Thrusting Cup")]
10341112
#[test_case("test_hismith_v4.yaml" ; "Hismith Mini Protocol - Hismith v4")]
1113+
#[test_case("test_hismith_wildolo.yaml" ; "Hismith Protocol - Wildolo")]
1114+
#[test_case("test_itoys_protocol.yaml" ; "iToys Protocol")]
1115+
#[test_case("test_lelo_idawave.yaml" ; "Lelo Harmony Protocol - Ida Wave")]
1116+
//#[test_case("test_lelo_tianiharmony.yaml" ; "Lelo Harmony Protocol - Tiani Harmony")]
1117+
#[test_case("test_leten_protocol.yaml" ; "Leten Protocol")]
1118+
#[test_case("test_lovense_battery_non_default.yaml" ; "Lovense Protocol - Lovense Battery (Non-Default Devices)")]
1119+
#[test_case("test_lovense_battery.yaml" ; "Lovense Protocol - Lovense Battery (Default Devices)")]
1120+
#[test_case("test_lovense_flexer_fw2.yaml" ; "Lovense Protocol - Flexer FW2")]
1121+
#[test_case("test_lovense_max.yaml" ; "Lovense Protocol - Lovense Max (Vibrate/Constrict)")]
1122+
#[test_case("test_lovense_single_vibrator.yaml" ; "Lovense Protocol - Single Vibrator Device")]
1123+
#[test_case("test_luvmazer_protocol.yaml" ; "Luvmazer Protocol")]
1124+
#[test_case("test_magic_motion_1_magic_cell.yaml" ; "MagicMotion Protocol 1 - Magic Cell")]
1125+
#[test_case("test_magic_motion_2_equinox.yaml" ; "MagicMotion Protocol 2 - Equinox")]
1126+
#[test_case("test_magic_motion_3_krush.yaml" ; "MagicMotion Protocol 3 - Krush")]
1127+
//#[test_case("test_meese_protocol.yaml" ; "Meese Protocol")]
1128+
#[test_case("test_mizzzee_protocol.yaml" ; "Mizz Zee Protocol")]
1129+
#[test_case("test_mizzzee_v2_protocol.yaml" ; "Mizz Zee v2 Protocol")]
1130+
#[test_case("test_nobra_protocol.yaml" ; "Nobra Protocol")]
1131+
#[test_case("test_omobo_protocol.yaml" ; "Omobo Protocol")]
1132+
#[test_case("test_pink_punch_protocol.yaml" ; "Pink Punch Protocol")]
1133+
#[test_case("test_sakuraneko_koikoi.yaml" ; "Sakuraneko Protocol - Koikoi")]
1134+
#[test_case("test_sakuraneko_protocol.yaml" ; "Sakuraneko Protocol")]
1135+
#[test_case("test_sensee_protocol.yaml" ; "Sensee Diandou Protocol - Rabbit")]
1136+
#[test_case("test_svakom_alex_v2.yaml" ; "Svakom Alex Neo 2")]
1137+
#[test_case("test_svakom_alex.yaml" ; "Svakom Alex Neo")]
1138+
#[test_case("test_wetoy_protocol.yaml" ; "WeToy Protocol")]
1139+
//#[test_case("test_wevibe_4plus.yaml" ; "WeVibe Protocol (Legacy) - 4 Plus")]
1140+
//#[test_case("test_wevibe_chorus.yaml" ; "WeVibe Protocol (Chorus) - Chorus")]
1141+
#[test_case("test_wevibe_moxie.yaml" ; "WeVibe Protocol (8bit) - Moxie")]
1142+
#[test_case("test_wevibe_pivot.yaml" ; "WeVibe Protocol (Legacy) - Pivot")]
1143+
//#[test_case("test_wevibe_vector.yaml" ; "WeVibe Protocol (8bit) - Vector")]
10351144
#[test_case("test_xibao_protocol.yaml" ; "Xibao Protocol")]
1145+
#[test_case("test_xiuxiuda_protocol.yaml" ; "Xiuxiuda Protocol")]
1146+
#[test_case("test_xuanhuan_protocol.yaml" ; "Xuanhuan Protocol")]
10361147
#[tokio::test]
10371148
async fn test_device_protocols_json_v0(test_file: &str) {
10381149
util::device_test::client::client_v0::run_json_test_case(&load_test_case(test_file).await).await;
10391150
}
1040-
*/

crates/buttplug_tests/tests/util/device_test/client/client_v0/device.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ use buttplug_server::message::{
2525
ButtplugClientMessageV0,
2626
ButtplugDeviceMessageNameV0,
2727
ButtplugServerMessageV0,
28+
SingleMotorVibrateCmdV0,
2829
StopDeviceCmdV0,
2930
};
3031
use futures::channel::oneshot;
@@ -226,6 +227,11 @@ impl ButtplugClientDevice {
226227
})
227228
}
228229

230+
/// Commands device to vibrate at a single speed (all motors).
231+
pub fn single_motor_vibrate(&self, speed: f64) -> ButtplugClientResultFuture {
232+
self.send_message_expect_ok(SingleMotorVibrateCmdV0::new(self.index, speed).into())
233+
}
234+
229235
/// Commands device to stop all movement.
230236
pub fn stop(&self) -> ButtplugClientResultFuture {
231237
// All devices accept StopDeviceCmd

crates/buttplug_tests/tests/util/device_test/client/client_v0/mod.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,15 @@ async fn run_test_client_command(command: &TestClientCommand, device: &Arc<Buttp
3939
use TestClientCommand::*;
4040
match command {
4141
Scalar(_) => {}
42-
Vibrate(_) => {}
42+
Vibrate(msg) => {
43+
// V0 only has SingleMotorVibrateCmd — use the first subcommand's speed.
44+
// This produces identical hardware output for single-vibrator devices.
45+
let speed = msg.first().map(|s| s.speed()).unwrap_or(0.0);
46+
device
47+
.single_motor_vibrate(speed)
48+
.await
49+
.expect("SingleMotorVibrate failed");
50+
}
4351
Stop => {
4452
device.stop().await.expect("Stop failed");
4553
}

0 commit comments

Comments
 (0)