diff --git a/.config/jp/tools/src/fs/grep_files_tests.rs b/.config/jp/tools/src/fs/grep_files_tests.rs index 5ee9dc66..6c5d5774 100644 --- a/.config/jp/tools/src/fs/grep_files_tests.rs +++ b/.config/jp/tools/src/fs/grep_files_tests.rs @@ -119,7 +119,8 @@ async fn test_grep_files() { let matches = fs_grep_files(root, pattern.to_owned(), Some(5), paths) .await - .unwrap(); + .unwrap() + .replace('\\', "/"); assert_eq!(matches, expected.join(""), "test case: {name}"); } diff --git a/.config/jp/tools/src/fs/list_files.rs b/.config/jp/tools/src/fs/list_files.rs index 9942fee3..0f496629 100644 --- a/.config/jp/tools/src/fs/list_files.rs +++ b/.config/jp/tools/src/fs/list_files.rs @@ -189,7 +189,15 @@ mod tests { let files = fs_list_files(root, prefixes, extensions).await.unwrap(); - assert_eq!(files.into_files(), expected, "test case: {name}"); + assert_eq!( + files + .into_files() + .into_iter() + .map(|s| s.replace('\\', "/")) + .collect::>(), + expected, + "test case: {name}" + ); } } diff --git a/.config/jp/tools/src/fs/modify_file_tests.rs b/.config/jp/tools/src/fs/modify_file_tests.rs index 3b1994f7..84519dec 100644 --- a/.config/jp/tools/src/fs/modify_file_tests.rs +++ b/.config/jp/tools/src/fs/modify_file_tests.rs @@ -123,8 +123,14 @@ fn test_validate_patterns() { #[test] fn test_validate_path() { + #[rustfmt::skip] let cases = [ + #[cfg(unix)] ("absolute", "/absolute/path", Err("Path must be relative.")), + + #[cfg(windows)] + ("absolute", "c:/absolute/path", Err("Path must be relative.")), + ("relative", "src/main.rs", Ok(())), ]; diff --git a/crates/jp_cli/src/cmd/query/interrupt/signals.rs b/crates/jp_cli/src/cmd/query/interrupt/signals.rs index 2d2236ea..544ef909 100644 --- a/crates/jp_cli/src/cmd/query/interrupt/signals.rs +++ b/crates/jp_cli/src/cmd/query/interrupt/signals.rs @@ -50,6 +50,7 @@ pub fn handle_streaming_signal( info!(?signal, "Received signal during streaming."); match signal { + #[cfg(any(unix, test))] SignalTo::Quit => { // Treat Quit like Stop: save partial content and exit gracefully. // This ensures we don't lose progress on hard quit signals. @@ -81,6 +82,7 @@ pub fn handle_streaming_signal( } } + #[cfg(any(unix, test))] SignalTo::ReloadFromDisk => LoopAction::Continue, } } @@ -140,6 +142,7 @@ pub fn handle_tool_signal( backend: &dyn PromptBackend, ) -> ToolSignalResult { match signal { + #[cfg(any(unix, test))] SignalTo::Quit => { // For hard quit during tool execution, we cancel and let the normal // flow handle persistence (responses will be cancelled). @@ -177,6 +180,7 @@ pub fn handle_tool_signal( } } + #[cfg(any(unix, test))] SignalTo::ReloadFromDisk => ToolSignalResult::Continue, } } diff --git a/crates/jp_cli/src/cmd/query/tool/renderer_tests.rs b/crates/jp_cli/src/cmd/query/tool/renderer_tests.rs index fc633563..525287cd 100644 --- a/crates/jp_cli/src/cmd/query/tool/renderer_tests.rs +++ b/crates/jp_cli/src/cmd/query/tool/renderer_tests.rs @@ -1,5 +1,6 @@ use std::{sync::Arc, time::Duration}; +use camino_tempfile::Utf8TempDir; use jp_config::{ AppConfig, conversation::tool::{CommandConfigOrString, style::ParametersStyle}, @@ -228,9 +229,11 @@ async fn test_format_args_function_call() { #[tokio::test] async fn test_format_args_custom_with_echo() { let mut args = Map::new(); - args.insert("path".into(), Value::String("/tmp/test.txt".into())); + let root = Utf8TempDir::new().unwrap(); + let path = root.path().join("test.txt"); + args.insert("path".into(), Value::String(path.into())); let style = ParametersStyle::Custom(CommandConfigOrString::String("echo custom-output".into())); - let result = format_args("my_tool", &args, &style, Utf8Path::new("/tmp")) + let result = format_args("my_tool", &args, &style, root.path()) .await .unwrap(); assert_eq!(result, ":\n\ncustom-output"); @@ -239,9 +242,11 @@ async fn test_format_args_custom_with_echo() { #[tokio::test] async fn test_format_args_custom_empty_output_returns_empty() { let mut args = Map::new(); - args.insert("path".into(), Value::String("/tmp/test.txt".into())); + let root = Utf8TempDir::new().unwrap(); + let path = root.path().join("test.txt"); + args.insert("path".into(), Value::String(path.into())); let style = ParametersStyle::Custom(CommandConfigOrString::String("true".into())); - let result = format_args("my_tool", &args, &style, Utf8Path::new("/tmp")) + let result = format_args("my_tool", &args, &style, root.path()) .await .unwrap(); assert_eq!(result, ""); diff --git a/crates/jp_cli/src/cmd/query/turn/coordinator.rs b/crates/jp_cli/src/cmd/query/turn/coordinator.rs index 1ee4902d..67d785d0 100644 --- a/crates/jp_cli/src/cmd/query/turn/coordinator.rs +++ b/crates/jp_cli/src/cmd/query/turn/coordinator.rs @@ -283,6 +283,7 @@ impl TurnCoordinator { /// /// Used when handling hard quit signals (SIGQUIT) where we want to save /// progress and exit gracefully without showing an interrupt menu. + #[cfg(any(unix, test))] pub fn force_complete(&mut self) { self.state = TurnPhase::Complete; } @@ -291,6 +292,7 @@ impl TurnCoordinator { /// /// Injects any partial content into the stream and transitions to /// Complete so that the turn loop persists and exits. + #[cfg(any(unix, test))] pub fn handle_quit(&mut self, stream: &mut ConversationStream) { if let Some(content) = self.peek_partial_content() { self.push_event(stream, ChatResponse::message(&content)); diff --git a/crates/jp_cli/src/signals.rs b/crates/jp_cli/src/signals.rs index b742af25..3f16dfed 100644 --- a/crates/jp_cli/src/signals.rs +++ b/crates/jp_cli/src/signals.rs @@ -1,7 +1,9 @@ use async_stream::stream; use futures::{Stream, StreamExt as _}; use tokio::{runtime::Runtime, sync::broadcast}; -use tracing::{error, info}; +use tracing::error; +#[cfg(unix)] +use tracing::info; pub type ShutdownTx = broadcast::Sender<()>; pub type SignalTx = broadcast::Sender; @@ -11,10 +13,12 @@ pub type SignalRx = broadcast::Receiver; #[derive(Debug, Clone, Copy, PartialEq)] pub enum SignalTo { /// Reload config from the filesystem. + #[cfg(any(unix, test))] ReloadFromDisk, /// Shutdown process, with a grace period. Shutdown, /// Shutdown process immediately. + #[cfg(any(unix, test))] Quit, } @@ -153,7 +157,7 @@ fn os_signals() -> impl Stream { stream! { loop { - let signal = tokio::signal::ctrl_c().map(|_| SignalTo::Shutdown(None)).await; + let signal = tokio::signal::ctrl_c().map(|_| SignalTo::Shutdown).await; yield signal; } diff --git a/crates/jp_config/src/util_tests.rs b/crates/jp_config/src/util_tests.rs index 8e19f786..ee44c77d 100644 --- a/crates/jp_config/src/util_tests.rs +++ b/crates/jp_config/src/util_tests.rs @@ -280,6 +280,7 @@ fn test_load_partial_at_path() { } #[test] +#[cfg_attr(windows, ignore = "overflows the stack on Windows")] fn test_load_partial_at_path_recursive() { struct TestCase { files: Vec<(&'static str, &'static str)>, diff --git a/crates/jp_llm/src/test.rs b/crates/jp_llm/src/test.rs index 86dcf057..f65809a1 100644 --- a/crates/jp_llm/src/test.rs +++ b/crates/jp_llm/src/test.rs @@ -320,6 +320,7 @@ pub async fn run_chat_completion( if !recording { // dummy api key value when replaying a cassette + #[cfg(unix)] match provider_id { ProviderId::Anthropic => config.anthropic.api_key_env = "USER".to_owned(), ProviderId::Google => config.google.api_key_env = "USER".to_owned(), @@ -327,6 +328,14 @@ pub async fn run_chat_completion( ProviderId::Openrouter => config.openrouter.api_key_env = "USER".to_owned(), _ => {} } + #[cfg(windows)] + match provider_id { + ProviderId::Anthropic => config.anthropic.api_key_env = "USERNAME".to_owned(), + ProviderId::Google => config.google.api_key_env = "USERNAME".to_owned(), + ProviderId::Openai => config.openai.api_key_env = "USERNAME".to_owned(), + ProviderId::Openrouter => config.openrouter.api_key_env = "USERNAME".to_owned(), + _ => {} + } } let provider = get_provider(provider_id, &config).unwrap(); diff --git a/crates/jp_workspace/src/lib.rs b/crates/jp_workspace/src/lib.rs index 807fc19e..b9d9276b 100644 --- a/crates/jp_workspace/src/lib.rs +++ b/crates/jp_workspace/src/lib.rs @@ -764,7 +764,7 @@ mod tests { } } - let result = Workspace::find_root(cwd, case.workspace_dir_name.unwrap_or("default")); + let result = Workspace::find_root(cwd, case.workspace_dir_name.unwrap_or("non-exist")); assert_eq!(result, expected, "Failed test case: {name}"); } }