-
Notifications
You must be signed in to change notification settings - Fork 12.8k
exec-server: canonicalize bound filesystem paths #25149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: starr/skills-path-ref-1-env-path-ref
Are you sure you want to change the base?
Changes from all commits
23e3bb5
0942afd
1b3cada
8c24577
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ use std::fmt; | |
| use std::hash::Hash; | ||
| use std::hash::Hasher; | ||
| use std::io; | ||
| use std::path::Path; | ||
| use std::sync::Arc; | ||
|
|
||
| use codex_utils_absolute_path::AbsolutePathBuf; | ||
|
|
@@ -60,6 +61,30 @@ impl EnvironmentPathRef { | |
| pub fn with_path(&self, path: AbsolutePathBuf) -> Self { | ||
| Self::new(Arc::clone(&self.file_system), path) | ||
| } | ||
|
|
||
| pub async fn join<P: AsRef<Path>>(&self, path: P) -> io::Result<Self> { | ||
| self.file_system | ||
| .join(&self.path, path.as_ref()) | ||
| .await | ||
| .map(|path| self.with_path(path)) | ||
| } | ||
|
|
||
| pub async fn parent(&self) -> io::Result<Option<Self>> { | ||
| self.file_system | ||
| .parent(&self.path) | ||
| .await | ||
| .map(|path| path.map(|path| self.with_path(path))) | ||
| } | ||
|
|
||
| pub async fn canonicalize( | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new canonicalize method accepts a sandbox, but |
||
| &self, | ||
| sandbox: Option<&FileSystemSandboxContext>, | ||
| ) -> io::Result<Self> { | ||
| self.file_system | ||
| .canonicalize(&self.path, sandbox) | ||
| .await | ||
| .map(|path| self.with_path(path)) | ||
| } | ||
| } | ||
|
|
||
| impl PartialEq for EnvironmentPathRef { | ||
|
|
@@ -96,10 +121,16 @@ mod tests { | |
| use codex_utils_absolute_path::test_support::PathBufExt; | ||
| use pretty_assertions::assert_eq; | ||
| use std::collections::HashSet; | ||
| use std::path::Path; | ||
| use std::sync::Mutex; | ||
|
|
||
| use crate::LOCAL_FS; | ||
|
|
||
| #[derive(Clone, Debug, Eq, PartialEq)] | ||
| enum RecordedMethod { | ||
| Canonicalize, | ||
| Join, | ||
| Parent, | ||
| ReadFileText, | ||
| Metadata, | ||
| ReadDirectory, | ||
|
|
@@ -140,8 +171,47 @@ mod tests { | |
| } | ||
| } | ||
|
|
||
| fn local_path_ref(path: AbsolutePathBuf) -> EnvironmentPathRef { | ||
| EnvironmentPathRef::new(Arc::clone(&LOCAL_FS), path) | ||
| } | ||
|
|
||
| #[async_trait] | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Side note but if you want to learn cool stuff about Rust, ask ChatGPT more about |
||
| impl ExecutorFileSystem for RecordingFileSystem { | ||
| async fn canonicalize( | ||
| &self, | ||
| path: &AbsolutePathBuf, | ||
| _sandbox: Option<&FileSystemSandboxContext>, | ||
| ) -> io::Result<AbsolutePathBuf> { | ||
| self.push_call(RecordedCall { | ||
| method: RecordedMethod::Canonicalize, | ||
| path: path.clone(), | ||
| sandbox: None, | ||
| }); | ||
| Ok(path.parent().unwrap()) | ||
| } | ||
|
|
||
| async fn join( | ||
| &self, | ||
| base_path: &AbsolutePathBuf, | ||
| path: &Path, | ||
| ) -> io::Result<AbsolutePathBuf> { | ||
| self.push_call(RecordedCall { | ||
| method: RecordedMethod::Join, | ||
| path: base_path.clone(), | ||
| sandbox: None, | ||
| }); | ||
| AbsolutePathBuf::from_absolute_path_checked(base_path.as_path().join(path)) | ||
| } | ||
|
|
||
| async fn parent(&self, path: &AbsolutePathBuf) -> io::Result<Option<AbsolutePathBuf>> { | ||
| self.push_call(RecordedCall { | ||
| method: RecordedMethod::Parent, | ||
| path: path.clone(), | ||
| sandbox: None, | ||
| }); | ||
| Ok(path.parent()) | ||
| } | ||
|
|
||
| async fn read_file( | ||
| &self, | ||
| path: &AbsolutePathBuf, | ||
|
|
@@ -318,4 +388,129 @@ mod tests { | |
| let set = HashSet::from([left, same, different_path, different_fs]); | ||
| assert_eq!(set.len(), 3); | ||
| } | ||
| #[tokio::test] | ||
| async fn canonicalize_keeps_bound_file_system_identity() { | ||
| let path = std::env::temp_dir().join("skills/demo").abs(); | ||
| let file_system = Arc::new(RecordingFileSystem::default()); | ||
| let path_ref = EnvironmentPathRef::new(file_system.clone(), path.clone()); | ||
|
|
||
| let canonicalized = path_ref | ||
| .canonicalize(/*sandbox*/ None) | ||
| .await | ||
| .expect("canonicalize"); | ||
|
|
||
| assert_eq!(canonicalized.path(), &path.parent().unwrap()); | ||
| assert_eq!( | ||
| canonicalized, | ||
| EnvironmentPathRef::new(file_system.clone(), path.parent().unwrap()) | ||
| ); | ||
| assert_eq!( | ||
| file_system.recorded_calls(), | ||
| vec![RecordedCall { | ||
| method: RecordedMethod::Canonicalize, | ||
| path, | ||
| sandbox: None, | ||
| }] | ||
| ); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn join_keeps_bound_file_system_identity() { | ||
| let path = std::env::temp_dir().join("skills").abs(); | ||
| let file_system = Arc::new(RecordingFileSystem::default()); | ||
| let path_ref = EnvironmentPathRef::new(file_system.clone(), path.clone()); | ||
|
|
||
| assert_eq!( | ||
| path_ref.join(Path::new("demo")).await.ok(), | ||
| Some(EnvironmentPathRef::new( | ||
| file_system.clone(), | ||
| std::env::temp_dir().join("skills/demo").abs(), | ||
| )) | ||
| ); | ||
| assert_eq!( | ||
| file_system.recorded_calls(), | ||
| vec![RecordedCall { | ||
| method: RecordedMethod::Join, | ||
| path, | ||
| sandbox: None, | ||
| }] | ||
| ); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn join_matches_absolute_path_buf_for_tilde_paths() { | ||
| let path_ref = local_path_ref(std::env::temp_dir().join("skills").abs()); | ||
|
|
||
| assert_eq!( | ||
| path_ref | ||
| .join(Path::new("~")) | ||
| .await | ||
| .ok() | ||
| .map(|path_ref| path_ref.path().clone()), | ||
| Some(path_ref.path().join(Path::new("~"))) | ||
| ); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn join_matches_absolute_path_buf_for_parent_dirs() { | ||
| let path_ref = local_path_ref(std::env::temp_dir().join("skills").abs()); | ||
|
|
||
| assert_eq!( | ||
| path_ref | ||
| .join(Path::new("../outside")) | ||
| .await | ||
| .expect("join") | ||
| .path() | ||
| .clone(), | ||
| path_ref.path().join(Path::new("../outside")) | ||
| ); | ||
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn parent_keeps_bound_file_system_identity() { | ||
| let path = std::env::temp_dir().join("skills/demo").abs(); | ||
| let file_system = Arc::new(RecordingFileSystem::default()); | ||
| let path_ref = EnvironmentPathRef::new(file_system.clone(), path.clone()); | ||
|
|
||
| assert_eq!( | ||
| path_ref.parent().await.expect("parent"), | ||
| Some(EnvironmentPathRef::new( | ||
| file_system.clone(), | ||
| std::env::temp_dir().join("skills").abs(), | ||
| )) | ||
| ); | ||
| assert_eq!( | ||
| file_system.recorded_calls(), | ||
| vec![RecordedCall { | ||
| method: RecordedMethod::Parent, | ||
| path, | ||
| sandbox: None, | ||
| }] | ||
| ); | ||
| } | ||
|
|
||
| #[cfg(windows)] | ||
| #[tokio::test] | ||
| async fn join_matches_absolute_path_buf_for_windows_prefixed_and_rooted_paths() { | ||
| let path_ref = local_path_ref(std::env::temp_dir().join("skills").abs()); | ||
|
|
||
| assert_eq!( | ||
| path_ref | ||
| .join(Path::new(r"C:temp")) | ||
| .await | ||
| .expect("join") | ||
| .path() | ||
| .clone(), | ||
| path_ref.path().join(Path::new(r"C:temp")) | ||
| ); | ||
| assert_eq!( | ||
| path_ref | ||
| .join(Path::new(r"\temp")) | ||
| .await | ||
| .expect("join") | ||
| .path() | ||
| .clone(), | ||
| path_ref.path().join(Path::new(r"\temp")) | ||
| ); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.