@@ -12,7 +12,9 @@ use red_knot_project::{Db, ProjectDatabase, ProjectMetadata};
1212use red_knot_python_semantic:: { resolve_module, ModuleName , PythonPlatform , PythonVersion } ;
1313use ruff_db:: files:: { system_path_to_file, File , FileError } ;
1414use ruff_db:: source:: source_text;
15- use ruff_db:: system:: { OsSystem , OsUserDirectoryOverride , SystemPath , SystemPathBuf } ;
15+ use ruff_db:: system:: {
16+ OsSystem , System , SystemPath , SystemPathBuf , UserConfigDirectoryOverrideGuard ,
17+ } ;
1618use ruff_db:: Upcast ;
1719
1820struct TestCase {
@@ -220,17 +222,44 @@ where
220222}
221223
222224trait SetupFiles {
223- fn setup ( self , root_path : & SystemPath , project_path : & SystemPath ) -> anyhow:: Result < ( ) > ;
225+ fn setup ( self , context : & SetupContext ) -> anyhow:: Result < ( ) > ;
226+ }
227+
228+ struct SetupContext < ' a > {
229+ system : & ' a OsSystem ,
230+ root_path : & ' a SystemPath ,
231+ }
232+
233+ impl < ' a > SetupContext < ' a > {
234+ fn system ( & self ) -> & ' a OsSystem {
235+ self . system
236+ }
237+
238+ fn join_project_path ( & self , relative : impl AsRef < SystemPath > ) -> SystemPathBuf {
239+ self . project_path ( ) . join ( relative)
240+ }
241+
242+ fn project_path ( & self ) -> & SystemPath {
243+ self . system . current_directory ( )
244+ }
245+
246+ fn root_path ( & self ) -> & ' a SystemPath {
247+ self . root_path
248+ }
249+
250+ fn join_root_path ( & self , relative : impl AsRef < SystemPath > ) -> SystemPathBuf {
251+ self . root_path ( ) . join ( relative)
252+ }
224253}
225254
226255impl < const N : usize , P > SetupFiles for [ ( P , & ' static str ) ; N ]
227256where
228257 P : AsRef < SystemPath > ,
229258{
230- fn setup ( self , _root_path : & SystemPath , project_path : & SystemPath ) -> anyhow:: Result < ( ) > {
259+ fn setup ( self , context : & SetupContext ) -> anyhow:: Result < ( ) > {
231260 for ( relative_path, content) in self {
232261 let relative_path = relative_path. as_ref ( ) ;
233- let absolute_path = project_path . join ( relative_path) ;
262+ let absolute_path = context . join_project_path ( relative_path) ;
234263 if let Some ( parent) = absolute_path. parent ( ) {
235264 std:: fs:: create_dir_all ( parent) . with_context ( || {
236265 format ! ( "Failed to create parent directory for file `{relative_path}`" )
@@ -250,24 +279,23 @@ where
250279
251280impl < F > SetupFiles for F
252281where
253- F : FnOnce ( & SystemPath , & SystemPath ) -> anyhow:: Result < ( ) > ,
282+ F : FnOnce ( & SetupContext ) -> anyhow:: Result < ( ) > ,
254283{
255- fn setup ( self , root_path : & SystemPath , project_path : & SystemPath ) -> anyhow:: Result < ( ) > {
256- self ( root_path , project_path )
284+ fn setup ( self , context : & SetupContext ) -> anyhow:: Result < ( ) > {
285+ self ( context )
257286 }
258287}
259288
260289fn setup < F > ( setup_files : F ) -> anyhow:: Result < TestCase >
261290where
262291 F : SetupFiles ,
263292{
264- setup_with_options ( setup_files, |_root , _project_path | None )
293+ setup_with_options ( setup_files, |_context | None )
265294}
266295
267- // TODO: Replace with configuration?
268296fn setup_with_options < F > (
269297 setup_files : F ,
270- create_options : impl FnOnce ( & SystemPath , & SystemPath ) -> Option < Options > ,
298+ create_options : impl FnOnce ( & SetupContext ) -> Option < Options > ,
271299) -> anyhow:: Result < TestCase >
272300where
273301 F : SetupFiles ,
@@ -295,13 +323,17 @@ where
295323 std:: fs:: create_dir_all ( project_path. as_std_path ( ) )
296324 . with_context ( || format ! ( "Failed to create project directory `{project_path}`" ) ) ?;
297325
326+ let system = OsSystem :: new ( & project_path) ;
327+ let setup_context = SetupContext {
328+ system : & system,
329+ root_path : & root_path,
330+ } ;
331+
298332 setup_files
299- . setup ( & root_path , & project_path )
333+ . setup ( & setup_context )
300334 . context ( "Failed to setup test files" ) ?;
301335
302- let system = OsSystem :: new ( & project_path) ;
303-
304- if let Some ( options) = create_options ( & root_path, & project_path) {
336+ if let Some ( options) = create_options ( & setup_context) {
305337 std:: fs:: write (
306338 project_path. join ( "pyproject.toml" ) . as_std_path ( ) ,
307339 toml:: to_string ( & PyProject {
@@ -791,10 +823,12 @@ fn directory_deleted() -> anyhow::Result<()> {
791823
792824#[ test]
793825fn search_path ( ) -> anyhow:: Result < ( ) > {
794- let mut case = setup_with_options ( [ ( "bar.py" , "import sub.a" ) ] , |root_path , _project_path | {
826+ let mut case = setup_with_options ( [ ( "bar.py" , "import sub.a" ) ] , |context | {
795827 Some ( Options {
796828 environment : Some ( EnvironmentOptions {
797- extra_paths : Some ( vec ! [ RelativePathBuf :: cli( root_path. join( "site_packages" ) ) ] ) ,
829+ extra_paths : Some ( vec ! [ RelativePathBuf :: cli(
830+ context. join_root_path( "site_packages" ) ,
831+ ) ] ) ,
798832 ..EnvironmentOptions :: default ( )
799833 } ) ,
800834 ..Options :: default ( )
@@ -855,10 +889,12 @@ fn add_search_path() -> anyhow::Result<()> {
855889
856890#[ test]
857891fn remove_search_path ( ) -> anyhow:: Result < ( ) > {
858- let mut case = setup_with_options ( [ ( "bar.py" , "import sub.a" ) ] , |root_path , _project_path | {
892+ let mut case = setup_with_options ( [ ( "bar.py" , "import sub.a" ) ] , |context | {
859893 Some ( Options {
860894 environment : Some ( EnvironmentOptions {
861- extra_paths : Some ( vec ! [ RelativePathBuf :: cli( root_path. join( "site_packages" ) ) ] ) ,
895+ extra_paths : Some ( vec ! [ RelativePathBuf :: cli(
896+ context. join_root_path( "site_packages" ) ,
897+ ) ] ) ,
862898 ..EnvironmentOptions :: default ( )
863899 } ) ,
864900 ..Options :: default ( )
@@ -896,7 +932,7 @@ import os
896932print(sys.last_exc, os.getegid())
897933"# ,
898934 ) ] ,
899- |_root_path , _project_path | {
935+ |_context | {
900936 Some ( Options {
901937 environment : Some ( EnvironmentOptions {
902938 python_version : Some ( RangedValue :: cli ( PythonVersion :: PY311 ) ) ,
@@ -944,21 +980,31 @@ print(sys.last_exc, os.getegid())
944980#[ test]
945981fn changed_versions_file ( ) -> anyhow:: Result < ( ) > {
946982 let mut case = setup_with_options (
947- |root_path : & SystemPath , project_path : & SystemPath | {
948- std:: fs:: write ( project_path. join ( "bar.py" ) . as_std_path ( ) , "import sub.a" ) ?;
949- std:: fs:: create_dir_all ( root_path. join ( "typeshed/stdlib" ) . as_std_path ( ) ) ?;
950- std:: fs:: write ( root_path. join ( "typeshed/stdlib/VERSIONS" ) . as_std_path ( ) , "" ) ?;
983+ |context : & SetupContext | {
984+ std:: fs:: write (
985+ context. join_project_path ( "bar.py" ) . as_std_path ( ) ,
986+ "import sub.a" ,
987+ ) ?;
988+ std:: fs:: create_dir_all ( context. join_root_path ( "typeshed/stdlib" ) . as_std_path ( ) ) ?;
989+ std:: fs:: write (
990+ context
991+ . join_root_path ( "typeshed/stdlib/VERSIONS" )
992+ . as_std_path ( ) ,
993+ "" ,
994+ ) ?;
951995 std:: fs:: write (
952- root_path. join ( "typeshed/stdlib/os.pyi" ) . as_std_path ( ) ,
996+ context
997+ . join_root_path ( "typeshed/stdlib/os.pyi" )
998+ . as_std_path ( ) ,
953999 "# not important" ,
9541000 ) ?;
9551001
9561002 Ok ( ( ) )
9571003 } ,
958- |root_path , _project_path | {
1004+ |context | {
9591005 Some ( Options {
9601006 environment : Some ( EnvironmentOptions {
961- typeshed : Some ( RelativePathBuf :: cli ( root_path . join ( "typeshed" ) ) ) ,
1007+ typeshed : Some ( RelativePathBuf :: cli ( context . join_root_path ( "typeshed" ) ) ) ,
9621008 ..EnvironmentOptions :: default ( )
9631009 } ) ,
9641010 ..Options :: default ( )
@@ -1009,12 +1055,12 @@ fn changed_versions_file() -> anyhow::Result<()> {
10091055/// we're seeing is that Windows only emits a single event, similar to Linux.
10101056#[ test]
10111057fn hard_links_in_project ( ) -> anyhow:: Result < ( ) > {
1012- let mut case = setup ( |_root : & SystemPath , project : & SystemPath | {
1013- let foo_path = project . join ( "foo.py" ) ;
1058+ let mut case = setup ( |context : & SetupContext | {
1059+ let foo_path = context . join_project_path ( "foo.py" ) ;
10141060 std:: fs:: write ( foo_path. as_std_path ( ) , "print('Version 1')" ) ?;
10151061
10161062 // Create a hardlink to `foo`
1017- let bar_path = project . join ( "bar.py" ) ;
1063+ let bar_path = context . join_project_path ( "bar.py" ) ;
10181064 std:: fs:: hard_link ( foo_path. as_std_path ( ) , bar_path. as_std_path ( ) )
10191065 . context ( "Failed to create hard link from foo.py -> bar.py" ) ?;
10201066
@@ -1080,12 +1126,12 @@ fn hard_links_in_project() -> anyhow::Result<()> {
10801126 ignore = "windows doesn't support observing changes to hard linked files."
10811127) ]
10821128fn hard_links_to_target_outside_project ( ) -> anyhow:: Result < ( ) > {
1083- let mut case = setup ( |root : & SystemPath , project : & SystemPath | {
1084- let foo_path = root . join ( "foo.py" ) ;
1129+ let mut case = setup ( |context : & SetupContext | {
1130+ let foo_path = context . join_root_path ( "foo.py" ) ;
10851131 std:: fs:: write ( foo_path. as_std_path ( ) , "print('Version 1')" ) ?;
10861132
10871133 // Create a hardlink to `foo`
1088- let bar_path = project . join ( "bar.py" ) ;
1134+ let bar_path = context . join_project_path ( "bar.py" ) ;
10891135 std:: fs:: hard_link ( foo_path. as_std_path ( ) , bar_path. as_std_path ( ) )
10901136 . context ( "Failed to create hard link from foo.py -> bar.py" ) ?;
10911137
@@ -1188,17 +1234,17 @@ mod unix {
11881234 ignore = "FSEvents doesn't emit change events for symlinked directories outside of the watched paths."
11891235 ) ]
11901236 fn symlink_target_outside_watched_paths ( ) -> anyhow:: Result < ( ) > {
1191- let mut case = setup ( |root : & SystemPath , project : & SystemPath | {
1237+ let mut case = setup ( |context : & SetupContext | {
11921238 // Set up the symlink target.
1193- let link_target = root . join ( "bar" ) ;
1239+ let link_target = context . join_root_path ( "bar" ) ;
11941240 std:: fs:: create_dir_all ( link_target. as_std_path ( ) )
11951241 . context ( "Failed to create link target directory" ) ?;
11961242 let baz_original = link_target. join ( "baz.py" ) ;
11971243 std:: fs:: write ( baz_original. as_std_path ( ) , "def baz(): ..." )
11981244 . context ( "Failed to write link target file" ) ?;
11991245
12001246 // Create a symlink inside the project
1201- let bar = project . join ( "bar" ) ;
1247+ let bar = context . join_project_path ( "bar" ) ;
12021248 std:: os:: unix:: fs:: symlink ( link_target. as_std_path ( ) , bar. as_std_path ( ) )
12031249 . context ( "Failed to create symlink to bar package" ) ?;
12041250
@@ -1269,17 +1315,17 @@ mod unix {
12691315 /// ```
12701316 #[ test]
12711317 fn symlink_inside_project ( ) -> anyhow:: Result < ( ) > {
1272- let mut case = setup ( |_root : & SystemPath , project : & SystemPath | {
1318+ let mut case = setup ( |context : & SetupContext | {
12731319 // Set up the symlink target.
1274- let link_target = project . join ( "patched/bar" ) ;
1320+ let link_target = context . join_project_path ( "patched/bar" ) ;
12751321 std:: fs:: create_dir_all ( link_target. as_std_path ( ) )
12761322 . context ( "Failed to create link target directory" ) ?;
12771323 let baz_original = link_target. join ( "baz.py" ) ;
12781324 std:: fs:: write ( baz_original. as_std_path ( ) , "def baz(): ..." )
12791325 . context ( "Failed to write link target file" ) ?;
12801326
12811327 // Create a symlink inside site-packages
1282- let bar_in_project = project . join ( "bar" ) ;
1328+ let bar_in_project = context . join_project_path ( "bar" ) ;
12831329 std:: os:: unix:: fs:: symlink ( link_target. as_std_path ( ) , bar_in_project. as_std_path ( ) )
12841330 . context ( "Failed to create symlink to bar package" ) ?;
12851331
@@ -1360,9 +1406,9 @@ mod unix {
13601406 #[ test]
13611407 fn symlinked_module_search_path ( ) -> anyhow:: Result < ( ) > {
13621408 let mut case = setup_with_options (
1363- |root : & SystemPath , project : & SystemPath | {
1409+ |context : & SetupContext | {
13641410 // Set up the symlink target.
1365- let site_packages = root . join ( "site-packages" ) ;
1411+ let site_packages = context . join_root_path ( "site-packages" ) ;
13661412 let bar = site_packages. join ( "bar" ) ;
13671413 std:: fs:: create_dir_all ( bar. as_std_path ( ) )
13681414 . context ( "Failed to create bar directory" ) ?;
@@ -1371,7 +1417,8 @@ mod unix {
13711417 . context ( "Failed to write baz.py" ) ?;
13721418
13731419 // Symlink the site packages in the venv to the global site packages
1374- let venv_site_packages = project. join ( ".venv/lib/python3.12/site-packages" ) ;
1420+ let venv_site_packages =
1421+ context. join_project_path ( ".venv/lib/python3.12/site-packages" ) ;
13751422 std:: fs:: create_dir_all ( venv_site_packages. parent ( ) . unwrap ( ) )
13761423 . context ( "Failed to create .venv directory" ) ?;
13771424 std:: os:: unix:: fs:: symlink (
@@ -1382,7 +1429,7 @@ mod unix {
13821429
13831430 Ok ( ( ) )
13841431 } ,
1385- |_root , _project | {
1432+ |_context | {
13861433 Some ( Options {
13871434 environment : Some ( EnvironmentOptions {
13881435 extra_paths : Some ( vec ! [ RelativePathBuf :: cli(
@@ -1452,9 +1499,9 @@ mod unix {
14521499
14531500#[ test]
14541501fn nested_projects_delete_root ( ) -> anyhow:: Result < ( ) > {
1455- let mut case = setup ( |root : & SystemPath , project_root : & SystemPath | {
1502+ let mut case = setup ( |context : & SetupContext | {
14561503 std:: fs:: write (
1457- project_root . join ( "pyproject.toml" ) . as_std_path ( ) ,
1504+ context . join_project_path ( "pyproject.toml" ) . as_std_path ( ) ,
14581505 r#"
14591506 [project]
14601507 name = "inner"
@@ -1464,7 +1511,7 @@ fn nested_projects_delete_root() -> anyhow::Result<()> {
14641511 ) ?;
14651512
14661513 std:: fs:: write (
1467- root . join ( "pyproject.toml" ) . as_std_path ( ) ,
1514+ context . join_root_path ( "pyproject.toml" ) . as_std_path ( ) ,
14681515 r#"
14691516 [project]
14701517 name = "outer"
@@ -1492,31 +1539,37 @@ fn nested_projects_delete_root() -> anyhow::Result<()> {
14921539
14931540#[ test]
14941541fn changes_to_user_configuration ( ) -> anyhow:: Result < ( ) > {
1495- let mut _config_dir_override: Option < OsUserDirectoryOverride > = None ;
1542+ let mut _config_dir_override: Option < UserConfigDirectoryOverrideGuard > = None ;
14961543
1497- let mut case = setup ( |root : & SystemPath , project_root : & SystemPath | {
1544+ let mut case = setup ( |context : & SetupContext | {
14981545 std:: fs:: write (
1499- project_root . join ( "pyproject.toml" ) . as_std_path ( ) ,
1546+ context . join_project_path ( "pyproject.toml" ) . as_std_path ( ) ,
15001547 r#"
15011548 [project]
15021549 name = "test"
15031550 "# ,
15041551 ) ?;
15051552
1506- std:: fs:: write ( project_root. join ( "foo.py" ) . as_std_path ( ) , "a = 10 / 0" ) ?;
1553+ std:: fs:: write (
1554+ context. join_project_path ( "foo.py" ) . as_std_path ( ) ,
1555+ "a = 10 / 0" ,
1556+ ) ?;
15071557
1508- std:: fs:: create_dir_all ( root. join ( "home/.config/knot" ) . as_std_path ( ) ) ?;
1558+ let config_directory = context. join_root_path ( "home/.config" ) ;
1559+ std:: fs:: create_dir_all ( config_directory. join ( "knot" ) . as_std_path ( ) ) ?;
15091560 std:: fs:: write (
1510- root . join ( "home/.config/ knot/knot.toml" ) . as_std_path ( ) ,
1561+ config_directory . join ( "knot/knot.toml" ) . as_std_path ( ) ,
15111562 r#"
15121563 [rules]
15131564 division-by-zero = "ignore"
15141565 "# ,
15151566 ) ?;
15161567
1517- _config_dir_override = Some ( OsUserDirectoryOverride :: scoped ( Some (
1518- root. join ( "home/.config" ) ,
1519- ) ) ) ;
1568+ _config_dir_override = Some (
1569+ context
1570+ . system ( )
1571+ . with_user_config_directory ( Some ( config_directory) ) ,
1572+ ) ;
15201573
15211574 Ok ( ( ) )
15221575 } ) ?;
0 commit comments