1- #![ allow( clippy:: disallowed_names) ]
2-
31use std:: collections:: HashSet ;
42use std:: io:: Write ;
53use std:: time:: { Duration , Instant } ;
@@ -16,7 +14,7 @@ use ruff_db::source::source_text;
1614use ruff_db:: system:: {
1715 OsSystem , System , SystemPath , SystemPathBuf , UserConfigDirectoryOverrideGuard ,
1816} ;
19- use ruff_db:: Upcast ;
17+ use ruff_db:: { Db as _ , Upcast } ;
2018use ruff_python_ast:: PythonVersion ;
2119
2220struct TestCase {
@@ -1790,3 +1788,82 @@ fn changes_to_user_configuration() -> anyhow::Result<()> {
17901788
17911789 Ok ( ( ) )
17921790}
1791+
1792+ /// Tests that renaming a file from `lib.py` to `Lib.py` is correctly reflected.
1793+ ///
1794+ /// This test currently fails on case-insensitive systems because `Files` is case-sensitive
1795+ /// but the `System::metadata` call isn't. This means that
1796+ /// Red Knot considers both `Lib.py` and `lib.py` to exist when only `lib.py` does
1797+ ///
1798+ /// The incoming change events then are no-ops because they don't change either file's
1799+ /// status nor does it update their last modified time (renaming a file doesn't bump it's
1800+ /// last modified timestamp).
1801+ ///
1802+ /// Fixing this requires to either make `Files` case-insensitive and store the
1803+ /// real-case path (if it differs) on `File` or make `Files` use a
1804+ /// case-sensitive `System::metadata` call. This does open the question if all
1805+ /// `System` calls should be case sensitive. This would be the most consistent
1806+ /// but might be hard to pull off.
1807+ ///
1808+ /// What the right solution is also depends on if Red Knot itself should be case
1809+ /// sensitive or not. E.g. should `include="src"` be case sensitive on all systems
1810+ /// or only on case-sensitive systems?
1811+ ///
1812+ /// Lastly, whatever solution we pick must also work well with VS Code which,
1813+ /// unfortunately ,doesn't propagate casing-only renames.
1814+ /// <https://github.com/rust-lang/rust-analyzer/issues/9581>
1815+ #[ ignore]
1816+ #[ test]
1817+ fn rename_files_casing_only ( ) -> anyhow:: Result < ( ) > {
1818+ let mut case = setup ( [ ( "lib.py" , "class Foo: ..." ) ] ) ?;
1819+
1820+ assert ! (
1821+ resolve_module( case. db( ) , & ModuleName :: new( "lib" ) . unwrap( ) ) . is_some( ) ,
1822+ "Expected `lib` module to exist."
1823+ ) ;
1824+ assert_eq ! (
1825+ resolve_module( case. db( ) , & ModuleName :: new( "Lib" ) . unwrap( ) ) ,
1826+ None ,
1827+ "Expected `Lib` module not to exist"
1828+ ) ;
1829+
1830+ // Now rename `lib.py` to `Lib.py`
1831+ if case. db ( ) . system ( ) . case_sensitivity ( ) . is_case_sensitive ( ) {
1832+ std:: fs:: rename (
1833+ case. project_path ( "lib.py" ) . as_std_path ( ) ,
1834+ case. project_path ( "Lib.py" ) . as_std_path ( ) ,
1835+ )
1836+ . context ( "Failed to rename `lib.py` to `Lib.py`" ) ?;
1837+ } else {
1838+ // On case-insensitive file systems, renaming a file to a different casing is a no-op.
1839+ // Rename to a different name first
1840+ std:: fs:: rename (
1841+ case. project_path ( "lib.py" ) . as_std_path ( ) ,
1842+ case. project_path ( "temp.py" ) . as_std_path ( ) ,
1843+ )
1844+ . context ( "Failed to rename `lib.py` to `temp.py`" ) ?;
1845+
1846+ std:: fs:: rename (
1847+ case. project_path ( "temp.py" ) . as_std_path ( ) ,
1848+ case. project_path ( "Lib.py" ) . as_std_path ( ) ,
1849+ )
1850+ . context ( "Failed to rename `temp.py` to `Lib.py`" ) ?;
1851+ }
1852+
1853+ let changes = case. stop_watch ( event_for_file ( "Lib.py" ) ) ;
1854+ case. apply_changes ( changes) ;
1855+
1856+ // Resolving `lib` should now fail but `Lib` should now succeed
1857+ assert_eq ! (
1858+ resolve_module( case. db( ) , & ModuleName :: new( "lib" ) . unwrap( ) ) ,
1859+ None ,
1860+ "Expected `lib` module to no longer exist."
1861+ ) ;
1862+
1863+ assert ! (
1864+ resolve_module( case. db( ) , & ModuleName :: new( "Lib" ) . unwrap( ) ) . is_some( ) ,
1865+ "Expected `Lib` module to exist"
1866+ ) ;
1867+
1868+ Ok ( ( ) )
1869+ }
0 commit comments