mirror of
https://github.com/farion1231/cc-switch.git
synced 2026-08-01 04:02:02 +08:00
fix(ci): run backend checks on Windows/macOS and repair platform-gated tests (#5138)
* fix(ci): run backend checks on Windows/macOS and repair platform-gated tests
The backend CI job ran only on ubuntu-22.04, so every #[cfg(target_os =
"windows")] / macOS path — tests and clippy lints alike — was never
compiled or run. As a result main shipped 8 failing Windows unit tests and
a hard `cargo clippy -- -D warnings` failure on Windows, all invisible to
CI because it only builds and tests Linux.
Changes:
- ci: run the backend job on a {ubuntu-22.04, windows-latest, macos-latest}
matrix (fail-fast: false); gate the apt install step on runner.os ==
'Linux' and use `shell: bash` for the dist placeholder so it works on all
three runners.
- misc.rs: fix 6 stale `anchored_upgrade_windows` tests. Commit 5092fe51
removed codex from `prefers_official_update`, so codex now anchors to the
package manager with no `codex update ||` prefix; update the five
package-manager expectations accordingly, and retarget the no-sibling
"official-update-without-fallback" test to `claude` (still in the list) so
that branch stays covered instead of being silently dropped.
- codex_state_db.rs / codex_history_migration.rs: the two sqlite_home tests
built TOML basic strings from Windows paths, whose backslashes are invalid
escape sequences and made the override fail to parse; switch to TOML
literal (single-quoted) strings so the path is taken verbatim.
- clippy (13 Windows-only warnings): move `use std::io::Write` into the
#[cfg(unix)] block that actually uses it; convert 3 unneeded `return`s in
Windows-gated tail positions to expressions; mark 9 POSIX-shell helpers
#[cfg_attr(windows, allow(dead_code))] since they are used only by the
macOS/Linux terminal launchers.
Verified locally (Windows 11, Rust 1.95.0):
cargo test --lib → 1748 passed; 0 failed (was 1740/8)
cargo clippy -- -D warnings → clean (was 13 errors)
cargo fmt --check → clean
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(skills): resolve home via get_home_dir so tests isolate on Windows
services/skill.rs called dirs::home_dir() directly in five places. On
Windows dirs::home_dir() resolves through the Known Folder API and
ignores HOME/USERPROFILE, so the CC_SWITCH_TEST_HOME override set by the
test harness never applied: the skill_sync integration tests scanned the
runner's real user profile, found no skills, and failed (the first panic
then poisoned the shared test mutex, cascading to all seven). On Unix
the harness also sets HOME, which dirs::home_dir() honors, so the suite
passed there by accident.
Route all five call sites through crate::config::get_home_dir(), the
crate-wide home resolver that prefers CC_SWITCH_TEST_HOME. The three
now-unreachable GET_HOME_DIR_FAILED error branches are dropped:
get_home_dir() is infallible, matching every other config-dir resolver.
Production behavior is unchanged (CC_SWITCH_TEST_HOME is unset outside
tests, so get_home_dir falls through to dirs::home_dir).
Add a regression test that discriminates on every platform by pointing
CC_SWITCH_TEST_HOME at a temp dir different from $HOME; it is marked
#[serial_test::serial] to stay mutually exclusive with the other tests
mutating the same process-global variable. Verified the test fails
against the old code on macOS with the same failure mode as Windows CI.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Jason <farion1231@gmail.com>
This commit is contained in:
@@ -638,7 +638,7 @@ fn build_tool_action_line(
|
||||
// (npm/pnpm)或 .exe(volta),静态命令头部是 `npm`(也是 .cmd)、`py` 等——
|
||||
// 全部加 `call ` 前缀,风格统一且语义正确。含空格的头部已被 `win_quote_path_for_batch`
|
||||
// 加上双引号,call 对带引号的路径解析正常。
|
||||
return Ok(format!("call {command}"));
|
||||
Ok(format!("call {command}"))
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
@@ -1069,6 +1069,8 @@ fn default_flag_for_shell(shell: &str) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
// 以下 shell 解析辅助函数仅被 macOS/Linux 的终端启动逻辑使用;Windows 非 test 编译下为死代码。
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn fallback_user_shell() -> &'static str {
|
||||
if cfg!(target_os = "macos") {
|
||||
"/bin/zsh"
|
||||
@@ -1077,6 +1079,7 @@ fn fallback_user_shell() -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn valid_user_shell_path(shell: &str) -> bool {
|
||||
if shell.is_empty()
|
||||
|| !shell.starts_with('/')
|
||||
@@ -1100,11 +1103,13 @@ fn is_executable_file(path: &std::path::Path) -> bool {
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn is_executable_file(path: &std::path::Path) -> bool {
|
||||
path.is_file()
|
||||
}
|
||||
|
||||
/// 获取用户默认 shell 的完整路径;异常或被污染的 SHELL 回退到平台默认值。
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn get_user_shell() -> String {
|
||||
std::env::var("SHELL")
|
||||
.ok()
|
||||
@@ -1113,6 +1118,7 @@ fn get_user_shell() -> String {
|
||||
}
|
||||
|
||||
/// 构建 exec 行:引号保护 shell 路径,交还用户 shell 让其按默认规则加载 rc 配置。
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn build_exec_line(shell: &str, cwd: Option<&Path>) -> String {
|
||||
let quoted_shell = shell_single_quote(shell);
|
||||
|
||||
@@ -1132,6 +1138,7 @@ fn build_exec_line(shell: &str, cwd: Option<&Path>) -> String {
|
||||
}
|
||||
|
||||
/// 构建 provider 命令行:通过用户 shell 的交互模式执行,确保 GUI 启动的终端也加载用户 PATH。
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn build_provider_command_line(shell: &str, config_path: &str, cwd: Option<&Path>) -> String {
|
||||
let claude_command = format!("claude --settings {}", shell_single_quote(config_path));
|
||||
let command = cwd
|
||||
@@ -1152,6 +1159,7 @@ fn build_provider_command_line(shell: &str, config_path: &str, cwd: Option<&Path
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn provider_command_flag_for_shell(shell: &str) -> &'static str {
|
||||
match shell.rsplit('/').next().unwrap_or(shell) {
|
||||
"dash" | "sh" => "-c",
|
||||
@@ -1160,6 +1168,7 @@ fn provider_command_flag_for_shell(shell: &str) -> &'static str {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn build_final_shell_cd_command(shell: &str, cwd: Option<&Path>) -> String {
|
||||
if matches!(shell.rsplit('/').next().unwrap_or(shell), "zsh") {
|
||||
return String::new();
|
||||
@@ -2731,7 +2740,7 @@ fn launch_terminal_with_env(
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
launch_windows_terminal(&temp_dir, &config_file, cwd)?;
|
||||
return Ok(());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "windows")))]
|
||||
@@ -3252,6 +3261,7 @@ del \"%~f0\" >nul 2>&1
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg_attr(windows, allow(dead_code))]
|
||||
fn shell_single_quote(value: &str) -> String {
|
||||
format!("'{}'", value.replace('\'', "'\"'\"'"))
|
||||
}
|
||||
@@ -3838,11 +3848,8 @@ mod tests {
|
||||
let (_dir, sub, bin_path) = setup_sibling("Volta", "codex.cmd", &["volta.exe"]);
|
||||
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
|
||||
let volta_full = format!("{}\\volta.exe", sub.to_string_lossy());
|
||||
let expected = format!(
|
||||
"{} update || call {} install @openai/codex",
|
||||
expect_quoted_path(&bin_path),
|
||||
expect_quoted_path(&volta_full)
|
||||
);
|
||||
// codex 自 5092fe51 起不在 prefers_official_update:直接锚定包管理器,无 `codex update ||` 前缀。
|
||||
let expected = format!("{} install @openai/codex", expect_quoted_path(&volta_full));
|
||||
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
|
||||
}
|
||||
|
||||
@@ -3854,9 +3861,9 @@ mod tests {
|
||||
let (_dir, sub, bin_path) = setup_sibling("pnpm", "codex.cmd", &["pnpm.cmd"]);
|
||||
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
|
||||
let pnpm_full = format!("{}\\pnpm.cmd", sub.to_string_lossy());
|
||||
// codex 自 5092fe51 起不在 prefers_official_update:直接锚定包管理器,无 `codex update ||` 前缀。
|
||||
let expected = format!(
|
||||
"{} update || call {} add -g @openai/codex@latest",
|
||||
expect_quoted_path(&bin_path),
|
||||
"{} add -g @openai/codex@latest",
|
||||
expect_quoted_path(&pnpm_full)
|
||||
);
|
||||
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
|
||||
@@ -3888,9 +3895,9 @@ mod tests {
|
||||
let (_dir, sub, bin_path) = setup_sibling("v22.0.0", "codex.cmd", &["npm.cmd"]);
|
||||
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
|
||||
let npm_full = format!("{}\\npm.cmd", sub.to_string_lossy());
|
||||
// codex 自 5092fe51 起不在 prefers_official_update:直接锚定包管理器,无 `codex update ||` 前缀。
|
||||
let expected = format!(
|
||||
"{} update || call {} i -g @openai/codex@latest",
|
||||
expect_quoted_path(&bin_path),
|
||||
"{} i -g @openai/codex@latest",
|
||||
expect_quoted_path(&npm_full)
|
||||
);
|
||||
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
|
||||
@@ -3898,10 +3905,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn windows_no_sibling_uses_cli_update_without_package_fallback() {
|
||||
// sibling npm.cmd 不存在(纯独立二进制)时,仍可锚定到 CLI 自身跑官方 update。
|
||||
// 只是没有包管理器 fallback。
|
||||
let (_dir, _sub, bin_path) = setup_sibling("", "codex.cmd", &[]);
|
||||
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
|
||||
// sibling 包管理器不存在(纯独立二进制)时,仍可锚定到 CLI 自身跑官方 update。
|
||||
// 只是没有包管理器 fallback。用 claude —— codex 自 5092fe51 起一律走 npm 锚定,
|
||||
// 已不再有官方 self-update 分支,故改用仍在 prefers_official_update 的 claude 覆盖此路径。
|
||||
let (_dir, _sub, bin_path) = setup_sibling("", "claude.cmd", &[]);
|
||||
let cmd = anchored_command_from_paths("claude", &bin_path, &bin_path);
|
||||
let expected = format!("{} update", expect_quoted_path(&bin_path));
|
||||
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
|
||||
}
|
||||
@@ -3977,9 +3985,9 @@ mod tests {
|
||||
let (_dir, sub, bin_path) = setup_sibling("Program Files", "codex.cmd", &["npm.cmd"]);
|
||||
let cmd = anchored_command_from_paths("codex", &bin_path, &bin_path);
|
||||
let npm_full = format!("{}\\npm.cmd", sub.to_string_lossy());
|
||||
// codex 走 npm 锚定(5092fe51),含空格的 npm 全路径仍必须被双引号包裹。
|
||||
let expected = format!(
|
||||
"{} update || call {} i -g @openai/codex@latest",
|
||||
expect_quoted_path(&bin_path),
|
||||
"{} i -g @openai/codex@latest",
|
||||
expect_quoted_path(&npm_full)
|
||||
);
|
||||
assert_eq!(cmd.as_deref(), Some(expected.as_str()));
|
||||
@@ -4001,9 +4009,10 @@ mod tests {
|
||||
// 含空格的环境**(否则 sub 本身含空格 + 子目录 `path%foo%` 触发 4 倍 `%` 转义
|
||||
// 会让 expected 漏引号、假失败)。
|
||||
let npm_full = format!("{}\\npm.cmd", sub.to_string_lossy());
|
||||
// codex 走 npm 锚定(5092fe51):batch 行 = `call <npm 全路径> i -g ...`,
|
||||
// 含字面 `%` 的 npm 路径仍须 4 倍转义。
|
||||
let expected = format!(
|
||||
"call {} update || call {} i -g @openai/codex@latest",
|
||||
expect_quoted_path(&bin_path),
|
||||
"call {} i -g @openai/codex@latest",
|
||||
expect_quoted_path(&npm_full)
|
||||
);
|
||||
assert_eq!(batch_line, expected);
|
||||
|
||||
@@ -247,7 +247,7 @@ pub async fn install_update_and_restart(app: AppHandle) -> Result<bool, String>
|
||||
"Windows 更新安装失败: {e}。已执行退出前清理,代理或 Live 接管可能已暂停;请重启应用或重新开启代理后再试。"
|
||||
)
|
||||
})?;
|
||||
return Ok(true);
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
|
||||
Reference in New Issue
Block a user