From 7147a41ec076e1a597dbb6f5b7e11a6b61805932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dav=C3=AD=C3=B0=20Steinn=20Geirsson?= Date: Tue, 17 Mar 2026 19:08:51 +0000 Subject: [PATCH] sound: add StreamDirection enum and --streams CLI arg Add a StreamDirection enum (Output, Input) with ValueEnum derive and a Display impl, and a --streams field to SoundArgs that accepts comma-separated stream direction values with output,input as the default. Add 7 CLI parsing tests covering the common cases. Co-Authored-By: Claude Opus 4.6 (1M context) --- vhost-device-sound/src/args.rs | 19 ++++++ vhost-device-sound/src/main.rs | 102 +++++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+) diff --git a/vhost-device-sound/src/args.rs b/vhost-device-sound/src/args.rs index 7a57bb2..334115e 100644 --- a/vhost-device-sound/src/args.rs +++ b/vhost-device-sound/src/args.rs @@ -1,6 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 or BSD-3-Clause //! An arguments type for the binary interface of this library. +use std::fmt; use std::os::fd::RawFd; use std::path::PathBuf; @@ -24,6 +25,24 @@ pub struct SoundArgs { #[clap(long)] #[clap(value_enum)] pub backend: BackendType, + /// Stream directions to enable (comma-separated). + #[clap(long, value_delimiter = ',', default_values_t = [StreamDirection::Output, StreamDirection::Input])] + pub streams: Vec, +} + +#[derive(ValueEnum, Clone, Copy, Debug, Eq, PartialEq)] +pub enum StreamDirection { + Output, + Input, +} + +impl fmt::Display for StreamDirection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.to_possible_value() + .expect("no values are skipped") + .get_name() + .fmt(f) + } } #[derive(ValueEnum, Clone, Copy, Default, Debug, Eq, PartialEq)] diff --git a/vhost-device-sound/src/main.rs b/vhost-device-sound/src/main.rs index 2d4c4df..abe7aff 100644 --- a/vhost-device-sound/src/main.rs +++ b/vhost-device-sound/src/main.rs @@ -34,6 +34,7 @@ mod tests { use clap::Parser; use rstest::*; use std::path::PathBuf; + use vhost_device_sound::args::StreamDirection; use vhost_device_sound::BackendType; use super::*; @@ -56,6 +57,107 @@ mod tests { assert_eq!(args.socket_fd, Some(3)); } + #[test] + fn test_cli_streams_output_only() { + let args: SoundArgs = Parser::parse_from([ + "", + "--socket", + "/tmp/vhost-sound.socket", + "--backend", + "null", + "--streams", + "output", + ]); + assert_eq!(args.streams, vec![StreamDirection::Output]); + } + + #[test] + fn test_cli_streams_input_only() { + let args: SoundArgs = Parser::parse_from([ + "", + "--socket", + "/tmp/vhost-sound.socket", + "--backend", + "null", + "--streams", + "input", + ]); + assert_eq!(args.streams, vec![StreamDirection::Input]); + } + + #[test] + fn test_cli_streams_both() { + let args: SoundArgs = Parser::parse_from([ + "", + "--socket", + "/tmp/vhost-sound.socket", + "--backend", + "null", + "--streams", + "output,input", + ]); + assert!(args.streams.contains(&StreamDirection::Output)); + assert!(args.streams.contains(&StreamDirection::Input)); + } + + #[test] + fn test_cli_streams_default() { + let args: SoundArgs = Parser::parse_from([ + "", + "--socket", + "/tmp/vhost-sound.socket", + "--backend", + "null", + ]); + assert!(args.streams.contains(&StreamDirection::Output)); + assert!(args.streams.contains(&StreamDirection::Input)); + assert_eq!(args.streams.len(), 2); + } + + #[test] + fn test_cli_streams_invalid() { + let result = SoundArgs::try_parse_from([ + "", + "--socket", + "/tmp/vhost-sound.socket", + "--backend", + "null", + "--streams", + "foobar", + ]); + assert!(result.is_err()); + } + + #[test] + fn test_cli_streams_duplicate() { + let args: SoundArgs = Parser::parse_from([ + "", + "--socket", + "/tmp/vhost-sound.socket", + "--backend", + "null", + "--streams", + "output,output", + ]); + // Duplicates are accepted by clap; the contains() conversion in main.rs + // naturally deduplicates since it produces booleans. + assert!(args.streams.contains(&StreamDirection::Output)); + } + + #[test] + fn test_cli_streams_empty() { + let result = SoundArgs::try_parse_from([ + "", + "--socket", + "/tmp/vhost-sound.socket", + "--backend", + "null", + "--streams", + "", + ]); + assert!(result.is_err()); + } + #[rstest] #[case::null_backend("null", BackendType::Null)] #[cfg_attr(