Files
codex/codex-rs/utils/path-utils/src/path_utils_tests.rs
rhan-oai 6515a72db7 Preserve runtime workspace roots across thread resume (#43848)
## Why

Resuming a thread should retain its selected workspace folders, including additional roots and explicit empty selections. Resume overrides also need to survive a subsequent resume when no turn has run.

## What changed

- Persist `runtime_workspace_roots` in startup metadata and thread settings snapshots, separately from explicit environment selections and permission-profile roots.
- Restore roots from the latest snapshot owned by the resumed thread, falling back to owned startup metadata only when no snapshot exists. Honor explicit `runtimeWorkspaceRoots` overrides, retarget the old `cwd` root when `cwd` changes, deduplicate roots, and validate restored paths for the current host.
- Checkpoint effective settings on resume and restored settings after revert. Reload resume configuration if saved workspace roots change during loading.
- Normalize Windows rollout path spellings when matching thread search results, preserving selection of the correct rollout after revert, including compressed rollouts.

## Testing

Add regression coverage for workspace restoration, empty and explicit overrides, foreign paths, compaction and revert, resume checkpoints without recency changes, concurrent settings persistence, and rollout search path matching.

GitOrigin-RevId: d98d9d34dd63934d441120916c61c12b69e7f062
2026-09-08 16:24:38 +00:00

137 lines
3.8 KiB
Rust

use super::replace_path_and_deduplicate;
use pretty_assertions::assert_eq;
use std::path::Path;
#[test]
fn replace_path_and_deduplicate_preserves_other_paths_and_order() {
let old_path = Path::new("old");
let new_path = Path::new("new");
let extra = Path::new("extra");
let descendant = Path::new("old/child");
assert_eq!(
replace_path_and_deduplicate(
vec![old_path, extra, descendant, new_path, extra],
old_path,
new_path,
),
vec![new_path, extra, descendant]
);
}
#[cfg(unix)]
mod symlinks {
use super::super::resolve_symlink_write_paths;
use pretty_assertions::assert_eq;
use std::os::unix::fs::symlink;
#[test]
fn symlink_cycles_fall_back_to_root_write_path() -> std::io::Result<()> {
let dir = tempfile::tempdir()?;
let a = dir.path().join("a");
let b = dir.path().join("b");
symlink(&b, &a)?;
symlink(&a, &b)?;
let resolved = resolve_symlink_write_paths(&a)?;
assert_eq!(resolved.read_path, None);
assert_eq!(resolved.write_path, a);
Ok(())
}
}
#[cfg(target_os = "linux")]
mod wsl {
use super::super::normalize_for_wsl_with_flag;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
#[test]
fn wsl_mnt_drive_paths_lowercase() {
let normalized =
normalize_for_wsl_with_flag(PathBuf::from("/mnt/C/Users/Dev"), /*is_wsl*/ true);
assert_eq!(normalized, PathBuf::from("/mnt/c/users/dev"));
}
#[test]
fn wsl_non_drive_paths_unchanged() {
let path = PathBuf::from("/mnt/cc/Users/Dev");
let normalized = normalize_for_wsl_with_flag(path.clone(), /*is_wsl*/ true);
assert_eq!(normalized, path);
}
#[test]
fn wsl_non_mnt_paths_unchanged() {
let path = PathBuf::from("/home/Dev");
let normalized = normalize_for_wsl_with_flag(path.clone(), /*is_wsl*/ true);
assert_eq!(normalized, path);
}
}
mod native_workdir {
use super::super::normalize_for_native_workdir_with_flag;
use pretty_assertions::assert_eq;
use std::path::PathBuf;
#[cfg(target_os = "windows")]
#[test]
fn windows_verbatim_paths_are_simplified() {
let path = PathBuf::from(r"\\?\D:\c\x\worktrees\2508\swift-base");
let normalized = normalize_for_native_workdir_with_flag(path, /*is_windows*/ true);
assert_eq!(
normalized,
PathBuf::from(r"D:\c\x\worktrees\2508\swift-base")
);
}
#[test]
fn non_windows_paths_are_unchanged() {
let path = PathBuf::from(r"\\?\D:\c\x\worktrees\2508\swift-base");
let normalized =
normalize_for_native_workdir_with_flag(path.clone(), /*is_windows*/ false);
assert_eq!(normalized, path);
}
}
mod path_comparison {
use super::super::paths_match_after_normalization;
use std::path::PathBuf;
#[test]
fn matches_identical_existing_paths() -> std::io::Result<()> {
let dir = tempfile::tempdir()?;
assert!(paths_match_after_normalization(dir.path(), dir.path()));
Ok(())
}
#[test]
fn falls_back_to_raw_equality_when_paths_cannot_be_normalized() {
assert!(paths_match_after_normalization(
PathBuf::from("missing"),
PathBuf::from("missing"),
));
assert!(!paths_match_after_normalization(
PathBuf::from("missing-a"),
PathBuf::from("missing-b"),
));
}
#[cfg(windows)]
#[test]
fn matches_windows_verbatim_paths() -> std::io::Result<()> {
let dir = tempfile::tempdir()?;
let verbatim_dir = PathBuf::from(format!(r"\\?\{}", dir.path().display()));
assert!(paths_match_after_normalization(verbatim_dir, dir.path()));
Ok(())
}
}