@@ -6,14 +6,15 @@ use std::time::Duration;
66use anyhow:: { anyhow, Context } ;
77
88use red_knot_python_semantic:: { resolve_module, ModuleName , Program , PythonVersion , SitePackages } ;
9- use red_knot_workspace:: db:: RootDatabase ;
9+ use red_knot_workspace:: db:: { Db , RootDatabase } ;
1010use red_knot_workspace:: watch;
1111use red_knot_workspace:: watch:: { directory_watcher, WorkspaceWatcher } ;
1212use red_knot_workspace:: workspace:: settings:: { Configuration , SearchPathConfiguration } ;
1313use red_knot_workspace:: workspace:: WorkspaceMetadata ;
1414use ruff_db:: files:: { system_path_to_file, File , FileError } ;
1515use ruff_db:: source:: source_text;
1616use ruff_db:: system:: { OsSystem , SystemPath , SystemPathBuf } ;
17+ use ruff_db:: testing:: setup_logging;
1718use ruff_db:: Upcast ;
1819
1920struct TestCase {
@@ -69,7 +70,6 @@ impl TestCase {
6970 Some ( all_events)
7071 }
7172
72- #[ cfg( unix) ]
7373 fn take_watch_changes ( & self ) -> Vec < watch:: ChangeEvent > {
7474 self . try_take_watch_changes ( Duration :: from_secs ( 10 ) )
7575 . expect ( "Expected watch changes but observed none" )
@@ -110,8 +110,8 @@ impl TestCase {
110110 ) -> anyhow:: Result < ( ) > {
111111 let program = Program :: get ( self . db ( ) ) ;
112112
113- self . configuration . search_paths = configuration. clone ( ) ;
114- let new_settings = configuration. into_settings ( self . db . workspace ( ) . root ( & self . db ) ) ;
113+ let new_settings = configuration. to_settings ( self . db . workspace ( ) . root ( & self . db ) ) ;
114+ self . configuration . search_paths = configuration;
115115
116116 program. update_search_paths ( & mut self . db , & new_settings) ?;
117117
@@ -204,7 +204,9 @@ where
204204 . as_utf8_path ( )
205205 . canonicalize_utf8 ( )
206206 . with_context ( || "Failed to canonicalize root path." ) ?,
207- ) ;
207+ )
208+ . simplified ( )
209+ . to_path_buf ( ) ;
208210
209211 let workspace_path = root_path. join ( "workspace" ) ;
210212
@@ -241,8 +243,7 @@ where
241243 search_paths,
242244 } ;
243245
244- let workspace =
245- WorkspaceMetadata :: from_path ( & workspace_path, & system, Some ( configuration. clone ( ) ) ) ?;
246+ let workspace = WorkspaceMetadata :: discover ( & workspace_path, & system, Some ( & configuration) ) ?;
246247
247248 let db = RootDatabase :: new ( workspace, system) ?;
248249
@@ -1311,3 +1312,138 @@ mod unix {
13111312 Ok ( ( ) )
13121313 }
13131314}
1315+
1316+ #[ test]
1317+ fn nested_packages_delete_root ( ) -> anyhow:: Result < ( ) > {
1318+ let mut case = setup ( |root : & SystemPath , workspace_root : & SystemPath | {
1319+ std:: fs:: write (
1320+ workspace_root. join ( "pyproject.toml" ) . as_std_path ( ) ,
1321+ r#"
1322+ [project]
1323+ name = "inner"
1324+ "# ,
1325+ ) ?;
1326+
1327+ std:: fs:: write (
1328+ root. join ( "pyproject.toml" ) . as_std_path ( ) ,
1329+ r#"
1330+ [project]
1331+ name = "outer"
1332+ "# ,
1333+ ) ?;
1334+
1335+ Ok ( ( ) )
1336+ } ) ?;
1337+
1338+ assert_eq ! (
1339+ case. db( ) . workspace( ) . root( case. db( ) ) ,
1340+ & * case. workspace_path( "" )
1341+ ) ;
1342+
1343+ std:: fs:: remove_file ( case. workspace_path ( "pyproject.toml" ) . as_std_path ( ) ) ?;
1344+
1345+ let changes = case. stop_watch ( ) ;
1346+
1347+ case. apply_changes ( changes) ;
1348+
1349+ // It should now pick up the outer workspace.
1350+ assert_eq ! ( case. db( ) . workspace( ) . root( case. db( ) ) , case. root_path( ) ) ;
1351+
1352+ Ok ( ( ) )
1353+ }
1354+
1355+ #[ test]
1356+ fn added_package ( ) -> anyhow:: Result < ( ) > {
1357+ let _ = setup_logging ( ) ;
1358+ let mut case = setup ( [
1359+ (
1360+ "pyproject.toml" ,
1361+ r#"
1362+ [project]
1363+ name = "inner"
1364+
1365+ [tool.knot.workspace]
1366+ members = ["packages/*"]
1367+ "# ,
1368+ ) ,
1369+ (
1370+ "packages/a/pyproject.toml" ,
1371+ r#"
1372+ [project]
1373+ name = "a"
1374+ "# ,
1375+ ) ,
1376+ ] ) ?;
1377+
1378+ assert_eq ! ( case. db( ) . workspace( ) . packages( case. db( ) ) . len( ) , 2 ) ;
1379+
1380+ std:: fs:: create_dir ( case. workspace_path ( "packages/b" ) . as_std_path ( ) )
1381+ . context ( "failed to create folder for package 'b'" ) ?;
1382+
1383+ // It seems that the file watcher won't pick up on file changes shortly after the folder
1384+ // was created... I suspect this is because most file watchers don't support recursive
1385+ // file watching. Instead, file-watching libraries manually implement recursive file watching
1386+ // by setting a watcher for each directory. But doing this obviously "lags" behind.
1387+ case. take_watch_changes ( ) ;
1388+
1389+ std:: fs:: write (
1390+ case. workspace_path ( "packages/b/pyproject.toml" )
1391+ . as_std_path ( ) ,
1392+ r#"
1393+ [project]
1394+ name = "b"
1395+ "# ,
1396+ )
1397+ . context ( "failed to write pyproject.toml for package b" ) ?;
1398+
1399+ let changes = case. stop_watch ( ) ;
1400+
1401+ case. apply_changes ( changes) ;
1402+
1403+ assert_eq ! ( case. db( ) . workspace( ) . packages( case. db( ) ) . len( ) , 3 ) ;
1404+
1405+ Ok ( ( ) )
1406+ }
1407+
1408+ #[ test]
1409+ fn removed_package ( ) -> anyhow:: Result < ( ) > {
1410+ let mut case = setup ( [
1411+ (
1412+ "pyproject.toml" ,
1413+ r#"
1414+ [project]
1415+ name = "inner"
1416+
1417+ [tool.knot.workspace]
1418+ members = ["packages/*"]
1419+ "# ,
1420+ ) ,
1421+ (
1422+ "packages/a/pyproject.toml" ,
1423+ r#"
1424+ [project]
1425+ name = "a"
1426+ "# ,
1427+ ) ,
1428+ (
1429+ "packages/b/pyproject.toml" ,
1430+ r#"
1431+ [project]
1432+ name = "b"
1433+ "# ,
1434+ ) ,
1435+ ] ) ?;
1436+
1437+ assert_eq ! ( case. db( ) . workspace( ) . packages( case. db( ) ) . len( ) , 3 ) ;
1438+
1439+ std:: fs:: remove_dir_all ( case. workspace_path ( "packages/b" ) . as_std_path ( ) )
1440+ . context ( "failed to remove package 'b'" ) ?;
1441+
1442+ let changes = case. stop_watch ( ) ;
1443+
1444+ case. apply_changes ( changes) ;
1445+
1446+ assert_eq ! ( case. db( ) . workspace( ) . packages( case. db( ) ) . len( ) , 2 ) ;
1447+
1448+ Ok ( ( ) )
1449+ }
0 commit comments