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) <noreply@anthropic.com>
This commit is contained in:
Davíð Steinn Geirsson 2026-03-17 19:08:51 +00:00
parent cde9d12ad9
commit 7147a41ec0
2 changed files with 121 additions and 0 deletions

View file

@ -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<StreamDirection>,
}
#[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)]

View file

@ -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(