Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ _tauri-check-unix:
_tauri-check-windows:
just tauri-check-windows

# Run the Rust plugin tests plus the app-crate suites for feature-gated
# Run the Rust plugin and Berd Call tests plus app-crate suites for feature-gated
# modules (telemetry consent, skill marketplace) with external sidecars
# disabled. Gated lanes pass a name filter and their enabling Cargo feature —
# e.g. `block-telemetry-enforced` swaps in the enforced-consent tests.
Expand All @@ -249,9 +249,10 @@ _tauri-test-skill-marketplace:
[unix]
_tauri-test-unix:
# rust-cache can restore Sherpa's generated cache directory without its native libraries.
if [ "$(uname -s)" = "Linux" ]; then rm -rf src-tauri/target/sherpa-onnx-prebuilt; fi
rm -rf "$(bash ./scripts/resolve-tauri-cargo-target-dir.sh)/sherpa-onnx-prebuilt"
just _tauri-cargo-unix test -p tauri-plugin-berdctl --features server
just _tauri-cargo-unix test -p berdctl
just _tauri-cargo-unix test -p berd-call --locked
just _tauri-cargo-unix test --lib services::acp::goose_serve::tests::storage_identity_guard
just _tauri-cargo-unix test --lib services::goose_config::tests
just _tauri-cargo-unix test --lib commands::message_search::tests
Expand Down
17 changes: 8 additions & 9 deletions src-tauri/crates/berd-call/src/host_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4186,15 +4186,14 @@ mod tests {
},
};
actor.session.lock().unwrap().spokesperson = Some(openai("marin", 1));
actor.saved = Some((
path.clone(),
crate::saved_settings::SavedSettings {
arguments: ["--voice", "Aaron", "--language", "en-US"]
.map(str::to_string)
.to_vec(),
..crate::saved_settings::SavedSettings::default()
},
));
let saved = crate::saved_settings::SavedSettings {
arguments: ["--voice", "Aaron", "--language", "en-US"]
.map(str::to_string)
.to_vec(),
..crate::saved_settings::SavedSettings::default()
};
crate::saved_settings::save(&path, &saved).unwrap();
actor.saved = Some((path.clone(), saved));
let (response, result) = mpsc::sync_channel(1);
actor
.handle_command(ControlCommand::SpokespersonSettings {
Expand Down
6 changes: 4 additions & 2 deletions src-tauri/crates/berd-call/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4029,12 +4029,14 @@ fn run_expert_spokesperson_session(
LiveSideEvent::Handoff { call_id, .. } => call_id,
_ => return Err("live handoff did not produce a handoff ID".into()),
};
runtime.as_ref().expect("initialized runtime").send(
// A provider that expired right behind the handoff has already
// closed the runtime. Its queued terminal event settles the handoff.
let _ = runtime.as_ref().expect("initialized runtime").send(
SpokespersonCommand::Provider(accepted_handoff_tool_output(
&call_id,
&handoff_id,
)?),
)?;
);
}
event @ (SpokespersonEvent::Expired(_) | SpokespersonEvent::SessionLost(_)) => {
let (message, connection_lost) = match event {
Expand Down
4 changes: 3 additions & 1 deletion src-tauri/crates/berd-call/src/openai_spokesperson.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,7 @@ mod tests {
async fn input_cutover_waits_for_committed_transcription_terminal() {
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let endpoint = format!("ws://{}/", listener.local_addr().unwrap());
let (transcribe, proceed) = tokio::sync::oneshot::channel();
let server = tokio::spawn(async move {
let (stream, _) = listener.accept().await.unwrap();
let mut socket = accept_hdr_async(stream, require_test_authorization)
Expand All @@ -1777,7 +1778,7 @@ mod tests {
)
.await;
send_json(&mut socket, json!({"type":"input_audio_buffer.cleared"})).await;
tokio::time::sleep(Duration::from_millis(120)).await;
proceed.await.unwrap();
send_json(
&mut socket,
json!({
Expand Down Expand Up @@ -1805,6 +1806,7 @@ mod tests {
SpokespersonEvent::UserSpeaking { active: true, .. }
));
assert!(events.recv_timeout(Duration::from_millis(60)).is_err());
transcribe.send(()).unwrap();
assert!(matches!(
events.recv_timeout(Duration::from_secs(2)).unwrap(),
SpokespersonEvent::UserFinal { .. }
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/crates/berd-call/tests/management_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn management_usage_errors_are_exit_two_and_do_not_emit_json() {
assert!(
String::from_utf8(output.stderr)
.expect("UTF-8 stderr")
.contains("usage:"),
.contains("Usage:"),
"{args:?}"
);
}
Expand Down
21 changes: 19 additions & 2 deletions src-tauri/crates/berd-call/tests/session_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,7 @@ fn framed_hello_reports_input_initialization_failure_before_ready() {

#[test]
fn expert_spokesperson_renews_before_provider_expiry_without_changing_settings() {
let started = std::time::Instant::now();
let (endpoint_tx, endpoint_rx) = mpsc::sync_channel(1);
let (audio_ready_tx, audio_ready_rx) = mpsc::sync_channel(1);
let (release_speech_tx, release_speech_rx) = mpsc::sync_channel(1);
Expand Down Expand Up @@ -630,18 +631,21 @@ fn expert_spokesperson_renews_before_provider_expiry_without_changing_settings()
let truncate = receive_realtime_json(&mut old).await;
assert_eq!(truncate["type"], "conversation.item.truncate");
send_realtime_json(&mut old, json!({"type":"conversation.item.truncated","item_id":"assistant-1","content_index":0})).await;
send_realtime_json(&mut old, json!({"type":"response.done","response":{"id":"response-1","status":"cancelled"}})).await;
send_realtime_json(&mut old, json!({"type":"input_audio_buffer.speech_stopped","item_id":"user-2"})).await;
send_realtime_json(&mut old, json!({"type":"conversation.item.input_audio_transcription.completed","item_id":"user-2","transcript":"interrupt now"})).await;
// Keep the cancelled response inflight until its successor blocks renewal.
send_realtime_json(&mut old, json!({"type":"response.created","response":{"id":"response-2"}})).await;
send_realtime_json(&mut old, json!({"type":"response.done","response":{"id":"response-1","status":"cancelled"}})).await;
send_realtime_json(&mut old, json!({"type":"response.output_audio_transcript.done","response_id":"response-2","item_id":"assistant-2","output_index":0,"content_index":0,"transcript":"after interruption"})).await;
send_realtime_json(&mut old, json!({"type":"response.done","response":{"id":"response-2","status":"completed"}})).await;

eprintln!("renewal: waiting for candidate at {:?}", started.elapsed());
let (candidate_stream, _) =
tokio::time::timeout(Duration::from_secs(2), listener.accept())
.await
.expect("renewal candidate should connect before provider expiry")
.unwrap();
eprintln!("renewal: candidate connected at {:?}", started.elapsed());
let mut candidate = accept_async(candidate_stream).await.unwrap();
let replacement = receive_realtime_json(&mut candidate).await;
assert_eq!(replacement["type"], "session.update");
Expand All @@ -651,6 +655,7 @@ fn expert_spokesperson_renews_before_provider_expiry_without_changing_settings()
);
assert_eq!(replacement["session"]["audio"]["output"]["speed"], 1.0);
acknowledge_realtime_session(&mut candidate, &replacement).await;
eprintln!("renewal: candidate ready; replaying transcript at {:?}", started.elapsed());
let user_seed = receive_realtime_json(&mut candidate).await;
assert_eq!(user_seed["item"]["role"], "user");
assert_eq!(user_seed["item"]["content"][0]["text"], "remember this");
Expand All @@ -677,10 +682,13 @@ fn expert_spokesperson_renews_before_provider_expiry_without_changing_settings()
assert_eq!(seed["item"]["content"][0]["text"], text);
send_realtime_json(&mut candidate, json!({"type":"conversation.item.created","item":{"id":seed["item"]["id"]}})).await;
}
eprintln!("renewal: transcript replayed; waiting for input barrier at {:?}", started.elapsed());
let clear = receive_realtime_json(&mut old).await;
assert_eq!(clear["type"], "input_audio_buffer.clear");
send_realtime_json(&mut old, json!({"type":"input_audio_buffer.cleared"})).await;
eprintln!("renewal: input barrier acknowledged; waiting for old session close at {:?}", started.elapsed());
let _ = old.next().await;
eprintln!("renewal: old session closed at {:?}", started.elapsed());
renewed_tx.send(()).unwrap();
let _ = candidate.next().await;
});
Expand All @@ -695,7 +703,16 @@ fn expert_spokesperson_renews_before_provider_expiry_without_changing_settings()
);
audio_ready_rx.recv_timeout(Duration::from_secs(2)).unwrap();
release_speech_tx.send(()).unwrap();
renewed_rx.recv_timeout(Duration::from_secs(2)).unwrap();
renewed_rx
.recv_timeout(Duration::from_secs(2))
.unwrap_or_else(|error| {
panic!(
"renewal did not finish at {:?}: {error}; output: {:?}; stderr: {:?}",
started.elapsed(),
session.output.try_iter().collect::<Vec<_>>(),
session.stderr.try_iter().collect::<Vec<_>>()
)
});
let messages = session.shutdown_and_collect();
assert!(messages
.iter()
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/crates/berd-call/tests/synthesis_cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fn synthesis_usage_errors_are_exit_two_and_emit_no_json() {
let output = berd_call(&args);
assert_eq!(output.status.code(), Some(2), "{args:?}");
assert!(output.stdout.is_empty(), "{args:?}");
assert!(String::from_utf8_lossy(&output.stderr).contains("usage:"));
assert!(String::from_utf8_lossy(&output.stderr).contains("Usage:"));
}
}

Expand Down
5 changes: 4 additions & 1 deletion src-tauri/crates/berdctl/tests/discovery_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use std::net::TcpListener;
use std::os::unix::fs::PermissionsExt;
use std::path::PathBuf;
use std::process::{Command, Output};
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread;

const CAPABILITY: &str = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
Expand All @@ -16,12 +17,14 @@ struct Fixture(PathBuf);

impl Fixture {
fn new(port: u16) -> Self {
static NEXT_FIXTURE: AtomicU64 = AtomicU64::new(0);
let sequence = NEXT_FIXTURE.fetch_add(1, Ordering::Relaxed);
let unique = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let dir = std::env::temp_dir().join(format!(
"berdctl-discovery-integration-{}-{unique}",
"berdctl-discovery-integration-{}-{unique}-{sequence}",
std::process::id()
));
fs::create_dir(&dir).unwrap();
Expand Down
Loading